@marlinjai/studio-mcp 0.3.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
@@ -8,6 +8,7 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/
8
8
 
9
9
  // src/config.ts
10
10
  var DEFAULT_BASE_URL = "https://studio.lumitra.co";
11
+ var LEGACY_API_KEY_VAR = "LUMITRA_STUDIO_SERVICE_TOKEN";
11
12
  var ConfigError = class extends Error {
12
13
  constructor(message) {
13
14
  super(message);
@@ -40,10 +41,11 @@ function resolveConfig(env = process.env) {
40
41
  `LUMITRA_STUDIO_BASE_URL is not a valid http(s) URL (got ${JSON.stringify(rawBase)})`
41
42
  );
42
43
  }
43
- const apiKey = readEnvVar("LUMITRA_STUDIO_API_KEY", env) ?? readEnvVar("LUMITRA_STUDIO_SERVICE_TOKEN", env);
44
+ const apiKey = readEnvVar("LUMITRA_STUDIO_API_KEY", env);
44
45
  if (!apiKey) {
46
+ const legacyHint = readEnvVar(LEGACY_API_KEY_VAR, env) ? ` ${LEGACY_API_KEY_VAR} is set, but that legacy name is no longer read since 0.4.0: provide the same key as LUMITRA_STUDIO_API_KEY.` : "";
45
47
  throw new ConfigError(
46
- 'Missing API key: set LUMITRA_STUDIO_API_KEY (or the legacy LUMITRA_STUDIO_SERVICE_TOKEN) to a Lumitra Studio bearer token. An unexpanded "${...}" placeholder counts as unset.'
48
+ `Missing API key: set LUMITRA_STUDIO_API_KEY to a Lumitra Studio bearer token. An unexpanded "\${...}" placeholder counts as unset.${legacyHint}`
47
49
  );
48
50
  }
49
51
  return { baseUrl, apiKey };
@@ -93,7 +95,11 @@ function apiMessageFromBody(body, fallback) {
93
95
  if (typeof o.error === "string" && o.error.length > 0) {
94
96
  const detail = typeof o.message === "string" ? o.message : "";
95
97
  const adds = detail.length > 0 && !o.error.includes(detail) && !detail.includes(o.error);
96
- 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})`;
97
103
  const quota = quotaDetail(o);
98
104
  return quota ? `${headline} (${quota})` : headline;
99
105
  }
@@ -153,6 +159,14 @@ var StudioClient = class {
153
159
  return this.json("GET", "/api/v1/projects");
154
160
  }
155
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
+ */
156
170
  generate(body) {
157
171
  return this.json("POST", "/api/generate", { json: body });
158
172
  }
@@ -441,7 +455,124 @@ function studio3dCreditsToUsd(credits) {
441
455
  return Math.round(credits * STUDIO3D_USD_PER_CREDIT * 1e3) / 1e3;
442
456
  }
443
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
+ }
444
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.
445
576
  // ── 3D AI Studio: 3D generation ────────────────────────────────────────
446
577
  // `credits` is what the model's DEFAULT request bills (the options its
447
578
  // builder sends when a node sets none), priced by providers/studio3d/
@@ -538,6 +669,7 @@ var MODEL_CATALOG = [
538
669
  label: "Nano Banana 2 (KIE)",
539
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.",
540
671
  cost: { usd: 0.04, usdByResolution: { "1K": 0.04, "2K": 0.06, "4K": 0.09 } },
672
+ listPrice: { source: "fal", model: "fal/nano-banana-2" },
541
673
  capabilities: { resolutions: ["1K", "2K", "4K"] }
542
674
  },
543
675
  {
@@ -550,6 +682,7 @@ var MODEL_CATALOG = [
550
682
  label: "Imagen 4 Fast (KIE)",
551
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.",
552
684
  cost: { usd: 0.02 },
685
+ listPrice: { source: "owner", usd: 0.02 },
553
686
  capabilities: { resolutions: [] }
554
687
  },
555
688
  {
@@ -559,11 +692,15 @@ var MODEL_CATALOG = [
559
692
  task: "text-to-image",
560
693
  label: "Seedream V4 (KIE)",
561
694
  description: "ByteDance Seedream V4 via KIE. Stylized output not available on Vertex. No IP indemnity.",
562
- cost: { usd: 0.0175 }
695
+ cost: { usd: 0.0175 },
696
+ listPrice: { source: "unrecorded" }
563
697
  },
564
698
  // ── KIE.ai: newer image models, the cheaper route (price feed 2026-09-23) ─
565
699
  // Billed from the credits KIE reports on each task. KIE offers no data
566
- // 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.
567
704
  {
568
705
  id: "kie/nano-banana-2-edit",
569
706
  provider: "kie",
@@ -572,6 +709,7 @@ var MODEL_CATALOG = [
572
709
  label: "Nano Banana 2 Edit (KIE)",
573
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.",
574
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" },
575
713
  capabilities: { resolutions: ["1K", "2K", "4K"], needsImageInput: true, maxInputImages: 8 }
576
714
  },
577
715
  {
@@ -582,6 +720,7 @@ var MODEL_CATALOG = [
582
720
  label: "Nano Banana Pro (KIE)",
583
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).",
584
722
  cost: { usd: 0.09, usdByResolution: { "1K": 0.09, "2K": 0.09, "4K": 0.12 } },
723
+ listPrice: { source: "fal", model: "fal/nano-banana-pro" },
585
724
  capabilities: { resolutions: ["1K", "2K", "4K"], maxInputImages: 0 }
586
725
  },
587
726
  {
@@ -592,6 +731,7 @@ var MODEL_CATALOG = [
592
731
  label: "Nano Banana Pro Edit (KIE)",
593
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).",
594
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" },
595
735
  capabilities: { resolutions: ["1K", "2K", "4K"], needsImageInput: true, maxInputImages: 8 }
596
736
  },
597
737
  {
@@ -602,6 +742,7 @@ var MODEL_CATALOG = [
602
742
  label: "Seedream 5 Lite (KIE)",
603
743
  description: "ByteDance Seedream 5 Lite via KIE, 2K or 4K. $0.0275 per image (fal: $0.035).",
604
744
  cost: { usd: 0.0275 },
745
+ listPrice: { source: "fal", model: "fal/seedream-5-lite" },
605
746
  capabilities: { resolutions: ["2K", "4K"], maxInputImages: 0 }
606
747
  },
607
748
  {
@@ -612,6 +753,7 @@ var MODEL_CATALOG = [
612
753
  label: "Seedream 5 Lite Edit (KIE)",
613
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).",
614
755
  cost: { usd: 0.0275 },
756
+ listPrice: { source: "fal", model: "fal/seedream-5-lite-edit" },
615
757
  capabilities: { resolutions: ["2K", "4K"], needsImageInput: true, maxInputImages: 10 }
616
758
  },
617
759
  {
@@ -622,6 +764,7 @@ var MODEL_CATALOG = [
622
764
  label: "Seedream 5 Pro (KIE)",
623
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).",
624
766
  cost: { usd: 0.035, usdByResolution: { "1K": 0.035, "2K": 0.07 } },
767
+ listPrice: { source: "fal", model: "fal/seedream-5-pro" },
625
768
  capabilities: { resolutions: ["1K", "2K"], maxInputImages: 0 }
626
769
  },
627
770
  {
@@ -632,6 +775,7 @@ var MODEL_CATALOG = [
632
775
  label: "Seedream 5 Pro Edit (KIE)",
633
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.",
634
777
  cost: { usd: 0.035, usdByResolution: { "1K": 0.035, "2K": 0.07 } },
778
+ listPrice: { source: "fal", model: "fal/seedream-5-pro-edit" },
635
779
  capabilities: { resolutions: ["1K", "2K"], needsImageInput: true, maxInputImages: 10 }
636
780
  },
637
781
  // ── fal.ai: image gen ─────────────────────────────────────────────────
@@ -926,8 +1070,11 @@ var MODEL_CATALOG = [
926
1070
  // ── KIE.ai video: the cheaper route to the same upstream models ──────────
927
1071
  // Async via KIE's Veo and createTask APIs (providers/kie-video.ts). 20 to 85%
928
1072
  // below fal (KIE price feed, 2026-09-23). KIE offers no data processing
929
- // agreement, so real-person uploads belong on fal. Anchors are the model's
930
- // 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.
931
1078
  {
932
1079
  id: "kie/veo-3-1-lite",
933
1080
  provider: "kie",
@@ -936,6 +1083,7 @@ var MODEL_CATALOG = [
936
1083
  label: "Veo 3.1 Lite (KIE)",
937
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.",
938
1085
  cost: { usd: 0.15 },
1086
+ listPrice: { source: "owner", model: "openrouter/veo-3-1-lite" },
939
1087
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
940
1088
  },
941
1089
  {
@@ -946,6 +1094,7 @@ var MODEL_CATALOG = [
946
1094
  label: "Veo 3.1 Fast (KIE)",
947
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.",
948
1096
  cost: { usd: 0.3 },
1097
+ listPrice: { source: "owner", model: "openrouter/veo-3-1-fast" },
949
1098
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
950
1099
  },
951
1100
  {
@@ -956,6 +1105,7 @@ var MODEL_CATALOG = [
956
1105
  label: "Veo 3.1 (KIE)",
957
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).",
958
1107
  cost: { usd: 1.25 },
1108
+ listPrice: { source: "owner", model: "openrouter/veo-3-1" },
959
1109
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
960
1110
  },
961
1111
  // ── OpenRouter video: Google Veo 3.1 at Google's own prices ───────────────
@@ -1002,6 +1152,7 @@ var MODEL_CATALOG = [
1002
1152
  label: "Grok Imagine (KIE)",
1003
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.",
1004
1154
  cost: { usd: 0.135 },
1155
+ listPrice: { source: "fal", model: "fal/grok-imagine-image-to-video" },
1005
1156
  capabilities: { needsImageInput: true, maxInputImages: 1 }
1006
1157
  },
1007
1158
  {
@@ -1012,6 +1163,7 @@ var MODEL_CATALOG = [
1012
1163
  label: "Grok Imagine 1.5 (KIE, preview)",
1013
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.",
1014
1165
  cost: { usd: 0.18 },
1166
+ listPrice: { source: "fal", model: "fal/grok-imagine-1-5-image-to-video" },
1015
1167
  capabilities: { needsImageInput: true, maxInputImages: 1 }
1016
1168
  },
1017
1169
  {
@@ -1022,6 +1174,7 @@ var MODEL_CATALOG = [
1022
1174
  label: "Kling 2.5 Turbo Pro (KIE)",
1023
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).",
1024
1176
  cost: { usd: 0.21 },
1177
+ listPrice: { source: "fal", model: "fal/kling-v2-5-image-to-video" },
1025
1178
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
1026
1179
  },
1027
1180
  {
@@ -1032,6 +1185,7 @@ var MODEL_CATALOG = [
1032
1185
  label: "Kling 2.6 (KIE)",
1033
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).",
1034
1187
  cost: { usd: 0.275 },
1188
+ listPrice: { source: "fal", model: "fal/kling-2-6-image-to-video" },
1035
1189
  capabilities: { needsImageInput: true, maxInputImages: 1 }
1036
1190
  },
1037
1191
  {
@@ -1043,6 +1197,7 @@ var MODEL_CATALOG = [
1043
1197
  refusesPhotorealPeople: true,
1044
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).",
1045
1199
  cost: { usd: 1.025 },
1200
+ listPrice: { source: "fal", model: "fal/seedance-2-0-image-to-video" },
1046
1201
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
1047
1202
  },
1048
1203
  {
@@ -1054,6 +1209,7 @@ var MODEL_CATALOG = [
1054
1209
  refusesPhotorealPeople: true,
1055
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.",
1056
1211
  cost: { usd: 1.575 },
1212
+ listPrice: { source: "fal", model: "fal/seedance-2-5-image-to-video" },
1057
1213
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
1058
1214
  },
1059
1215
  {
@@ -1064,6 +1220,7 @@ var MODEL_CATALOG = [
1064
1220
  label: "Wan 3.0 (KIE)",
1065
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).",
1066
1222
  cost: { usd: 0.4 },
1223
+ listPrice: { source: "fal", model: "fal/wan-3-0-image-to-video" },
1067
1224
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
1068
1225
  },
1069
1226
  {
@@ -1074,6 +1231,7 @@ var MODEL_CATALOG = [
1074
1231
  label: "MiniMax H3 (KIE)",
1075
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.",
1076
1233
  cost: { usd: 0.24 },
1234
+ listPrice: { source: "unrecorded" },
1077
1235
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
1078
1236
  },
1079
1237
  {
@@ -1084,6 +1242,7 @@ var MODEL_CATALOG = [
1084
1242
  label: "Gemini Omni 1.1 Flash (KIE)",
1085
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.",
1086
1244
  cost: { usd: 0.525 },
1245
+ listPrice: { source: "unrecorded" },
1087
1246
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
1088
1247
  },
1089
1248
  {
@@ -1094,6 +1253,7 @@ var MODEL_CATALOG = [
1094
1253
  label: "Kling O3 (KIE)",
1095
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.",
1096
1255
  cost: { usd: 0.35 },
1256
+ listPrice: { source: "unrecorded" },
1097
1257
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
1098
1258
  },
1099
1259
  {
@@ -1412,6 +1572,7 @@ var MODEL_CATALOG = [
1412
1572
  label: "Suno V5 (KIE)",
1413
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.",
1414
1574
  cost: { usd: 0.04 },
1575
+ listPrice: { source: "unrecorded" },
1415
1576
  isDefault: true
1416
1577
  },
1417
1578
  // ── audio: voice via ElevenLabs (B2) ───────────────────────────────────
@@ -1460,6 +1621,7 @@ var MODEL_CATALOG = [
1460
1621
  label: "Claude Sonnet 4.5 (KIE)",
1461
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.",
1462
1623
  cost: { usd: 0.01 },
1624
+ listPrice: { source: "unrecorded" },
1463
1625
  isDefault: true
1464
1626
  },
1465
1627
  {
@@ -1469,7 +1631,8 @@ var MODEL_CATALOG = [
1469
1631
  task: "text-generation",
1470
1632
  label: "Claude Opus 4.5 (KIE)",
1471
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.",
1472
- cost: { usd: 0.05 }
1634
+ cost: { usd: 0.05 },
1635
+ listPrice: { source: "unrecorded" }
1473
1636
  },
1474
1637
  {
1475
1638
  id: "openrouter/claude-sonnet-4-6",
@@ -1534,8 +1697,26 @@ var MODEL_CATALOG = [
1534
1697
  description: "Vertex AI direct, newest flash variant. Global region. Preview status."
1535
1698
  }
1536
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
+ }
1537
1718
  function tryGetModel(id) {
1538
- return MODEL_CATALOG.find((m) => m.id === id);
1719
+ return MODEL_CATALOG.find((m) => m.id === id) ?? routeEntry(id);
1539
1720
  }
1540
1721
  var ROWS = [
1541
1722
  [0, "Idle", "DailyActions", "Idle"],
@@ -2463,7 +2644,10 @@ var VIDEO_UNSUPPORTED_PARAMS = {
2463
2644
  ...Object.fromEntries(KLING_O3_I2V_ENDPOINTS.map((e) => [e, ["resolution", "aspectRatio", "seed", "negativePrompt"]])),
2464
2645
  ...Object.fromEntries(LTX_25_I2V_ENDPOINTS.map((e) => [e, ["seed", "negativePrompt"]]))
2465
2646
  };
2466
- 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
+ };
2467
2651
  function veoSpec(model, label, perClip) {
2468
2652
  return {
2469
2653
  family: "veo",
@@ -2477,7 +2661,7 @@ function veoSpec(model, label, perClip) {
2477
2661
  audioSwitch: false,
2478
2662
  defaultAudio: true,
2479
2663
  // KIE prices Veo per clip whatever its length, and always renders audio.
2480
- price: (_s, r) => perClip[r] ?? 0,
2664
+ price: (_s, r) => perClip[r],
2481
2665
  build: (i) => ({
2482
2666
  prompt: i.prompt,
2483
2667
  model,
@@ -2684,7 +2868,7 @@ var KIE_VIDEO_SPECS = {
2684
2868
  audioSwitch: false,
2685
2869
  defaultAudio: false,
2686
2870
  // Priced per clip by length; 4K carries a surcharge.
2687
- 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],
2688
2872
  // The first-and-last-frame mode: KIE refuses it combined with registered
2689
2873
  // characters, reference images, audio or video, so none are sent.
2690
2874
  build: (i) => ({
@@ -2710,7 +2894,7 @@ var KIE_VIDEO_SPECS = {
2710
2894
  audioSwitch: true,
2711
2895
  defaultAudio: false,
2712
2896
  // With-audio rates from KIE's price feed, the higher of its two listings.
2713
- 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),
2714
2898
  build: (i) => ({
2715
2899
  prompt: i.prompt,
2716
2900
  image_urls: i.end ? [i.start, i.end] : [i.start],
@@ -3152,7 +3336,124 @@ function imageResolutionTier(value) {
3152
3336
  return null;
3153
3337
  }
3154
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
+ }
3155
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.
3156
3457
  // ── 3D AI Studio: 3D generation ────────────────────────────────────────
3157
3458
  // `credits` is what the model's DEFAULT request bills (the options its
3158
3459
  // builder sends when a node sets none), priced by providers/studio3d/
@@ -3249,6 +3550,7 @@ var MODEL_CATALOG2 = [
3249
3550
  label: "Nano Banana 2 (KIE)",
3250
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.",
3251
3552
  cost: { usd: 0.04, usdByResolution: { "1K": 0.04, "2K": 0.06, "4K": 0.09 } },
3553
+ listPrice: { source: "fal", model: "fal/nano-banana-2" },
3252
3554
  capabilities: { resolutions: ["1K", "2K", "4K"] }
3253
3555
  },
3254
3556
  {
@@ -3261,6 +3563,7 @@ var MODEL_CATALOG2 = [
3261
3563
  label: "Imagen 4 Fast (KIE)",
3262
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.",
3263
3565
  cost: { usd: 0.02 },
3566
+ listPrice: { source: "owner", usd: 0.02 },
3264
3567
  capabilities: { resolutions: [] }
3265
3568
  },
3266
3569
  {
@@ -3270,11 +3573,15 @@ var MODEL_CATALOG2 = [
3270
3573
  task: "text-to-image",
3271
3574
  label: "Seedream V4 (KIE)",
3272
3575
  description: "ByteDance Seedream V4 via KIE. Stylized output not available on Vertex. No IP indemnity.",
3273
- cost: { usd: 0.0175 }
3576
+ cost: { usd: 0.0175 },
3577
+ listPrice: { source: "unrecorded" }
3274
3578
  },
3275
3579
  // ── KIE.ai: newer image models, the cheaper route (price feed 2026-09-23) ─
3276
3580
  // Billed from the credits KIE reports on each task. KIE offers no data
3277
- // 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.
3278
3585
  {
3279
3586
  id: "kie/nano-banana-2-edit",
3280
3587
  provider: "kie",
@@ -3283,6 +3590,7 @@ var MODEL_CATALOG2 = [
3283
3590
  label: "Nano Banana 2 Edit (KIE)",
3284
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.",
3285
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" },
3286
3594
  capabilities: { resolutions: ["1K", "2K", "4K"], needsImageInput: true, maxInputImages: 8 }
3287
3595
  },
3288
3596
  {
@@ -3293,6 +3601,7 @@ var MODEL_CATALOG2 = [
3293
3601
  label: "Nano Banana Pro (KIE)",
3294
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).",
3295
3603
  cost: { usd: 0.09, usdByResolution: { "1K": 0.09, "2K": 0.09, "4K": 0.12 } },
3604
+ listPrice: { source: "fal", model: "fal/nano-banana-pro" },
3296
3605
  capabilities: { resolutions: ["1K", "2K", "4K"], maxInputImages: 0 }
3297
3606
  },
3298
3607
  {
@@ -3303,6 +3612,7 @@ var MODEL_CATALOG2 = [
3303
3612
  label: "Nano Banana Pro Edit (KIE)",
3304
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).",
3305
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" },
3306
3616
  capabilities: { resolutions: ["1K", "2K", "4K"], needsImageInput: true, maxInputImages: 8 }
3307
3617
  },
3308
3618
  {
@@ -3313,6 +3623,7 @@ var MODEL_CATALOG2 = [
3313
3623
  label: "Seedream 5 Lite (KIE)",
3314
3624
  description: "ByteDance Seedream 5 Lite via KIE, 2K or 4K. $0.0275 per image (fal: $0.035).",
3315
3625
  cost: { usd: 0.0275 },
3626
+ listPrice: { source: "fal", model: "fal/seedream-5-lite" },
3316
3627
  capabilities: { resolutions: ["2K", "4K"], maxInputImages: 0 }
3317
3628
  },
3318
3629
  {
@@ -3323,6 +3634,7 @@ var MODEL_CATALOG2 = [
3323
3634
  label: "Seedream 5 Lite Edit (KIE)",
3324
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).",
3325
3636
  cost: { usd: 0.0275 },
3637
+ listPrice: { source: "fal", model: "fal/seedream-5-lite-edit" },
3326
3638
  capabilities: { resolutions: ["2K", "4K"], needsImageInput: true, maxInputImages: 10 }
3327
3639
  },
3328
3640
  {
@@ -3333,6 +3645,7 @@ var MODEL_CATALOG2 = [
3333
3645
  label: "Seedream 5 Pro (KIE)",
3334
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).",
3335
3647
  cost: { usd: 0.035, usdByResolution: { "1K": 0.035, "2K": 0.07 } },
3648
+ listPrice: { source: "fal", model: "fal/seedream-5-pro" },
3336
3649
  capabilities: { resolutions: ["1K", "2K"], maxInputImages: 0 }
3337
3650
  },
3338
3651
  {
@@ -3343,6 +3656,7 @@ var MODEL_CATALOG2 = [
3343
3656
  label: "Seedream 5 Pro Edit (KIE)",
3344
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.",
3345
3658
  cost: { usd: 0.035, usdByResolution: { "1K": 0.035, "2K": 0.07 } },
3659
+ listPrice: { source: "fal", model: "fal/seedream-5-pro-edit" },
3346
3660
  capabilities: { resolutions: ["1K", "2K"], needsImageInput: true, maxInputImages: 10 }
3347
3661
  },
3348
3662
  // ── fal.ai: image gen ─────────────────────────────────────────────────
@@ -3637,8 +3951,11 @@ var MODEL_CATALOG2 = [
3637
3951
  // ── KIE.ai video: the cheaper route to the same upstream models ──────────
3638
3952
  // Async via KIE's Veo and createTask APIs (providers/kie-video.ts). 20 to 85%
3639
3953
  // below fal (KIE price feed, 2026-09-23). KIE offers no data processing
3640
- // agreement, so real-person uploads belong on fal. Anchors are the model's
3641
- // 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.
3642
3959
  {
3643
3960
  id: "kie/veo-3-1-lite",
3644
3961
  provider: "kie",
@@ -3647,6 +3964,7 @@ var MODEL_CATALOG2 = [
3647
3964
  label: "Veo 3.1 Lite (KIE)",
3648
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.",
3649
3966
  cost: { usd: 0.15 },
3967
+ listPrice: { source: "owner", model: "openrouter/veo-3-1-lite" },
3650
3968
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
3651
3969
  },
3652
3970
  {
@@ -3657,6 +3975,7 @@ var MODEL_CATALOG2 = [
3657
3975
  label: "Veo 3.1 Fast (KIE)",
3658
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.",
3659
3977
  cost: { usd: 0.3 },
3978
+ listPrice: { source: "owner", model: "openrouter/veo-3-1-fast" },
3660
3979
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
3661
3980
  },
3662
3981
  {
@@ -3667,6 +3986,7 @@ var MODEL_CATALOG2 = [
3667
3986
  label: "Veo 3.1 (KIE)",
3668
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).",
3669
3988
  cost: { usd: 1.25 },
3989
+ listPrice: { source: "owner", model: "openrouter/veo-3-1" },
3670
3990
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
3671
3991
  },
3672
3992
  // ── OpenRouter video: Google Veo 3.1 at Google's own prices ───────────────
@@ -3713,6 +4033,7 @@ var MODEL_CATALOG2 = [
3713
4033
  label: "Grok Imagine (KIE)",
3714
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.",
3715
4035
  cost: { usd: 0.135 },
4036
+ listPrice: { source: "fal", model: "fal/grok-imagine-image-to-video" },
3716
4037
  capabilities: { needsImageInput: true, maxInputImages: 1 }
3717
4038
  },
3718
4039
  {
@@ -3723,6 +4044,7 @@ var MODEL_CATALOG2 = [
3723
4044
  label: "Grok Imagine 1.5 (KIE, preview)",
3724
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.",
3725
4046
  cost: { usd: 0.18 },
4047
+ listPrice: { source: "fal", model: "fal/grok-imagine-1-5-image-to-video" },
3726
4048
  capabilities: { needsImageInput: true, maxInputImages: 1 }
3727
4049
  },
3728
4050
  {
@@ -3733,6 +4055,7 @@ var MODEL_CATALOG2 = [
3733
4055
  label: "Kling 2.5 Turbo Pro (KIE)",
3734
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).",
3735
4057
  cost: { usd: 0.21 },
4058
+ listPrice: { source: "fal", model: "fal/kling-v2-5-image-to-video" },
3736
4059
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
3737
4060
  },
3738
4061
  {
@@ -3743,6 +4066,7 @@ var MODEL_CATALOG2 = [
3743
4066
  label: "Kling 2.6 (KIE)",
3744
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).",
3745
4068
  cost: { usd: 0.275 },
4069
+ listPrice: { source: "fal", model: "fal/kling-2-6-image-to-video" },
3746
4070
  capabilities: { needsImageInput: true, maxInputImages: 1 }
3747
4071
  },
3748
4072
  {
@@ -3754,6 +4078,7 @@ var MODEL_CATALOG2 = [
3754
4078
  refusesPhotorealPeople: true,
3755
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).",
3756
4080
  cost: { usd: 1.025 },
4081
+ listPrice: { source: "fal", model: "fal/seedance-2-0-image-to-video" },
3757
4082
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
3758
4083
  },
3759
4084
  {
@@ -3765,6 +4090,7 @@ var MODEL_CATALOG2 = [
3765
4090
  refusesPhotorealPeople: true,
3766
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.",
3767
4092
  cost: { usd: 1.575 },
4093
+ listPrice: { source: "fal", model: "fal/seedance-2-5-image-to-video" },
3768
4094
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
3769
4095
  },
3770
4096
  {
@@ -3775,6 +4101,7 @@ var MODEL_CATALOG2 = [
3775
4101
  label: "Wan 3.0 (KIE)",
3776
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).",
3777
4103
  cost: { usd: 0.4 },
4104
+ listPrice: { source: "fal", model: "fal/wan-3-0-image-to-video" },
3778
4105
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
3779
4106
  },
3780
4107
  {
@@ -3785,6 +4112,7 @@ var MODEL_CATALOG2 = [
3785
4112
  label: "MiniMax H3 (KIE)",
3786
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.",
3787
4114
  cost: { usd: 0.24 },
4115
+ listPrice: { source: "unrecorded" },
3788
4116
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
3789
4117
  },
3790
4118
  {
@@ -3795,6 +4123,7 @@ var MODEL_CATALOG2 = [
3795
4123
  label: "Gemini Omni 1.1 Flash (KIE)",
3796
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.",
3797
4125
  cost: { usd: 0.525 },
4126
+ listPrice: { source: "unrecorded" },
3798
4127
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
3799
4128
  },
3800
4129
  {
@@ -3805,6 +4134,7 @@ var MODEL_CATALOG2 = [
3805
4134
  label: "Kling O3 (KIE)",
3806
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.",
3807
4136
  cost: { usd: 0.35 },
4137
+ listPrice: { source: "unrecorded" },
3808
4138
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
3809
4139
  },
3810
4140
  {
@@ -4123,6 +4453,7 @@ var MODEL_CATALOG2 = [
4123
4453
  label: "Suno V5 (KIE)",
4124
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.",
4125
4455
  cost: { usd: 0.04 },
4456
+ listPrice: { source: "unrecorded" },
4126
4457
  isDefault: true
4127
4458
  },
4128
4459
  // ── audio: voice via ElevenLabs (B2) ───────────────────────────────────
@@ -4171,6 +4502,7 @@ var MODEL_CATALOG2 = [
4171
4502
  label: "Claude Sonnet 4.5 (KIE)",
4172
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.",
4173
4504
  cost: { usd: 0.01 },
4505
+ listPrice: { source: "unrecorded" },
4174
4506
  isDefault: true
4175
4507
  },
4176
4508
  {
@@ -4180,7 +4512,8 @@ var MODEL_CATALOG2 = [
4180
4512
  task: "text-generation",
4181
4513
  label: "Claude Opus 4.5 (KIE)",
4182
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.",
4183
- cost: { usd: 0.05 }
4515
+ cost: { usd: 0.05 },
4516
+ listPrice: { source: "unrecorded" }
4184
4517
  },
4185
4518
  {
4186
4519
  id: "openrouter/claude-sonnet-4-6",
@@ -4245,8 +4578,26 @@ var MODEL_CATALOG2 = [
4245
4578
  description: "Vertex AI direct, newest flash variant. Global region. Preview status."
4246
4579
  }
4247
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
+ }
4248
4599
  function tryGetModel2(id) {
4249
- return MODEL_CATALOG2.find((m) => m.id === id);
4600
+ return MODEL_CATALOG2.find((m) => m.id === id) ?? routeEntry2(id);
4250
4601
  }
4251
4602
  function supportedResolutions(model) {
4252
4603
  const declared = model?.capabilities?.resolutions;
@@ -5893,7 +6244,7 @@ function nodeUrl(res, nodeId) {
5893
6244
  }
5894
6245
  var studioListProjects = defineTool({
5895
6246
  name: "studio_list_projects",
5896
- 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.',
5897
6248
  inputSchema: {},
5898
6249
  annotations: { readOnlyHint: true },
5899
6250
  handler: async (_args, ctx) => {
@@ -5903,17 +6254,26 @@ var studioListProjects = defineTool({
5903
6254
  });
5904
6255
  var studioGenerateImage = defineTool({
5905
6256
  name: "studio_generate_image",
5906
- 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.',
5907
6258
  inputSchema: {
5908
- brandSlug: slug.describe('Brand to apply (e.g. "lola-stories"). Required by the API.'),
5909
- brandMode: z8.string().optional().describe('Optional brand mode (e.g. "illustration"); modes are defined per brand.'),
5910
- prompt: z8.string().min(1).describe("What to generate. The brand style prefix is prepended server-side."),
5911
- 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
+ ),
5912
6270
  model: z8.string().optional().describe('Image model id (e.g. "kie/nano-banana-2", "fal/nano-banana-2"). Omit for the Studio default.'),
5913
6271
  aspectRatio: z8.string().optional().describe('Aspect ratio such as "1:1", "16:9", "3:4".'),
5914
6272
  imageSize: z8.string().optional().describe('Provider size hint, e.g. "1K", "2K".'),
5915
6273
  seed: z8.number().int().optional(),
5916
- 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
+ ),
5917
6277
  wait: z8.boolean().default(true).describe("Poll until the job finishes (default true)."),
5918
6278
  waitSeconds
5919
6279
  },
@@ -5925,19 +6285,24 @@ var studioGenerateImage = defineTool({
5925
6285
  if (args.seed !== void 0) settings.seed = args.seed;
5926
6286
  if (args.candidateCount !== void 0) settings.candidateCount = args.candidateCount;
5927
6287
  const submitted = await ctx.client.generate({
5928
- brandSlug: args.brandSlug,
6288
+ ...args.brandSlug ? { brandSlug: args.brandSlug } : {},
5929
6289
  ...args.brandMode ? { brandMode: args.brandMode } : {},
5930
6290
  prompt: args.prompt,
5931
6291
  ...args.characterSlug ? { characterSlug: args.characterSlug } : {},
6292
+ ...args.projectId ? { projectId: args.projectId } : {},
5932
6293
  ...Object.keys(settings).length > 0 ? { settings } : {}
5933
6294
  });
5934
- 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 };
5935
6300
  const done = await waitForJob(ctx.client, submitted.jobId, pollOpts(ctx, args.waitSeconds));
5936
6301
  const out = projectJob(done);
5937
6302
  if (done.job.status !== "succeeded") {
5938
6303
  throw new Error(`Job ${submitted.jobId} ${done.job.status}: ${done.job.errorMessage ?? "no error message from the API"}`);
5939
6304
  }
5940
- return { ...out, kind: submitted.kind };
6305
+ return { ...out, kind: submitted.kind, ...plan };
5941
6306
  }
5942
6307
  });
5943
6308
  var GENERATE_3D_TASKS = /* @__PURE__ */ new Set(["text-to-3d", "image-to-3d"]);
@@ -6083,7 +6448,7 @@ var studioGetJob = defineTool({
6083
6448
  });
6084
6449
  var studioListCharacters = defineTool({
6085
6450
  name: "studio_list_characters",
6086
- 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.",
6087
6452
  inputSchema: {},
6088
6453
  annotations: { readOnlyHint: true },
6089
6454
  handler: async (_args, ctx) => {
@@ -6093,7 +6458,7 @@ var studioListCharacters = defineTool({
6093
6458
  });
6094
6459
  var studioGetCharacter = defineTool({
6095
6460
  name: "studio_get_character",
6096
- 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.",
6097
6462
  inputSchema: { slug },
6098
6463
  annotations: { readOnlyHint: true },
6099
6464
  handler: async (args, ctx) => {
@@ -6108,7 +6473,7 @@ function assertLikenessRightsConfirmed(value) {
6108
6473
  }
6109
6474
  var studioCreateCharacter = defineTool({
6110
6475
  name: "studio_create_character",
6111
- 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.",
6112
6477
  inputSchema: {
6113
6478
  name: z8.string().min(1).describe('Display name, e.g. "Bruno Hartmann".'),
6114
6479
  slug,
@@ -6200,7 +6565,7 @@ var studioMintCharacter = defineTool({
6200
6565
  });
6201
6566
  var studioCreateBrand = defineTool({
6202
6567
  name: "studio_create_brand",
6203
- 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.",
6204
6569
  inputSchema: {
6205
6570
  name: z8.string().min(1),
6206
6571
  slug,
@@ -6225,7 +6590,7 @@ var studioUploadBrandReference = defineTool({
6225
6590
  name: "studio_upload_brand_reference",
6226
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.`,
6227
6592
  inputSchema: {
6228
- 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."),
6229
6594
  source: z8.string().min(1).describe("Local file path or http(s) URL of an image."),
6230
6595
  category: z8.string().optional().describe('Library category, default "general".'),
6231
6596
  label: z8.string().optional().describe("Optional file name / label."),
@@ -6347,7 +6712,9 @@ var studioReplaceCharacterInVideo = defineTool({
6347
6712
  inputSchema: {
6348
6713
  projectId: z8.string().min(1).describe("Project id from studio_list_projects."),
6349
6714
  sceneFrameUrl: httpUrl.describe("Public or signed URL of ONE frame of the target shot (png/jpg)."),
6350
- 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
+ ),
6351
6718
  characterReferenceUrl: httpUrl.optional().describe("Explicit character reference image URL (use instead of characterSlug)."),
6352
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."),
6353
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."),
@@ -6420,7 +6787,7 @@ var studioReplaceCharacterInVideo = defineTool({
6420
6787
  });
6421
6788
  var studioGetSpend = defineTool({
6422
6789
  name: "studio_get_spend",
6423
- 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).",
6424
6791
  inputSchema: {
6425
6792
  tzOffsetMinutes: z8.number().int().min(-840).max(840).optional()
6426
6793
  },
@@ -6432,7 +6799,7 @@ var studioGetSpend = defineTool({
6432
6799
  });
6433
6800
  var studioGetUsage = defineTool({
6434
6801
  name: "studio_get_usage",
6435
- 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.",
6436
6803
  inputSchema: {},
6437
6804
  annotations: { readOnlyHint: true },
6438
6805
  handler: async (_args, ctx) => {
@@ -6552,7 +6919,7 @@ function registerTools(server, ctx) {
6552
6919
 
6553
6920
  // src/server.ts
6554
6921
  var SERVER_NAME = "lumitra-studio";
6555
- var SERVER_VERSION = "0.3.0";
6922
+ var SERVER_VERSION = "0.5.0";
6556
6923
  function createStudioMcpServer(opts = {}) {
6557
6924
  const config = opts.config ?? resolveConfig(opts.env ?? process.env);
6558
6925
  const client = new StudioClient(config, {
@@ -6564,7 +6931,7 @@ function createStudioMcpServer(opts = {}) {
6564
6931
  {
6565
6932
  instructions: [
6566
6933
  "Lumitra Studio: brand-consistent image generation, character minting, workflow runs and the still-swap video recipe over the hosted Studio API.",
6567
- "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.",
6568
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.",
6569
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.",
6570
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."
@@ -6605,7 +6972,7 @@ Usage:
6605
6972
  studio-mcp --http --host 0.0.0.0 --port 3939
6606
6973
 
6607
6974
  Environment:
6608
- LUMITRA_STUDIO_API_KEY bearer token (required; legacy LUMITRA_STUDIO_SERVICE_TOKEN accepted)
6975
+ LUMITRA_STUDIO_API_KEY bearer token (required)
6609
6976
  LUMITRA_STUDIO_BASE_URL default https://studio.lumitra.co
6610
6977
 
6611
6978
  Tools: ${TOOL_NAMES.join(", ")}