@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/index.js CHANGED
@@ -42,7 +42,11 @@ function apiMessageFromBody(body, fallback) {
42
42
  if (typeof o.error === "string" && o.error.length > 0) {
43
43
  const detail = typeof o.message === "string" ? o.message : "";
44
44
  const adds = detail.length > 0 && !o.error.includes(detail) && !detail.includes(o.error);
45
- const headline = adds ? `${o.error}: ${detail}` : o.error;
45
+ let headline = adds ? `${o.error}: ${detail}` : o.error;
46
+ const hint = typeof o.hint === "string" ? o.hint.trim() : "";
47
+ if (hint.length > 0 && !headline.includes(hint)) headline = `${headline}${/[.!?]$/.test(headline) ? "" : "."} ${hint}`;
48
+ const code2 = typeof o.code === "string" ? o.code.trim() : "";
49
+ if (code2.length > 0 && !headline.includes(code2)) headline = `${headline} (code: ${code2})`;
46
50
  const quota = quotaDetail(o);
47
51
  return quota ? `${headline} (${quota})` : headline;
48
52
  }
@@ -102,6 +106,14 @@ var StudioClient = class {
102
106
  return this.json("GET", "/api/v1/projects");
103
107
  }
104
108
  // ── Generation jobs ──
109
+ /**
110
+ * One image job. `brandSlug` is optional: without it the image is generated
111
+ * with no brand. A `brandSlug` or `characterSlug` the key's company cannot
112
+ * use is refused with 404 `input_not_found` before anything is spent (since
113
+ * 2026-10-11; it used to generate without the brand). `projectId` chooses the
114
+ * project; without it the job lands in the project named after the brand, or
115
+ * in `default`.
116
+ */
105
117
  generate(body) {
106
118
  return this.json("POST", "/api/generate", { json: body });
107
119
  }
@@ -377,6 +389,7 @@ function projectQuery(projectId) {
377
389
 
378
390
  // src/config.ts
379
391
  var DEFAULT_BASE_URL = "https://studio.lumitra.co";
392
+ var LEGACY_API_KEY_VAR = "LUMITRA_STUDIO_SERVICE_TOKEN";
380
393
  var ConfigError = class extends Error {
381
394
  constructor(message) {
382
395
  super(message);
@@ -409,10 +422,11 @@ function resolveConfig(env = process.env) {
409
422
  `LUMITRA_STUDIO_BASE_URL is not a valid http(s) URL (got ${JSON.stringify(rawBase)})`
410
423
  );
411
424
  }
412
- const apiKey = readEnvVar("LUMITRA_STUDIO_API_KEY", env) ?? readEnvVar("LUMITRA_STUDIO_SERVICE_TOKEN", env);
425
+ const apiKey = readEnvVar("LUMITRA_STUDIO_API_KEY", env);
413
426
  if (!apiKey) {
427
+ 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.` : "";
414
428
  throw new ConfigError(
415
- '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.'
429
+ `Missing API key: set LUMITRA_STUDIO_API_KEY to a Lumitra Studio bearer token. An unexpanded "\${...}" placeholder counts as unset.${legacyHint}`
416
430
  );
417
431
  }
418
432
  return { baseUrl, apiKey };
@@ -433,7 +447,124 @@ function studio3dCreditsToUsd(credits) {
433
447
  return Math.round(credits * STUDIO3D_USD_PER_CREDIT * 1e3) / 1e3;
434
448
  }
435
449
  var FAL_MESHY_RIG_USD = 0.2;
450
+ var veo = (tier, label) => {
451
+ const slug2 = tier ? `veo-3-1-${tier}` : "veo-3-1";
452
+ return {
453
+ kind: "video",
454
+ id: `route/${slug2}`,
455
+ label: `${label} (KIE, fal fallback)`,
456
+ primary: `kie/${slug2}`,
457
+ fallback: { start: `fal/${slug2}-image-to-video`, startAndEnd: `fal/${slug2}-first-last-frame` }
458
+ };
459
+ };
460
+ var image = (slug2, label, defaultTier) => ({
461
+ kind: "image",
462
+ id: `route/${slug2}`,
463
+ label: `${label} (KIE, fal fallback)`,
464
+ primary: `kie/${slug2}`,
465
+ fallback: `fal/${slug2}`,
466
+ defaultTier
467
+ });
468
+ var ROUTES = [
469
+ veo("lite", "Veo 3.1 Lite"),
470
+ veo("fast", "Veo 3.1 Fast"),
471
+ veo("", "Veo 3.1"),
472
+ {
473
+ kind: "video",
474
+ id: "route/grok-imagine",
475
+ label: "Grok Imagine (KIE, fal fallback)",
476
+ primary: "kie/grok-imagine-image-to-video",
477
+ fallback: { start: "fal/grok-imagine-image-to-video" }
478
+ },
479
+ {
480
+ kind: "video",
481
+ id: "route/grok-imagine-1-5",
482
+ label: "Grok Imagine 1.5 (KIE, fal fallback)",
483
+ primary: "kie/grok-imagine-1-5-image-to-video",
484
+ fallback: { start: "fal/grok-imagine-1-5-image-to-video" }
485
+ },
486
+ {
487
+ kind: "video",
488
+ id: "route/kling-2-5-turbo-pro",
489
+ label: "Kling 2.5 Turbo Pro (KIE, fal fallback)",
490
+ primary: "kie/kling-v2-5-turbo-pro-image-to-video",
491
+ fallback: { start: "fal/kling-v2-5-image-to-video" },
492
+ // fal needs an aspect ratio (16:9, 9:16 or 1:1) and KIE takes none, so a
493
+ // fallback would crop a start frame of another shape. The ratio comes from
494
+ // the start frame once that is built (decision 7 of the plan).
495
+ 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."
496
+ },
497
+ {
498
+ kind: "video",
499
+ id: "route/kling-2-6",
500
+ label: "Kling 2.6 (KIE, fal fallback)",
501
+ primary: "kie/kling-2-6-image-to-video",
502
+ fallback: { start: "fal/kling-2-6-image-to-video" }
503
+ },
504
+ {
505
+ kind: "video",
506
+ id: "route/kling-o3",
507
+ label: "Kling O3 (KIE, fal fallback)",
508
+ primary: "kie/kling-o3-image-to-video",
509
+ // fal sells Kling O3 as tiers and takes no resolution. Its schema (read
510
+ // 2026-10-10) declares length, audio and the frames but states no output
511
+ // size, so Standard for 720p and Pro for 1080p follows Kling's own tier
512
+ // naming and is still to be confirmed by measuring a rendered clip.
513
+ fallback: {
514
+ byResolution: {
515
+ "720p": "fal/kling-o3-standard-image-to-video",
516
+ "1080p": "fal/kling-o3-pro-image-to-video"
517
+ },
518
+ label: "fal renders Kling O3 at 720p (Standard) or 1080p (Pro)"
519
+ }
520
+ },
521
+ {
522
+ kind: "video",
523
+ id: "route/seedance-2-0",
524
+ label: "Seedance 2.0 (KIE, fal fallback)",
525
+ primary: "kie/seedance-2-0-image-to-video",
526
+ fallback: { start: "fal/seedance-2-0-image-to-video" }
527
+ },
528
+ {
529
+ kind: "video",
530
+ id: "route/seedance-2-5",
531
+ label: "Seedance 2.5 (KIE, fal fallback)",
532
+ primary: "kie/seedance-2-5-image-to-video",
533
+ fallback: { start: "fal/seedance-2-5-image-to-video" }
534
+ },
535
+ {
536
+ kind: "video",
537
+ id: "route/wan-3-0",
538
+ label: "Wan 3.0 (KIE, fal fallback)",
539
+ primary: "kie/wan-3-0-image-to-video",
540
+ fallback: { start: "fal/wan-3-0-image-to-video" }
541
+ },
542
+ image("nano-banana-2", "Nano Banana 2", "1K"),
543
+ image("nano-banana-2-edit", "Nano Banana 2 Edit", "1K"),
544
+ image("nano-banana-pro", "Nano Banana Pro", "1K"),
545
+ image("nano-banana-pro-edit", "Nano Banana Pro Edit", "1K"),
546
+ image("seedream-5-lite", "Seedream 5 Lite", "2K"),
547
+ image("seedream-5-lite-edit", "Seedream 5 Lite Edit", "2K"),
548
+ image("seedream-5-pro", "Seedream 5 Pro", "1K"),
549
+ image("seedream-5-pro-edit", "Seedream 5 Pro Edit", "1K")
550
+ ];
551
+ var MODEL_ROUTES = Object.fromEntries(ROUTES.map((r) => [r.id, r]));
552
+ function getRoute(modelId) {
553
+ return modelId ? MODEL_ROUTES[modelId] : void 0;
554
+ }
436
555
  var MODEL_CATALOG = [
556
+ // LIST PRICES (`listPrice`, pricing plan of 2026-10-10). Every KIE entry
557
+ // names where the official list price of its requests is found, because KIE
558
+ // is the reseller route that sells below list and the price floor needs the
559
+ // figure. The rule: the model owner's own published price where the catalog
560
+ // records one (Google's Veo price, billed unchanged by the OpenRouter
561
+ // entries; Vertex AI's Imagen 4 Fast price), else the fal entry for the same
562
+ // upstream model. `unrecorded` means neither exists in this catalog, and the
563
+ // floor then does not apply: Seedream V4, MiniMax H3 and Gemini Omni Flash
564
+ // have no fal entry, Kling O3 on KIE does not say whether it is fal's
565
+ // Standard or Pro tier, Suno has no per-song list price, and the Claude
566
+ // entries bill per token. Entries of every other provider carry no
567
+ // reference: their route bills the list price itself.
437
568
  // ── 3D AI Studio: 3D generation ────────────────────────────────────────
438
569
  // `credits` is what the model's DEFAULT request bills (the options its
439
570
  // builder sends when a node sets none), priced by providers/studio3d/
@@ -530,6 +661,7 @@ var MODEL_CATALOG = [
530
661
  label: "Nano Banana 2 (KIE)",
531
662
  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.",
532
663
  cost: { usd: 0.04, usdByResolution: { "1K": 0.04, "2K": 0.06, "4K": 0.09 } },
664
+ listPrice: { source: "fal", model: "fal/nano-banana-2" },
533
665
  capabilities: { resolutions: ["1K", "2K", "4K"] }
534
666
  },
535
667
  {
@@ -542,6 +674,7 @@ var MODEL_CATALOG = [
542
674
  label: "Imagen 4 Fast (KIE)",
543
675
  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.",
544
676
  cost: { usd: 0.02 },
677
+ listPrice: { source: "owner", usd: 0.02 },
545
678
  capabilities: { resolutions: [] }
546
679
  },
547
680
  {
@@ -551,11 +684,15 @@ var MODEL_CATALOG = [
551
684
  task: "text-to-image",
552
685
  label: "Seedream V4 (KIE)",
553
686
  description: "ByteDance Seedream V4 via KIE. Stylized output not available on Vertex. No IP indemnity.",
554
- cost: { usd: 0.0175 }
687
+ cost: { usd: 0.0175 },
688
+ listPrice: { source: "unrecorded" }
555
689
  },
556
690
  // ── KIE.ai: newer image models, the cheaper route (price feed 2026-09-23) ─
557
691
  // Billed from the credits KIE reports on each task. KIE offers no data
558
- // processing agreement, so real-person uploads belong on the fal entries.
692
+ // processing agreement. Decided 2026-09-23 (Q5 C of the routing plan,
693
+ // docs/plans/2026-09-26-kie-first-fal-fallback.md): the automatic routes try
694
+ // KIE first for everything, a recorded risk; pinning the fal entry of a model
695
+ // is how a job with a real person's photo is kept off KIE.
559
696
  {
560
697
  id: "kie/nano-banana-2-edit",
561
698
  provider: "kie",
@@ -564,6 +701,7 @@ var MODEL_CATALOG = [
564
701
  label: "Nano Banana 2 Edit (KIE)",
565
702
  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.",
566
703
  cost: { usd: 0.04, usdByResolution: { "1K": 0.04, "2K": 0.06, "4K": 0.09 } },
704
+ listPrice: { source: "fal", model: "fal/nano-banana-2-edit" },
567
705
  capabilities: { resolutions: ["1K", "2K", "4K"], needsImageInput: true, maxInputImages: 8 }
568
706
  },
569
707
  {
@@ -574,6 +712,7 @@ var MODEL_CATALOG = [
574
712
  label: "Nano Banana Pro (KIE)",
575
713
  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).",
576
714
  cost: { usd: 0.09, usdByResolution: { "1K": 0.09, "2K": 0.09, "4K": 0.12 } },
715
+ listPrice: { source: "fal", model: "fal/nano-banana-pro" },
577
716
  capabilities: { resolutions: ["1K", "2K", "4K"], maxInputImages: 0 }
578
717
  },
579
718
  {
@@ -584,6 +723,7 @@ var MODEL_CATALOG = [
584
723
  label: "Nano Banana Pro Edit (KIE)",
585
724
  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).",
586
725
  cost: { usd: 0.09, usdByResolution: { "1K": 0.09, "2K": 0.09, "4K": 0.12 } },
726
+ listPrice: { source: "fal", model: "fal/nano-banana-pro-edit" },
587
727
  capabilities: { resolutions: ["1K", "2K", "4K"], needsImageInput: true, maxInputImages: 8 }
588
728
  },
589
729
  {
@@ -594,6 +734,7 @@ var MODEL_CATALOG = [
594
734
  label: "Seedream 5 Lite (KIE)",
595
735
  description: "ByteDance Seedream 5 Lite via KIE, 2K or 4K. $0.0275 per image (fal: $0.035).",
596
736
  cost: { usd: 0.0275 },
737
+ listPrice: { source: "fal", model: "fal/seedream-5-lite" },
597
738
  capabilities: { resolutions: ["2K", "4K"], maxInputImages: 0 }
598
739
  },
599
740
  {
@@ -604,6 +745,7 @@ var MODEL_CATALOG = [
604
745
  label: "Seedream 5 Lite Edit (KIE)",
605
746
  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).",
606
747
  cost: { usd: 0.0275 },
748
+ listPrice: { source: "fal", model: "fal/seedream-5-lite-edit" },
607
749
  capabilities: { resolutions: ["2K", "4K"], needsImageInput: true, maxInputImages: 10 }
608
750
  },
609
751
  {
@@ -614,6 +756,7 @@ var MODEL_CATALOG = [
614
756
  label: "Seedream 5 Pro (KIE)",
615
757
  description: "ByteDance Seedream 5 Pro via KIE, 1K or 2K. $0.035 at 1K, $0.07 at 2K (fal: $0.0675 to $0.135).",
616
758
  cost: { usd: 0.035, usdByResolution: { "1K": 0.035, "2K": 0.07 } },
759
+ listPrice: { source: "fal", model: "fal/seedream-5-pro" },
617
760
  capabilities: { resolutions: ["1K", "2K"], maxInputImages: 0 }
618
761
  },
619
762
  {
@@ -624,6 +767,7 @@ var MODEL_CATALOG = [
624
767
  label: "Seedream 5 Pro Edit (KIE)",
625
768
  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.",
626
769
  cost: { usd: 0.035, usdByResolution: { "1K": 0.035, "2K": 0.07 } },
770
+ listPrice: { source: "fal", model: "fal/seedream-5-pro-edit" },
627
771
  capabilities: { resolutions: ["1K", "2K"], needsImageInput: true, maxInputImages: 10 }
628
772
  },
629
773
  // ── fal.ai: image gen ─────────────────────────────────────────────────
@@ -918,8 +1062,11 @@ var MODEL_CATALOG = [
918
1062
  // ── KIE.ai video: the cheaper route to the same upstream models ──────────
919
1063
  // Async via KIE's Veo and createTask APIs (providers/kie-video.ts). 20 to 85%
920
1064
  // below fal (KIE price feed, 2026-09-23). KIE offers no data processing
921
- // agreement, so real-person uploads belong on fal. Anchors are the model's
922
- // default clip at 720p (Kling: its fixed size). KIE docs vetted 2026-09-23.
1065
+ // agreement. Decided 2026-09-23 (Q5 C of the routing plan): the automatic
1066
+ // routes try KIE first for everything, a recorded risk; pinning the fal
1067
+ // entry of a model is how a job with a real person's photo is kept off KIE.
1068
+ // Anchors are the model's default clip at 720p (Kling: its fixed size). KIE
1069
+ // docs vetted 2026-09-23.
923
1070
  {
924
1071
  id: "kie/veo-3-1-lite",
925
1072
  provider: "kie",
@@ -928,6 +1075,7 @@ var MODEL_CATALOG = [
928
1075
  label: "Veo 3.1 Lite (KIE)",
929
1076
  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.",
930
1077
  cost: { usd: 0.15 },
1078
+ listPrice: { source: "owner", model: "openrouter/veo-3-1-lite" },
931
1079
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
932
1080
  },
933
1081
  {
@@ -938,6 +1086,7 @@ var MODEL_CATALOG = [
938
1086
  label: "Veo 3.1 Fast (KIE)",
939
1087
  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.",
940
1088
  cost: { usd: 0.3 },
1089
+ listPrice: { source: "owner", model: "openrouter/veo-3-1-fast" },
941
1090
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
942
1091
  },
943
1092
  {
@@ -948,6 +1097,7 @@ var MODEL_CATALOG = [
948
1097
  label: "Veo 3.1 (KIE)",
949
1098
  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).",
950
1099
  cost: { usd: 1.25 },
1100
+ listPrice: { source: "owner", model: "openrouter/veo-3-1" },
951
1101
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
952
1102
  },
953
1103
  // ── OpenRouter video: Google Veo 3.1 at Google's own prices ───────────────
@@ -994,6 +1144,7 @@ var MODEL_CATALOG = [
994
1144
  label: "Grok Imagine (KIE)",
995
1145
  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.",
996
1146
  cost: { usd: 0.135 },
1147
+ listPrice: { source: "fal", model: "fal/grok-imagine-image-to-video" },
997
1148
  capabilities: { needsImageInput: true, maxInputImages: 1 }
998
1149
  },
999
1150
  {
@@ -1004,6 +1155,7 @@ var MODEL_CATALOG = [
1004
1155
  label: "Grok Imagine 1.5 (KIE, preview)",
1005
1156
  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.",
1006
1157
  cost: { usd: 0.18 },
1158
+ listPrice: { source: "fal", model: "fal/grok-imagine-1-5-image-to-video" },
1007
1159
  capabilities: { needsImageInput: true, maxInputImages: 1 }
1008
1160
  },
1009
1161
  {
@@ -1014,6 +1166,7 @@ var MODEL_CATALOG = [
1014
1166
  label: "Kling 2.5 Turbo Pro (KIE)",
1015
1167
  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).",
1016
1168
  cost: { usd: 0.21 },
1169
+ listPrice: { source: "fal", model: "fal/kling-v2-5-image-to-video" },
1017
1170
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
1018
1171
  },
1019
1172
  {
@@ -1024,6 +1177,7 @@ var MODEL_CATALOG = [
1024
1177
  label: "Kling 2.6 (KIE)",
1025
1178
  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).",
1026
1179
  cost: { usd: 0.275 },
1180
+ listPrice: { source: "fal", model: "fal/kling-2-6-image-to-video" },
1027
1181
  capabilities: { needsImageInput: true, maxInputImages: 1 }
1028
1182
  },
1029
1183
  {
@@ -1035,6 +1189,7 @@ var MODEL_CATALOG = [
1035
1189
  refusesPhotorealPeople: true,
1036
1190
  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).",
1037
1191
  cost: { usd: 1.025 },
1192
+ listPrice: { source: "fal", model: "fal/seedance-2-0-image-to-video" },
1038
1193
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
1039
1194
  },
1040
1195
  {
@@ -1046,6 +1201,7 @@ var MODEL_CATALOG = [
1046
1201
  refusesPhotorealPeople: true,
1047
1202
  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.",
1048
1203
  cost: { usd: 1.575 },
1204
+ listPrice: { source: "fal", model: "fal/seedance-2-5-image-to-video" },
1049
1205
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
1050
1206
  },
1051
1207
  {
@@ -1056,6 +1212,7 @@ var MODEL_CATALOG = [
1056
1212
  label: "Wan 3.0 (KIE)",
1057
1213
  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).",
1058
1214
  cost: { usd: 0.4 },
1215
+ listPrice: { source: "fal", model: "fal/wan-3-0-image-to-video" },
1059
1216
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
1060
1217
  },
1061
1218
  {
@@ -1066,6 +1223,7 @@ var MODEL_CATALOG = [
1066
1223
  label: "MiniMax H3 (KIE)",
1067
1224
  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.",
1068
1225
  cost: { usd: 0.24 },
1226
+ listPrice: { source: "unrecorded" },
1069
1227
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
1070
1228
  },
1071
1229
  {
@@ -1076,6 +1234,7 @@ var MODEL_CATALOG = [
1076
1234
  label: "Gemini Omni 1.1 Flash (KIE)",
1077
1235
  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.",
1078
1236
  cost: { usd: 0.525 },
1237
+ listPrice: { source: "unrecorded" },
1079
1238
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
1080
1239
  },
1081
1240
  {
@@ -1086,6 +1245,7 @@ var MODEL_CATALOG = [
1086
1245
  label: "Kling O3 (KIE)",
1087
1246
  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.",
1088
1247
  cost: { usd: 0.35 },
1248
+ listPrice: { source: "unrecorded" },
1089
1249
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
1090
1250
  },
1091
1251
  {
@@ -1404,6 +1564,7 @@ var MODEL_CATALOG = [
1404
1564
  label: "Suno V5 (KIE)",
1405
1565
  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.",
1406
1566
  cost: { usd: 0.04 },
1567
+ listPrice: { source: "unrecorded" },
1407
1568
  isDefault: true
1408
1569
  },
1409
1570
  // ── audio: voice via ElevenLabs (B2) ───────────────────────────────────
@@ -1452,6 +1613,7 @@ var MODEL_CATALOG = [
1452
1613
  label: "Claude Sonnet 4.5 (KIE)",
1453
1614
  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.",
1454
1615
  cost: { usd: 0.01 },
1616
+ listPrice: { source: "unrecorded" },
1455
1617
  isDefault: true
1456
1618
  },
1457
1619
  {
@@ -1461,7 +1623,8 @@ var MODEL_CATALOG = [
1461
1623
  task: "text-generation",
1462
1624
  label: "Claude Opus 4.5 (KIE)",
1463
1625
  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.",
1464
- cost: { usd: 0.05 }
1626
+ cost: { usd: 0.05 },
1627
+ listPrice: { source: "unrecorded" }
1465
1628
  },
1466
1629
  {
1467
1630
  id: "openrouter/claude-sonnet-4-6",
@@ -1526,8 +1689,26 @@ var MODEL_CATALOG = [
1526
1689
  description: "Vertex AI direct, newest flash variant. Global region. Preview status."
1527
1690
  }
1528
1691
  ];
1692
+ var routeEntries = /* @__PURE__ */ new Map();
1693
+ function routeEntry(id) {
1694
+ const cached = routeEntries.get(id);
1695
+ if (cached) return cached;
1696
+ const route = getRoute(id);
1697
+ const primary = route ? MODEL_CATALOG.find((m) => m.id === route.primary) : void 0;
1698
+ if (!route || !primary) return void 0;
1699
+ const entry = {
1700
+ ...primary,
1701
+ // A route is never the default of its task (that is slice 3's switch).
1702
+ isDefault: void 0,
1703
+ id: route.id,
1704
+ label: route.label,
1705
+ description: `Runs on KIE first and, when KIE fails on its side, again on fal with the same settings. ${primary.description ?? ""}`.trim()
1706
+ };
1707
+ routeEntries.set(id, entry);
1708
+ return entry;
1709
+ }
1529
1710
  function tryGetModel(id) {
1530
- return MODEL_CATALOG.find((m) => m.id === id);
1711
+ return MODEL_CATALOG.find((m) => m.id === id) ?? routeEntry(id);
1531
1712
  }
1532
1713
  var ROWS = [
1533
1714
  [0, "Idle", "DailyActions", "Idle"],
@@ -2455,7 +2636,10 @@ var VIDEO_UNSUPPORTED_PARAMS = {
2455
2636
  ...Object.fromEntries(KLING_O3_I2V_ENDPOINTS.map((e) => [e, ["resolution", "aspectRatio", "seed", "negativePrompt"]])),
2456
2637
  ...Object.fromEntries(LTX_25_I2V_ENDPOINTS.map((e) => [e, ["seed", "negativePrompt"]]))
2457
2638
  };
2458
- var perSecond = (rates) => (s, r) => (rates[r] ?? 0) * s;
2639
+ var perSecond = (rates) => (s, r) => {
2640
+ const rate = rates[r];
2641
+ return rate === void 0 ? void 0 : rate * s;
2642
+ };
2459
2643
  function veoSpec(model, label, perClip) {
2460
2644
  return {
2461
2645
  family: "veo",
@@ -2469,7 +2653,7 @@ function veoSpec(model, label, perClip) {
2469
2653
  audioSwitch: false,
2470
2654
  defaultAudio: true,
2471
2655
  // KIE prices Veo per clip whatever its length, and always renders audio.
2472
- price: (_s, r) => perClip[r] ?? 0,
2656
+ price: (_s, r) => perClip[r],
2473
2657
  build: (i) => ({
2474
2658
  prompt: i.prompt,
2475
2659
  model,
@@ -2676,7 +2860,7 @@ var KIE_VIDEO_SPECS = {
2676
2860
  audioSwitch: false,
2677
2861
  defaultAudio: false,
2678
2862
  // Priced per clip by length; 4K carries a surcharge.
2679
- 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,
2863
+ 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],
2680
2864
  // The first-and-last-frame mode: KIE refuses it combined with registered
2681
2865
  // characters, reference images, audio or video, so none are sent.
2682
2866
  build: (i) => ({
@@ -2702,7 +2886,7 @@ var KIE_VIDEO_SPECS = {
2702
2886
  audioSwitch: true,
2703
2887
  defaultAudio: false,
2704
2888
  // With-audio rates from KIE's price feed, the higher of its two listings.
2705
- 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,
2889
+ 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),
2706
2890
  build: (i) => ({
2707
2891
  prompt: i.prompt,
2708
2892
  image_urls: i.end ? [i.start, i.end] : [i.start],
@@ -3144,7 +3328,124 @@ function imageResolutionTier(value) {
3144
3328
  return null;
3145
3329
  }
3146
3330
  }
3331
+ var veo2 = (tier, label) => {
3332
+ const slug2 = tier ? `veo-3-1-${tier}` : "veo-3-1";
3333
+ return {
3334
+ kind: "video",
3335
+ id: `route/${slug2}`,
3336
+ label: `${label} (KIE, fal fallback)`,
3337
+ primary: `kie/${slug2}`,
3338
+ fallback: { start: `fal/${slug2}-image-to-video`, startAndEnd: `fal/${slug2}-first-last-frame` }
3339
+ };
3340
+ };
3341
+ var image2 = (slug2, label, defaultTier) => ({
3342
+ kind: "image",
3343
+ id: `route/${slug2}`,
3344
+ label: `${label} (KIE, fal fallback)`,
3345
+ primary: `kie/${slug2}`,
3346
+ fallback: `fal/${slug2}`,
3347
+ defaultTier
3348
+ });
3349
+ var ROUTES2 = [
3350
+ veo2("lite", "Veo 3.1 Lite"),
3351
+ veo2("fast", "Veo 3.1 Fast"),
3352
+ veo2("", "Veo 3.1"),
3353
+ {
3354
+ kind: "video",
3355
+ id: "route/grok-imagine",
3356
+ label: "Grok Imagine (KIE, fal fallback)",
3357
+ primary: "kie/grok-imagine-image-to-video",
3358
+ fallback: { start: "fal/grok-imagine-image-to-video" }
3359
+ },
3360
+ {
3361
+ kind: "video",
3362
+ id: "route/grok-imagine-1-5",
3363
+ label: "Grok Imagine 1.5 (KIE, fal fallback)",
3364
+ primary: "kie/grok-imagine-1-5-image-to-video",
3365
+ fallback: { start: "fal/grok-imagine-1-5-image-to-video" }
3366
+ },
3367
+ {
3368
+ kind: "video",
3369
+ id: "route/kling-2-5-turbo-pro",
3370
+ label: "Kling 2.5 Turbo Pro (KIE, fal fallback)",
3371
+ primary: "kie/kling-v2-5-turbo-pro-image-to-video",
3372
+ fallback: { start: "fal/kling-v2-5-image-to-video" },
3373
+ // fal needs an aspect ratio (16:9, 9:16 or 1:1) and KIE takes none, so a
3374
+ // fallback would crop a start frame of another shape. The ratio comes from
3375
+ // the start frame once that is built (decision 7 of the plan).
3376
+ 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."
3377
+ },
3378
+ {
3379
+ kind: "video",
3380
+ id: "route/kling-2-6",
3381
+ label: "Kling 2.6 (KIE, fal fallback)",
3382
+ primary: "kie/kling-2-6-image-to-video",
3383
+ fallback: { start: "fal/kling-2-6-image-to-video" }
3384
+ },
3385
+ {
3386
+ kind: "video",
3387
+ id: "route/kling-o3",
3388
+ label: "Kling O3 (KIE, fal fallback)",
3389
+ primary: "kie/kling-o3-image-to-video",
3390
+ // fal sells Kling O3 as tiers and takes no resolution. Its schema (read
3391
+ // 2026-10-10) declares length, audio and the frames but states no output
3392
+ // size, so Standard for 720p and Pro for 1080p follows Kling's own tier
3393
+ // naming and is still to be confirmed by measuring a rendered clip.
3394
+ fallback: {
3395
+ byResolution: {
3396
+ "720p": "fal/kling-o3-standard-image-to-video",
3397
+ "1080p": "fal/kling-o3-pro-image-to-video"
3398
+ },
3399
+ label: "fal renders Kling O3 at 720p (Standard) or 1080p (Pro)"
3400
+ }
3401
+ },
3402
+ {
3403
+ kind: "video",
3404
+ id: "route/seedance-2-0",
3405
+ label: "Seedance 2.0 (KIE, fal fallback)",
3406
+ primary: "kie/seedance-2-0-image-to-video",
3407
+ fallback: { start: "fal/seedance-2-0-image-to-video" }
3408
+ },
3409
+ {
3410
+ kind: "video",
3411
+ id: "route/seedance-2-5",
3412
+ label: "Seedance 2.5 (KIE, fal fallback)",
3413
+ primary: "kie/seedance-2-5-image-to-video",
3414
+ fallback: { start: "fal/seedance-2-5-image-to-video" }
3415
+ },
3416
+ {
3417
+ kind: "video",
3418
+ id: "route/wan-3-0",
3419
+ label: "Wan 3.0 (KIE, fal fallback)",
3420
+ primary: "kie/wan-3-0-image-to-video",
3421
+ fallback: { start: "fal/wan-3-0-image-to-video" }
3422
+ },
3423
+ image2("nano-banana-2", "Nano Banana 2", "1K"),
3424
+ image2("nano-banana-2-edit", "Nano Banana 2 Edit", "1K"),
3425
+ image2("nano-banana-pro", "Nano Banana Pro", "1K"),
3426
+ image2("nano-banana-pro-edit", "Nano Banana Pro Edit", "1K"),
3427
+ image2("seedream-5-lite", "Seedream 5 Lite", "2K"),
3428
+ image2("seedream-5-lite-edit", "Seedream 5 Lite Edit", "2K"),
3429
+ image2("seedream-5-pro", "Seedream 5 Pro", "1K"),
3430
+ image2("seedream-5-pro-edit", "Seedream 5 Pro Edit", "1K")
3431
+ ];
3432
+ var MODEL_ROUTES2 = Object.fromEntries(ROUTES2.map((r) => [r.id, r]));
3433
+ function getRoute2(modelId) {
3434
+ return modelId ? MODEL_ROUTES2[modelId] : void 0;
3435
+ }
3147
3436
  var MODEL_CATALOG2 = [
3437
+ // LIST PRICES (`listPrice`, pricing plan of 2026-10-10). Every KIE entry
3438
+ // names where the official list price of its requests is found, because KIE
3439
+ // is the reseller route that sells below list and the price floor needs the
3440
+ // figure. The rule: the model owner's own published price where the catalog
3441
+ // records one (Google's Veo price, billed unchanged by the OpenRouter
3442
+ // entries; Vertex AI's Imagen 4 Fast price), else the fal entry for the same
3443
+ // upstream model. `unrecorded` means neither exists in this catalog, and the
3444
+ // floor then does not apply: Seedream V4, MiniMax H3 and Gemini Omni Flash
3445
+ // have no fal entry, Kling O3 on KIE does not say whether it is fal's
3446
+ // Standard or Pro tier, Suno has no per-song list price, and the Claude
3447
+ // entries bill per token. Entries of every other provider carry no
3448
+ // reference: their route bills the list price itself.
3148
3449
  // ── 3D AI Studio: 3D generation ────────────────────────────────────────
3149
3450
  // `credits` is what the model's DEFAULT request bills (the options its
3150
3451
  // builder sends when a node sets none), priced by providers/studio3d/
@@ -3241,6 +3542,7 @@ var MODEL_CATALOG2 = [
3241
3542
  label: "Nano Banana 2 (KIE)",
3242
3543
  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.",
3243
3544
  cost: { usd: 0.04, usdByResolution: { "1K": 0.04, "2K": 0.06, "4K": 0.09 } },
3545
+ listPrice: { source: "fal", model: "fal/nano-banana-2" },
3244
3546
  capabilities: { resolutions: ["1K", "2K", "4K"] }
3245
3547
  },
3246
3548
  {
@@ -3253,6 +3555,7 @@ var MODEL_CATALOG2 = [
3253
3555
  label: "Imagen 4 Fast (KIE)",
3254
3556
  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.",
3255
3557
  cost: { usd: 0.02 },
3558
+ listPrice: { source: "owner", usd: 0.02 },
3256
3559
  capabilities: { resolutions: [] }
3257
3560
  },
3258
3561
  {
@@ -3262,11 +3565,15 @@ var MODEL_CATALOG2 = [
3262
3565
  task: "text-to-image",
3263
3566
  label: "Seedream V4 (KIE)",
3264
3567
  description: "ByteDance Seedream V4 via KIE. Stylized output not available on Vertex. No IP indemnity.",
3265
- cost: { usd: 0.0175 }
3568
+ cost: { usd: 0.0175 },
3569
+ listPrice: { source: "unrecorded" }
3266
3570
  },
3267
3571
  // ── KIE.ai: newer image models, the cheaper route (price feed 2026-09-23) ─
3268
3572
  // Billed from the credits KIE reports on each task. KIE offers no data
3269
- // processing agreement, so real-person uploads belong on the fal entries.
3573
+ // processing agreement. Decided 2026-09-23 (Q5 C of the routing plan,
3574
+ // docs/plans/2026-09-26-kie-first-fal-fallback.md): the automatic routes try
3575
+ // KIE first for everything, a recorded risk; pinning the fal entry of a model
3576
+ // is how a job with a real person's photo is kept off KIE.
3270
3577
  {
3271
3578
  id: "kie/nano-banana-2-edit",
3272
3579
  provider: "kie",
@@ -3275,6 +3582,7 @@ var MODEL_CATALOG2 = [
3275
3582
  label: "Nano Banana 2 Edit (KIE)",
3276
3583
  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.",
3277
3584
  cost: { usd: 0.04, usdByResolution: { "1K": 0.04, "2K": 0.06, "4K": 0.09 } },
3585
+ listPrice: { source: "fal", model: "fal/nano-banana-2-edit" },
3278
3586
  capabilities: { resolutions: ["1K", "2K", "4K"], needsImageInput: true, maxInputImages: 8 }
3279
3587
  },
3280
3588
  {
@@ -3285,6 +3593,7 @@ var MODEL_CATALOG2 = [
3285
3593
  label: "Nano Banana Pro (KIE)",
3286
3594
  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).",
3287
3595
  cost: { usd: 0.09, usdByResolution: { "1K": 0.09, "2K": 0.09, "4K": 0.12 } },
3596
+ listPrice: { source: "fal", model: "fal/nano-banana-pro" },
3288
3597
  capabilities: { resolutions: ["1K", "2K", "4K"], maxInputImages: 0 }
3289
3598
  },
3290
3599
  {
@@ -3295,6 +3604,7 @@ var MODEL_CATALOG2 = [
3295
3604
  label: "Nano Banana Pro Edit (KIE)",
3296
3605
  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).",
3297
3606
  cost: { usd: 0.09, usdByResolution: { "1K": 0.09, "2K": 0.09, "4K": 0.12 } },
3607
+ listPrice: { source: "fal", model: "fal/nano-banana-pro-edit" },
3298
3608
  capabilities: { resolutions: ["1K", "2K", "4K"], needsImageInput: true, maxInputImages: 8 }
3299
3609
  },
3300
3610
  {
@@ -3305,6 +3615,7 @@ var MODEL_CATALOG2 = [
3305
3615
  label: "Seedream 5 Lite (KIE)",
3306
3616
  description: "ByteDance Seedream 5 Lite via KIE, 2K or 4K. $0.0275 per image (fal: $0.035).",
3307
3617
  cost: { usd: 0.0275 },
3618
+ listPrice: { source: "fal", model: "fal/seedream-5-lite" },
3308
3619
  capabilities: { resolutions: ["2K", "4K"], maxInputImages: 0 }
3309
3620
  },
3310
3621
  {
@@ -3315,6 +3626,7 @@ var MODEL_CATALOG2 = [
3315
3626
  label: "Seedream 5 Lite Edit (KIE)",
3316
3627
  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).",
3317
3628
  cost: { usd: 0.0275 },
3629
+ listPrice: { source: "fal", model: "fal/seedream-5-lite-edit" },
3318
3630
  capabilities: { resolutions: ["2K", "4K"], needsImageInput: true, maxInputImages: 10 }
3319
3631
  },
3320
3632
  {
@@ -3325,6 +3637,7 @@ var MODEL_CATALOG2 = [
3325
3637
  label: "Seedream 5 Pro (KIE)",
3326
3638
  description: "ByteDance Seedream 5 Pro via KIE, 1K or 2K. $0.035 at 1K, $0.07 at 2K (fal: $0.0675 to $0.135).",
3327
3639
  cost: { usd: 0.035, usdByResolution: { "1K": 0.035, "2K": 0.07 } },
3640
+ listPrice: { source: "fal", model: "fal/seedream-5-pro" },
3328
3641
  capabilities: { resolutions: ["1K", "2K"], maxInputImages: 0 }
3329
3642
  },
3330
3643
  {
@@ -3335,6 +3648,7 @@ var MODEL_CATALOG2 = [
3335
3648
  label: "Seedream 5 Pro Edit (KIE)",
3336
3649
  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.",
3337
3650
  cost: { usd: 0.035, usdByResolution: { "1K": 0.035, "2K": 0.07 } },
3651
+ listPrice: { source: "fal", model: "fal/seedream-5-pro-edit" },
3338
3652
  capabilities: { resolutions: ["1K", "2K"], needsImageInput: true, maxInputImages: 10 }
3339
3653
  },
3340
3654
  // ── fal.ai: image gen ─────────────────────────────────────────────────
@@ -3629,8 +3943,11 @@ var MODEL_CATALOG2 = [
3629
3943
  // ── KIE.ai video: the cheaper route to the same upstream models ──────────
3630
3944
  // Async via KIE's Veo and createTask APIs (providers/kie-video.ts). 20 to 85%
3631
3945
  // below fal (KIE price feed, 2026-09-23). KIE offers no data processing
3632
- // agreement, so real-person uploads belong on fal. Anchors are the model's
3633
- // default clip at 720p (Kling: its fixed size). KIE docs vetted 2026-09-23.
3946
+ // agreement. Decided 2026-09-23 (Q5 C of the routing plan): the automatic
3947
+ // routes try KIE first for everything, a recorded risk; pinning the fal
3948
+ // entry of a model is how a job with a real person's photo is kept off KIE.
3949
+ // Anchors are the model's default clip at 720p (Kling: its fixed size). KIE
3950
+ // docs vetted 2026-09-23.
3634
3951
  {
3635
3952
  id: "kie/veo-3-1-lite",
3636
3953
  provider: "kie",
@@ -3639,6 +3956,7 @@ var MODEL_CATALOG2 = [
3639
3956
  label: "Veo 3.1 Lite (KIE)",
3640
3957
  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.",
3641
3958
  cost: { usd: 0.15 },
3959
+ listPrice: { source: "owner", model: "openrouter/veo-3-1-lite" },
3642
3960
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
3643
3961
  },
3644
3962
  {
@@ -3649,6 +3967,7 @@ var MODEL_CATALOG2 = [
3649
3967
  label: "Veo 3.1 Fast (KIE)",
3650
3968
  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.",
3651
3969
  cost: { usd: 0.3 },
3970
+ listPrice: { source: "owner", model: "openrouter/veo-3-1-fast" },
3652
3971
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
3653
3972
  },
3654
3973
  {
@@ -3659,6 +3978,7 @@ var MODEL_CATALOG2 = [
3659
3978
  label: "Veo 3.1 (KIE)",
3660
3979
  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).",
3661
3980
  cost: { usd: 1.25 },
3981
+ listPrice: { source: "owner", model: "openrouter/veo-3-1" },
3662
3982
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
3663
3983
  },
3664
3984
  // ── OpenRouter video: Google Veo 3.1 at Google's own prices ───────────────
@@ -3705,6 +4025,7 @@ var MODEL_CATALOG2 = [
3705
4025
  label: "Grok Imagine (KIE)",
3706
4026
  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.",
3707
4027
  cost: { usd: 0.135 },
4028
+ listPrice: { source: "fal", model: "fal/grok-imagine-image-to-video" },
3708
4029
  capabilities: { needsImageInput: true, maxInputImages: 1 }
3709
4030
  },
3710
4031
  {
@@ -3715,6 +4036,7 @@ var MODEL_CATALOG2 = [
3715
4036
  label: "Grok Imagine 1.5 (KIE, preview)",
3716
4037
  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.",
3717
4038
  cost: { usd: 0.18 },
4039
+ listPrice: { source: "fal", model: "fal/grok-imagine-1-5-image-to-video" },
3718
4040
  capabilities: { needsImageInput: true, maxInputImages: 1 }
3719
4041
  },
3720
4042
  {
@@ -3725,6 +4047,7 @@ var MODEL_CATALOG2 = [
3725
4047
  label: "Kling 2.5 Turbo Pro (KIE)",
3726
4048
  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).",
3727
4049
  cost: { usd: 0.21 },
4050
+ listPrice: { source: "fal", model: "fal/kling-v2-5-image-to-video" },
3728
4051
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
3729
4052
  },
3730
4053
  {
@@ -3735,6 +4058,7 @@ var MODEL_CATALOG2 = [
3735
4058
  label: "Kling 2.6 (KIE)",
3736
4059
  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).",
3737
4060
  cost: { usd: 0.275 },
4061
+ listPrice: { source: "fal", model: "fal/kling-2-6-image-to-video" },
3738
4062
  capabilities: { needsImageInput: true, maxInputImages: 1 }
3739
4063
  },
3740
4064
  {
@@ -3746,6 +4070,7 @@ var MODEL_CATALOG2 = [
3746
4070
  refusesPhotorealPeople: true,
3747
4071
  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).",
3748
4072
  cost: { usd: 1.025 },
4073
+ listPrice: { source: "fal", model: "fal/seedance-2-0-image-to-video" },
3749
4074
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
3750
4075
  },
3751
4076
  {
@@ -3757,6 +4082,7 @@ var MODEL_CATALOG2 = [
3757
4082
  refusesPhotorealPeople: true,
3758
4083
  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.",
3759
4084
  cost: { usd: 1.575 },
4085
+ listPrice: { source: "fal", model: "fal/seedance-2-5-image-to-video" },
3760
4086
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
3761
4087
  },
3762
4088
  {
@@ -3767,6 +4093,7 @@ var MODEL_CATALOG2 = [
3767
4093
  label: "Wan 3.0 (KIE)",
3768
4094
  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).",
3769
4095
  cost: { usd: 0.4 },
4096
+ listPrice: { source: "fal", model: "fal/wan-3-0-image-to-video" },
3770
4097
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
3771
4098
  },
3772
4099
  {
@@ -3777,6 +4104,7 @@ var MODEL_CATALOG2 = [
3777
4104
  label: "MiniMax H3 (KIE)",
3778
4105
  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.",
3779
4106
  cost: { usd: 0.24 },
4107
+ listPrice: { source: "unrecorded" },
3780
4108
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
3781
4109
  },
3782
4110
  {
@@ -3787,6 +4115,7 @@ var MODEL_CATALOG2 = [
3787
4115
  label: "Gemini Omni 1.1 Flash (KIE)",
3788
4116
  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.",
3789
4117
  cost: { usd: 0.525 },
4118
+ listPrice: { source: "unrecorded" },
3790
4119
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
3791
4120
  },
3792
4121
  {
@@ -3797,6 +4126,7 @@ var MODEL_CATALOG2 = [
3797
4126
  label: "Kling O3 (KIE)",
3798
4127
  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.",
3799
4128
  cost: { usd: 0.35 },
4129
+ listPrice: { source: "unrecorded" },
3800
4130
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
3801
4131
  },
3802
4132
  {
@@ -4115,6 +4445,7 @@ var MODEL_CATALOG2 = [
4115
4445
  label: "Suno V5 (KIE)",
4116
4446
  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.",
4117
4447
  cost: { usd: 0.04 },
4448
+ listPrice: { source: "unrecorded" },
4118
4449
  isDefault: true
4119
4450
  },
4120
4451
  // ── audio: voice via ElevenLabs (B2) ───────────────────────────────────
@@ -4163,6 +4494,7 @@ var MODEL_CATALOG2 = [
4163
4494
  label: "Claude Sonnet 4.5 (KIE)",
4164
4495
  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.",
4165
4496
  cost: { usd: 0.01 },
4497
+ listPrice: { source: "unrecorded" },
4166
4498
  isDefault: true
4167
4499
  },
4168
4500
  {
@@ -4172,7 +4504,8 @@ var MODEL_CATALOG2 = [
4172
4504
  task: "text-generation",
4173
4505
  label: "Claude Opus 4.5 (KIE)",
4174
4506
  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.",
4175
- cost: { usd: 0.05 }
4507
+ cost: { usd: 0.05 },
4508
+ listPrice: { source: "unrecorded" }
4176
4509
  },
4177
4510
  {
4178
4511
  id: "openrouter/claude-sonnet-4-6",
@@ -4237,8 +4570,26 @@ var MODEL_CATALOG2 = [
4237
4570
  description: "Vertex AI direct, newest flash variant. Global region. Preview status."
4238
4571
  }
4239
4572
  ];
4573
+ var routeEntries2 = /* @__PURE__ */ new Map();
4574
+ function routeEntry2(id) {
4575
+ const cached = routeEntries2.get(id);
4576
+ if (cached) return cached;
4577
+ const route = getRoute2(id);
4578
+ const primary = route ? MODEL_CATALOG2.find((m) => m.id === route.primary) : void 0;
4579
+ if (!route || !primary) return void 0;
4580
+ const entry = {
4581
+ ...primary,
4582
+ // A route is never the default of its task (that is slice 3's switch).
4583
+ isDefault: void 0,
4584
+ id: route.id,
4585
+ label: route.label,
4586
+ description: `Runs on KIE first and, when KIE fails on its side, again on fal with the same settings. ${primary.description ?? ""}`.trim()
4587
+ };
4588
+ routeEntries2.set(id, entry);
4589
+ return entry;
4590
+ }
4240
4591
  function tryGetModel2(id) {
4241
- return MODEL_CATALOG2.find((m) => m.id === id);
4592
+ return MODEL_CATALOG2.find((m) => m.id === id) ?? routeEntry2(id);
4242
4593
  }
4243
4594
  function supportedResolutions(model) {
4244
4595
  const declared = model?.capabilities?.resolutions;
@@ -5888,7 +6239,7 @@ function nodeUrl(res, nodeId) {
5888
6239
  }
5889
6240
  var studioListProjects = defineTool({
5890
6241
  name: "studio_list_projects",
5891
- 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").',
6242
+ 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.',
5892
6243
  inputSchema: {},
5893
6244
  annotations: { readOnlyHint: true },
5894
6245
  handler: async (_args, ctx) => {
@@ -5898,17 +6249,26 @@ var studioListProjects = defineTool({
5898
6249
  });
5899
6250
  var studioGenerateImage = defineTool({
5900
6251
  name: "studio_generate_image",
5901
- 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.",
6252
+ 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.',
5902
6253
  inputSchema: {
5903
- brandSlug: slug.describe('Brand to apply (e.g. "lola-stories"). Required by the API.'),
5904
- brandMode: z8.string().optional().describe('Optional brand mode (e.g. "illustration"); modes are defined per brand.'),
5905
- prompt: z8.string().min(1).describe("What to generate. The brand style prefix is prepended server-side."),
5906
- characterSlug: slug.optional().describe("Optional minted character whose identity references are injected."),
6254
+ brandSlug: slug.optional().describe(
6255
+ "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."
6256
+ ),
6257
+ brandMode: z8.string().optional().describe('Optional brand mode (e.g. "illustration"); modes are defined per brand. Only meaningful with brandSlug.'),
6258
+ prompt: z8.string().min(1).describe("What to generate. With brandSlug, the brand style prefix is prepended server-side."),
6259
+ characterSlug: slug.optional().describe(
6260
+ "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."
6261
+ ),
6262
+ projectId: z8.string().min(1).optional().describe(
6263
+ '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.'
6264
+ ),
5907
6265
  model: z8.string().optional().describe('Image model id (e.g. "kie/nano-banana-2", "fal/nano-banana-2"). Omit for the Studio default.'),
5908
6266
  aspectRatio: z8.string().optional().describe('Aspect ratio such as "1:1", "16:9", "3:4".'),
5909
6267
  imageSize: z8.string().optional().describe('Provider size hint, e.g. "1K", "2K".'),
5910
6268
  seed: z8.number().int().optional(),
5911
- candidateCount: z8.number().int().min(1).max(4).optional().describe("Number of candidates (1 to 4)."),
6269
+ candidateCount: z8.number().int().min(1).max(4).optional().describe(
6270
+ "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."
6271
+ ),
5912
6272
  wait: z8.boolean().default(true).describe("Poll until the job finishes (default true)."),
5913
6273
  waitSeconds
5914
6274
  },
@@ -5920,19 +6280,24 @@ var studioGenerateImage = defineTool({
5920
6280
  if (args.seed !== void 0) settings.seed = args.seed;
5921
6281
  if (args.candidateCount !== void 0) settings.candidateCount = args.candidateCount;
5922
6282
  const submitted = await ctx.client.generate({
5923
- brandSlug: args.brandSlug,
6283
+ ...args.brandSlug ? { brandSlug: args.brandSlug } : {},
5924
6284
  ...args.brandMode ? { brandMode: args.brandMode } : {},
5925
6285
  prompt: args.prompt,
5926
6286
  ...args.characterSlug ? { characterSlug: args.characterSlug } : {},
6287
+ ...args.projectId ? { projectId: args.projectId } : {},
5927
6288
  ...Object.keys(settings).length > 0 ? { settings } : {}
5928
6289
  });
5929
- if (!args.wait) return { jobId: submitted.jobId, kind: submitted.kind, status: "queued" };
6290
+ const plan = {
6291
+ ...submitted.candidates ? { candidates: submitted.candidates } : {},
6292
+ ...typeof submitted.notice === "string" && submitted.notice.length > 0 ? { notice: submitted.notice } : {}
6293
+ };
6294
+ if (!args.wait) return { jobId: submitted.jobId, kind: submitted.kind, status: "queued", ...plan };
5930
6295
  const done = await waitForJob(ctx.client, submitted.jobId, pollOpts(ctx, args.waitSeconds));
5931
6296
  const out = projectJob(done);
5932
6297
  if (done.job.status !== "succeeded") {
5933
6298
  throw new Error(`Job ${submitted.jobId} ${done.job.status}: ${done.job.errorMessage ?? "no error message from the API"}`);
5934
6299
  }
5935
- return { ...out, kind: submitted.kind };
6300
+ return { ...out, kind: submitted.kind, ...plan };
5936
6301
  }
5937
6302
  });
5938
6303
  var GENERATE_3D_TASKS = /* @__PURE__ */ new Set(["text-to-3d", "image-to-3d"]);
@@ -6078,7 +6443,7 @@ var studioGetJob = defineTool({
6078
6443
  });
6079
6444
  var studioListCharacters = defineTool({
6080
6445
  name: "studio_list_characters",
6081
- 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.",
6446
+ 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.",
6082
6447
  inputSchema: {},
6083
6448
  annotations: { readOnlyHint: true },
6084
6449
  handler: async (_args, ctx) => {
@@ -6088,7 +6453,7 @@ var studioListCharacters = defineTool({
6088
6453
  });
6089
6454
  var studioGetCharacter = defineTool({
6090
6455
  name: "studio_get_character",
6091
- 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.",
6456
+ 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.",
6092
6457
  inputSchema: { slug },
6093
6458
  annotations: { readOnlyHint: true },
6094
6459
  handler: async (args, ctx) => {
@@ -6103,7 +6468,7 @@ function assertLikenessRightsConfirmed(value) {
6103
6468
  }
6104
6469
  var studioCreateCharacter = defineTool({
6105
6470
  name: "studio_create_character",
6106
- 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.",
6471
+ 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.",
6107
6472
  inputSchema: {
6108
6473
  name: z8.string().min(1).describe('Display name, e.g. "Bruno Hartmann".'),
6109
6474
  slug,
@@ -6195,7 +6560,7 @@ var studioMintCharacter = defineTool({
6195
6560
  });
6196
6561
  var studioCreateBrand = defineTool({
6197
6562
  name: "studio_create_brand",
6198
- 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.",
6563
+ 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.",
6199
6564
  inputSchema: {
6200
6565
  name: z8.string().min(1),
6201
6566
  slug,
@@ -6220,7 +6585,7 @@ var studioUploadBrandReference = defineTool({
6220
6585
  name: "studio_upload_brand_reference",
6221
6586
  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.`,
6222
6587
  inputSchema: {
6223
- brandSlug: slug,
6588
+ 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."),
6224
6589
  source: z8.string().min(1).describe("Local file path or http(s) URL of an image."),
6225
6590
  category: z8.string().optional().describe('Library category, default "general".'),
6226
6591
  label: z8.string().optional().describe("Optional file name / label."),
@@ -6342,7 +6707,9 @@ var studioReplaceCharacterInVideo = defineTool({
6342
6707
  inputSchema: {
6343
6708
  projectId: z8.string().min(1).describe("Project id from studio_list_projects."),
6344
6709
  sceneFrameUrl: httpUrl.describe("Public or signed URL of ONE frame of the target shot (png/jpg)."),
6345
- characterSlug: slug.optional().describe("Minted character to insert; its full-body reference (fallback three-quarter, face) is resolved automatically."),
6710
+ characterSlug: slug.optional().describe(
6711
+ "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."
6712
+ ),
6346
6713
  characterReferenceUrl: httpUrl.optional().describe("Explicit character reference image URL (use instead of characterSlug)."),
6347
6714
  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."),
6348
6715
  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."),
@@ -6415,7 +6782,7 @@ var studioReplaceCharacterInVideo = defineTool({
6415
6782
  });
6416
6783
  var studioGetSpend = defineTool({
6417
6784
  name: "studio_get_spend",
6418
- 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).`,
6785
+ 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).",
6419
6786
  inputSchema: {
6420
6787
  tzOffsetMinutes: z8.number().int().min(-840).max(840).optional()
6421
6788
  },
@@ -6427,7 +6794,7 @@ var studioGetSpend = defineTool({
6427
6794
  });
6428
6795
  var studioGetUsage = defineTool({
6429
6796
  name: "studio_get_usage",
6430
- 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.`,
6797
+ 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.",
6431
6798
  inputSchema: {},
6432
6799
  annotations: { readOnlyHint: true },
6433
6800
  handler: async (_args, ctx) => {
@@ -6547,7 +6914,7 @@ function registerTools(server, ctx) {
6547
6914
 
6548
6915
  // src/server.ts
6549
6916
  var SERVER_NAME = "lumitra-studio";
6550
- var SERVER_VERSION = "0.3.0";
6917
+ var SERVER_VERSION = "0.5.0";
6551
6918
  function createStudioMcpServer(opts = {}) {
6552
6919
  const config = opts.config ?? resolveConfig(opts.env ?? process.env);
6553
6920
  const client = new StudioClient(config, {
@@ -6559,7 +6926,7 @@ function createStudioMcpServer(opts = {}) {
6559
6926
  {
6560
6927
  instructions: [
6561
6928
  "Lumitra Studio: brand-consistent image generation, character minting, workflow runs and the still-swap video recipe over the hosted Studio API.",
6562
- "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.",
6929
+ "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.",
6563
6930
  "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.",
6564
6931
  "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.",
6565
6932
  "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."
@@ -6604,7 +6971,7 @@ Usage:
6604
6971
  studio-mcp --http --host 0.0.0.0 --port 3939
6605
6972
 
6606
6973
  Environment:
6607
- LUMITRA_STUDIO_API_KEY bearer token (required; legacy LUMITRA_STUDIO_SERVICE_TOKEN accepted)
6974
+ LUMITRA_STUDIO_API_KEY bearer token (required)
6608
6975
  LUMITRA_STUDIO_BASE_URL default https://studio.lumitra.co
6609
6976
 
6610
6977
  Tools: ${TOOL_NAMES.join(", ")}