@shop-os/modelkit 0.8.0 → 0.9.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/catalog.js CHANGED
@@ -1219,6 +1219,303 @@ var VEED_SUBTITLES = {
1219
1219
  }
1220
1220
  };
1221
1221
 
1222
+ // src/catalog/voices-fish.ts
1223
+ var FISH_VOICES = [
1224
+ {
1225
+ id: "933563129e564b19a115bedd57b7406a",
1226
+ name: "Sarah",
1227
+ gender: "female",
1228
+ languages: ["en"],
1229
+ description: "An engaged speaker.",
1230
+ previewUrl: "https://platform.r2.fish.audio/task/4e204945670c460c86fdab57512cb308.mp3"
1231
+ },
1232
+ {
1233
+ id: "bf322df2096a46f18c579d0baa36f41d",
1234
+ name: "Adrian",
1235
+ gender: "male",
1236
+ languages: ["en"],
1237
+ description: "A steady and reliable narrator.",
1238
+ previewUrl: "https://platform.r2.fish.audio/task/d730b09f4a484316a77ecc86137e85fb.mp3"
1239
+ },
1240
+ {
1241
+ id: "536d3a5e000945adb7038665781a4aca",
1242
+ name: "Ethan",
1243
+ gender: "male",
1244
+ languages: ["en"],
1245
+ description: "A curious explainer.",
1246
+ previewUrl: "https://platform.r2.fish.audio/task/bdd677ed767744fc91f166468786264b.mp3"
1247
+ },
1248
+ {
1249
+ id: "b347db033a6549378b48d00acb0d06cd",
1250
+ name: "Selene",
1251
+ gender: "female",
1252
+ languages: ["en"],
1253
+ description: "A meditative female voice.",
1254
+ previewUrl: "https://platform.r2.fish.audio/task/fcf629b4a0b343558f461338ba1b9da4.mp3"
1255
+ },
1256
+ {
1257
+ id: "9a9cf47702da476aa4629e2506d4a857",
1258
+ name: "Hannah",
1259
+ gender: "female",
1260
+ languages: ["en"],
1261
+ description: "A conversation specialist.",
1262
+ previewUrl: "https://platform.r2.fish.audio/task/08d658835feb427db808e37060b78eb0.mp3"
1263
+ },
1264
+ {
1265
+ id: "79d0bd3e4e5444b18f7b6d89b5927bf1",
1266
+ name: "Jordan",
1267
+ gender: "male",
1268
+ languages: ["en"],
1269
+ description: "A motivational speaker.",
1270
+ previewUrl: "https://platform.r2.fish.audio/task/edecbc0bdd4c46c39eda8520a0f729ef.mp3"
1271
+ },
1272
+ {
1273
+ id: "e3cd384158934cc9a01029cd7d278634",
1274
+ name: "Laura",
1275
+ gender: "female",
1276
+ languages: ["en"],
1277
+ description: "A confident female narrator.",
1278
+ previewUrl: "https://platform.r2.fish.audio/task/e821bef995344d519caa1c93d808db23.mp3"
1279
+ },
1280
+ {
1281
+ id: "f3a2b90078d54a65af4b95f63bc7798e",
1282
+ name: "Sheila",
1283
+ gender: "female",
1284
+ accent: "american",
1285
+ languages: ["en"],
1286
+ description: "A young American English woman's voice.",
1287
+ previewUrl: "https://platform.r2.fish.audio/tasks/2026-08-24/9bb9e1a1cf3f47f38700da1c5c0a678a.mp3"
1288
+ },
1289
+ {
1290
+ id: "0db6e93af420470b9d9b9a886dbaa954",
1291
+ name: "Kai",
1292
+ gender: "male",
1293
+ languages: ["en"],
1294
+ description: "A friendly English man's voice for companion apps and everyday conversation.",
1295
+ previewUrl: "https://platform.r2.fish.audio/tasks/2026-08-24/13306f1172544c3ba3106d1e363feaa6.mp3"
1296
+ },
1297
+ {
1298
+ id: "8abfa9ebd4c24a3399b68c3cdac8c7b5",
1299
+ name: "Freya",
1300
+ gender: "female",
1301
+ accent: "british",
1302
+ languages: ["en"],
1303
+ description: "A natural, professional British English voice with a brisk, conversational pace. Clear and easy to follow, suited to voice assistants, customer support and everyday narration.",
1304
+ previewUrl: "https://platform.r2.fish.audio/tasks/2026-08-25/07918705a56b42daaab149f65392cea7.mp3"
1305
+ },
1306
+ {
1307
+ id: "2f64e528a4f2406691925ab1e0ed5b7f",
1308
+ name: "Ananya",
1309
+ gender: "female",
1310
+ languages: ["hi"],
1311
+ description: "\u0938\u093E\u092B\u093C \u0914\u0930 \u0906\u0924\u094D\u092E\u0935\u093F\u0936\u094D\u0935\u093E\u0938 \u092D\u0930\u0940 \u0906\u0935\u093E\u091C\u093C, \u091C\u094B \u0935\u0949\u0907\u0938 \u0905\u0938\u093F\u0938\u094D\u091F\u0947\u0902\u091F, \u0917\u094D\u0930\u093E\u0939\u0915 \u0938\u0947\u0935\u093E \u0914\u0930 \u092A\u094D\u0930\u0938\u094D\u0924\u0941\u0924\u093F\u092F\u094B\u0902 \u0915\u0947 \u0932\u093F\u090F \u0909\u092A\u092F\u0941\u0915\u094D\u0924 \u0939\u0948\u0964",
1312
+ previewUrl: "https://platform.r2.fish.audio/tasks/2026-08-25/a17a700668d8405f8316db120733145c.mp3"
1313
+ },
1314
+ {
1315
+ id: "cefd91a07b99468f9148e2580990082d",
1316
+ name: "Arjun",
1317
+ gender: "male",
1318
+ languages: ["hi"],
1319
+ description: "\u0938\u093E\u092B\u093C \u0914\u0930 \u0906\u0924\u094D\u092E\u0935\u093F\u0936\u094D\u0935\u093E\u0938 \u092D\u0930\u0940 \u0906\u0935\u093E\u091C\u093C, \u091C\u094B \u0935\u0949\u0907\u0938 \u0905\u0938\u093F\u0938\u094D\u091F\u0947\u0902\u091F, \u0917\u094D\u0930\u093E\u0939\u0915 \u0938\u0947\u0935\u093E \u0914\u0930 \u092A\u094D\u0930\u0938\u094D\u0924\u0941\u0924\u093F\u092F\u094B\u0902 \u0915\u0947 \u0932\u093F\u090F \u0909\u092A\u092F\u0941\u0915\u094D\u0924 \u0939\u0948\u0964",
1320
+ previewUrl: "https://platform.r2.fish.audio/tasks/2026-08-24/a0c4a8eadb7841cdae8e5e68031a2dfb.mp3"
1321
+ },
1322
+ {
1323
+ id: "3f065d660212478a9c697fa965b4d098",
1324
+ name: "Carmen",
1325
+ gender: "female",
1326
+ languages: ["es"],
1327
+ description: "Una voz clara y segura para asistentes de voz, atenci\xF3n al cliente y presentaciones.",
1328
+ previewUrl: "https://platform.r2.fish.audio/tasks/2026-08-25/3421be48b6cb4c28bf829c25188e7e74.mp3"
1329
+ },
1330
+ {
1331
+ id: "ec0718028a2445fb9be8e8bfb56a0150",
1332
+ name: "Rodrigo",
1333
+ gender: "male",
1334
+ languages: ["es"],
1335
+ description: "Una voz clara y segura para asistentes de voz, atenci\xF3n al cliente y presentaciones.",
1336
+ previewUrl: "https://platform.r2.fish.audio/tasks/2026-08-25/5a77c32365604800ad5b4eb13c5585df.mp3"
1337
+ },
1338
+ {
1339
+ id: "10a3a20742114a4ea6dd441e7591850f",
1340
+ name: "Manon",
1341
+ gender: "female",
1342
+ languages: ["fr"],
1343
+ description: "Une voix claire et vive, adapt\xE9e aux assistants vocaux et au support client.",
1344
+ previewUrl: "https://platform.r2.fish.audio/tasks/2026-08-25/f7b75ee1fa524e48a8cefb003cf72952.mp3"
1345
+ },
1346
+ {
1347
+ id: "f69bca092b674168a8d02d61ca20943c",
1348
+ name: "Lucas",
1349
+ gender: "male",
1350
+ languages: ["fr"],
1351
+ description: "Une voix claire et assur\xE9e, adapt\xE9e aux assistants vocaux et au service client.",
1352
+ previewUrl: "https://platform.r2.fish.audio/tasks/2026-08-25/86fca3e56a014a3d8a11450854bb8311.mp3"
1353
+ },
1354
+ {
1355
+ id: "3937b44b604249f18b94913daef94043",
1356
+ name: "Lena",
1357
+ gender: "female",
1358
+ languages: ["de"],
1359
+ description: "Eine klare, souver\xE4ne Stimme f\xFCr Sprachassistenten, Kundenservice und Pr\xE4sentationen.",
1360
+ previewUrl: "https://platform.r2.fish.audio/tasks/2026-08-25/8659fec80b74442dab68a7755716a401.mp3"
1361
+ },
1362
+ {
1363
+ id: "c1e7cc926aa5469aa966c353094d4b0b",
1364
+ name: "Lukas",
1365
+ gender: "male",
1366
+ languages: ["de"],
1367
+ description: "Eine klare, souver\xE4ne Stimme f\xFCr Sprachassistenten, Kundenservice und Pr\xE4sentationen.",
1368
+ previewUrl: "https://platform.r2.fish.audio/tasks/2026-08-25/fb1d538126bb4fe38f8366250f6a2136.mp3"
1369
+ },
1370
+ {
1371
+ id: "b3c060cc41544a7da94a2c487647c902",
1372
+ name: "Let\xEDcia",
1373
+ gender: "female",
1374
+ accent: "brazilian",
1375
+ languages: ["pt"],
1376
+ description: "A Brazilian-accented Portuguese woman's voice, in her late twenties. Soft, warm, intimate. A freelance designer from Belo Horizonte who enjoys long walks in the park. Best suited to companion apps and casual everyday conversation.",
1377
+ previewUrl: "https://platform.r2.fish.audio/tasks/2026-10-12/13c6c0d96b5c4069951f89868ee15360.mp3"
1378
+ },
1379
+ {
1380
+ id: "add7bcb8d3c8423bbfa152ca232aad6e",
1381
+ name: "Ricardo",
1382
+ gender: "male",
1383
+ accent: "brazilian",
1384
+ languages: ["pt"],
1385
+ description: "A Brazilian-accented Portuguese man's voice, in his late forties. Clear, professional, monotone. A high school history teacher from Curitiba who loves classical music. Best suited to lessons, tutoring and explainer content.",
1386
+ previewUrl: "https://platform.r2.fish.audio/tasks/2026-10-12/67475fc717ae45cbb38e647a2dd37fe1.mp3"
1387
+ },
1388
+ {
1389
+ id: "b2d9d8db057042688a5e318b8f405bc2",
1390
+ name: "Kyoko",
1391
+ gender: "female",
1392
+ languages: ["ja"],
1393
+ description: "\u4E01\u5BE7\u3067\u843D\u3061\u7740\u3044\u305F\u8A71\u3057\u65B9\u304C\u7279\u5FB4\u306E\u5973\u6027\u306E\u58F0\u3002\u304A\u5BA2\u69D8\u5BFE\u5FDC\u3084\u60C5\u5831\u63D0\u4F9B\u306B\u6700\u9069\u3002",
1394
+ previewUrl: "https://platform.r2.fish.audio/task/993b3ccb52124341aff79580f1ce5a5a.mp3"
1395
+ },
1396
+ {
1397
+ id: "297a6fd278df47c3b9da9bfdf55ac89a",
1398
+ name: "Satoru",
1399
+ gender: "male",
1400
+ languages: ["ja"],
1401
+ description: "\u82E5\u3005\u3057\u304F\u3082\u843D\u3061\u7740\u304D\u306E\u3042\u308B\u7537\u6027\u306E\u58F0\u3002\u65E5\u5E38\u306E\u8A9E\u308A\u3084\u30EA\u30E9\u30C3\u30AF\u30B9\u3057\u305F\u30CA\u30EC\u30FC\u30B7\u30E7\u30F3\u306B\u6700\u9069\u3002",
1402
+ previewUrl: "https://platform.r2.fish.audio/task/1b4712fb831f4c149eef6b0133157418.mp3"
1403
+ },
1404
+ {
1405
+ id: "0b14f34a13a94ed88fe6113193749bb0",
1406
+ name: "Fatima",
1407
+ gender: "female",
1408
+ languages: ["ar"],
1409
+ description: "\u0635\u0648\u062A \u0648\u0627\u0636\u062D \u0648\u0648\u0627\u062B\u0642 \u064A\u0646\u0627\u0633\u0628 \u0627\u0644\u0645\u0633\u0627\u0639\u062F\u0627\u062A \u0627\u0644\u0635\u0648\u062A\u064A\u0629 \u0648\u062E\u062F\u0645\u0629 \u0627\u0644\u0639\u0645\u0644\u0627\u0621 \u0648\u0627\u0644\u0639\u0631\u0648\u0636 \u0627\u0644\u062A\u0642\u062F\u064A\u0645\u064A\u0629.",
1410
+ previewUrl: "https://platform.r2.fish.audio/tasks/2026-08-24/93406e1b905a4c4994306e3421243390.mp3"
1411
+ },
1412
+ {
1413
+ id: "74c6aba5cbf94a15bbdc547ffce5cb38",
1414
+ name: "Yutong",
1415
+ gender: "female",
1416
+ languages: ["zh"],
1417
+ description: "\u58F0\u97F3\u6E05\u6670\u81EA\u7136\uFF0C\u8868\u8FBE\u5229\u843D\uFF0C\u9002\u5408\u8BED\u97F3\u52A9\u624B\u3001\u5BA2\u670D\u548C\u65E5\u5E38\u64AD\u62A5\u3002",
1418
+ previewUrl: "https://platform.r2.fish.audio/tasks/2026-08-25/a48320f37fe44c4699549a1afc3778e8.mp3"
1419
+ }
1420
+ ];
1421
+
1422
+ // src/catalog/speech-fish.ts
1423
+ var FISH_LOGO = "https://cdn.jsdelivr.net/npm/@lobehub/icons-static-png@1.97.0/light/fishaudio.png";
1424
+ var PER_CALL = { unit: "images" };
1425
+ var PER_REQUEST = { unit: "requests" };
1426
+ var TTS_MAX_CHARS = 1e4;
1427
+ function fishTts(id, label, providerModelId, description) {
1428
+ return {
1429
+ id,
1430
+ aliases: [],
1431
+ label,
1432
+ type: "speech",
1433
+ adapter: "fish",
1434
+ providerModelId,
1435
+ lifecycle: "active",
1436
+ exposure: [],
1437
+ constraints: {
1438
+ voices: FISH_VOICES,
1439
+ outputFormats: ["mp3", "wav"],
1440
+ supports: [],
1441
+ prompt: { referenceSyntax: "numbered", maxChars: TTS_MAX_CHARS }
1442
+ },
1443
+ defaults: { voice: "9a9cf47702da476aa4629e2506d4a857", outputFormat: "mp3" },
1444
+ usage: PER_CALL,
1445
+ pricingKind: "flat",
1446
+ display: { provider: "fishaudio", logo: FISH_LOGO, description },
1447
+ capabilities: { customVoices: true }
1448
+ };
1449
+ }
1450
+ var FISH_MODELS = [
1451
+ fishTts(
1452
+ "fish-s2.1-pro",
1453
+ "Fish Audio S2.1 Pro",
1454
+ "s2.1-pro",
1455
+ "Fish Audio S2.1 Pro text to speech in 83 languages, with [bracket] directions and your own cloned voices."
1456
+ ),
1457
+ fishTts(
1458
+ "fish-s2.1-pro-free",
1459
+ "Fish Audio S2.1 Pro Free",
1460
+ "s2.1-pro-free",
1461
+ "Fish Audio S2.1 Pro on the free developer tier: the same voices at no cost, without latency or data-processing guarantees."
1462
+ ),
1463
+ fishTts(
1464
+ "fish-drama-3",
1465
+ "Fish Audio Drama 3",
1466
+ "drama-3",
1467
+ "Fish Audio Drama 3 text to speech you direct like an actor: [directions], sounds, pauses and per-word tags."
1468
+ ),
1469
+ {
1470
+ id: "fish-voice-clone",
1471
+ aliases: [],
1472
+ label: "Fish Audio Voice Clone",
1473
+ type: "voice-clone",
1474
+ adapter: "fish",
1475
+ providerModelId: "voice-clone",
1476
+ lifecycle: "active",
1477
+ exposure: [],
1478
+ constraints: {
1479
+ // POST /model takes 1-20 voice files; Fish advises 10 s or more of clean speech each.
1480
+ references: { audios: 20, audioFormats: ["wav", "mp3", "m4a", "opus"] },
1481
+ supports: []
1482
+ },
1483
+ defaults: {},
1484
+ usage: PER_REQUEST,
1485
+ pricingKind: "flat",
1486
+ display: {
1487
+ provider: "fishaudio",
1488
+ logo: FISH_LOGO,
1489
+ description: "Clone a private, reusable voice from one or more clean recordings, then speak with it on the Fish Audio speech models."
1490
+ },
1491
+ capabilities: {}
1492
+ },
1493
+ {
1494
+ id: "fish-voice-design-1",
1495
+ aliases: [],
1496
+ label: "Fish Audio Voice Design",
1497
+ type: "voice-design",
1498
+ adapter: "fish",
1499
+ providerModelId: "voice-design-1",
1500
+ lifecycle: "active",
1501
+ exposure: [],
1502
+ constraints: {
1503
+ voiceDesign: { maxCandidates: 4, previewMaxChars: 150 },
1504
+ supports: [],
1505
+ prompt: { referenceSyntax: "numbered", maxChars: 2e3 }
1506
+ },
1507
+ defaults: {},
1508
+ usage: PER_REQUEST,
1509
+ pricingKind: "flat",
1510
+ display: {
1511
+ provider: "fishaudio",
1512
+ logo: FISH_LOGO,
1513
+ description: "Describe a voice and get up to four short candidates; keep one as a cloned voice."
1514
+ },
1515
+ capabilities: {}
1516
+ }
1517
+ ];
1518
+
1222
1519
  // src/catalog/languages-audio.ts
1223
1520
  var ELEVENLABS_V4_LANGUAGES = [
1224
1521
  { code: "af", name: "Afrikaans" },
@@ -3300,7 +3597,7 @@ var ELEVENLABS_LOGO = "https://cdn.jsdelivr.net/npm/@lobehub/icons-static-png@1.
3300
3597
  var GOOGLE_LOGO = "https://d31vvfpk2ppdyq.cloudfront.net/assets/image/192485d2-a76a-4657-9c06-930b2c70badd_compressed.png";
3301
3598
  var MINIMAX_LOGO = "https://d31vvfpk2ppdyq.cloudfront.net/assets/image/f4c2e906-28b7-4a4a-b491-df8fef7808ed.png";
3302
3599
  var SYNC_LOGO = "https://cdn.jsdelivr.net/npm/@lobehub/icons-static-png@1.97.0/light/sync.png";
3303
- var PER_CALL = { unit: "images" };
3600
+ var PER_CALL2 = { unit: "images" };
3304
3601
  var EXPOSURE4 = ["cowork", "space_builder"];
3305
3602
  var LIPSYNC_AUDIO = { audioSeconds: { max: 30 } };
3306
3603
  function elevenLabsTts(tts) {
@@ -3321,7 +3618,7 @@ function elevenLabsTts(tts) {
3321
3618
  prompt: { referenceSyntax: "numbered", maxChars: tts.maxChars }
3322
3619
  },
3323
3620
  defaults: { voice: "Rachel", ...tts.formats && { outputFormat: "mp3_44100_128" } },
3324
- usage: PER_CALL,
3621
+ usage: PER_CALL2,
3325
3622
  pricingKind: "flat",
3326
3623
  display: { provider: "elevenlabs", logo: ELEVENLABS_LOGO, description: tts.description },
3327
3624
  capabilities: {}
@@ -3345,7 +3642,7 @@ function geminiTts(id, label, description) {
3345
3642
  prompt: { referenceSyntax: "numbered", maxChars: 8192 }
3346
3643
  },
3347
3644
  defaults: { voice: "Kore" },
3348
- usage: PER_CALL,
3645
+ usage: PER_CALL2,
3349
3646
  pricingKind: "flat",
3350
3647
  display: { provider: "google", logo: GOOGLE_LOGO, description },
3351
3648
  capabilities: {}
@@ -3368,7 +3665,7 @@ function syncLipsync(id, label, providerModelId, syncModel, description) {
3368
3665
  },
3369
3666
  defaults: {},
3370
3667
  ...syncModel !== void 0 && { providerOptions: { fal: { model: syncModel } } },
3371
- usage: PER_CALL,
3668
+ usage: PER_CALL2,
3372
3669
  pricingKind: "flat",
3373
3670
  display: { provider: "sync", logo: SYNC_LOGO, description },
3374
3671
  capabilities: { acceptsSourceVideo: true }
@@ -3429,7 +3726,7 @@ var MEDIA_NODE_MODELS = [
3429
3726
  prompt: { referenceSyntax: "numbered", maxChars: 1e4 }
3430
3727
  },
3431
3728
  defaults: { voice: "Wise_Woman" },
3432
- usage: PER_CALL,
3729
+ usage: PER_CALL2,
3433
3730
  pricingKind: "flat",
3434
3731
  display: {
3435
3732
  provider: "minimax",
@@ -3456,7 +3753,7 @@ var MEDIA_NODE_MODELS = [
3456
3753
  supports: []
3457
3754
  },
3458
3755
  defaults: { voice: "Rachel", outputFormat: "mp3_44100_128" },
3459
- usage: PER_CALL,
3756
+ usage: PER_CALL2,
3460
3757
  pricingKind: "flat",
3461
3758
  display: {
3462
3759
  provider: "elevenlabs",
@@ -3483,7 +3780,7 @@ var MEDIA_NODE_MODELS = [
3483
3780
  supports: []
3484
3781
  },
3485
3782
  defaults: {},
3486
- usage: PER_CALL,
3783
+ usage: PER_CALL2,
3487
3784
  pricingKind: "flat",
3488
3785
  display: {
3489
3786
  provider: "elevenlabs",
@@ -3530,7 +3827,7 @@ var MEDIA_NODE_MODELS = [
3530
3827
  supports: []
3531
3828
  },
3532
3829
  defaults: {},
3533
- usage: PER_CALL,
3830
+ usage: PER_CALL2,
3534
3831
  pricingKind: "flat",
3535
3832
  display: {
3536
3833
  provider: "veed",
@@ -3582,7 +3879,7 @@ var MEDIA_NODE_MODELS = [
3582
3879
  supports: []
3583
3880
  },
3584
3881
  defaults: {},
3585
- usage: PER_CALL,
3882
+ usage: PER_CALL2,
3586
3883
  pricingKind: "flat",
3587
3884
  display: {
3588
3885
  provider: "elevenlabs",
@@ -3591,7 +3888,8 @@ var MEDIA_NODE_MODELS = [
3591
3888
  },
3592
3889
  capabilities: {}
3593
3890
  },
3594
- SAM_3_IMAGE
3891
+ SAM_3_IMAGE,
3892
+ ...FISH_MODELS
3595
3893
  ];
3596
3894
 
3597
3895
  // src/catalog/model3d.ts
@@ -7232,7 +7530,8 @@ var ADAPTER_IDS = [
7232
7530
  "prodia",
7233
7531
  "higgsfield",
7234
7532
  "xai-video",
7235
- "luma"
7533
+ "luma",
7534
+ "fish"
7236
7535
  ];
7237
7536
  var ModelTypeSchema = z3.enum([
7238
7537
  "text",
@@ -7249,6 +7548,8 @@ var ModelTypeSchema = z3.enum([
7249
7548
  "dubbing",
7250
7549
  "lipsync",
7251
7550
  "transcription",
7551
+ "voice-clone",
7552
+ "voice-design",
7252
7553
  "model3d"
7253
7554
  ]);
7254
7555
  var LifecycleSchema = z3.enum(["active", "deprecated", "retired"]);
@@ -7424,6 +7725,10 @@ var ModelConstraintsSchema = z3.looseObject({
7424
7725
  supports: z3.array(SupportedOptionSchema).optional(),
7425
7726
  ratioIgnoredWith: z3.array(z3.enum(["firstFrame", "sourceVideo"])).optional(),
7426
7727
  voices: z3.array(VoiceOptionSchema).optional(),
7728
+ voiceDesign: z3.object({
7729
+ maxCandidates: z3.number().int().positive(),
7730
+ previewMaxChars: z3.number().int().positive()
7731
+ }).optional(),
7427
7732
  languages: z3.array(LanguageOptionSchema).optional(),
7428
7733
  outputFormats: z3.array(z3.string().min(1)).min(1).optional(),
7429
7734
  sourceVideoSeconds: VideoSecondsLimitsSchema.omit({ total: true }).extend({ inReferenceTotal: z3.boolean().optional() }).optional(),
@@ -7522,6 +7827,7 @@ var CatalogDisplaySchema = z3.looseObject({
7522
7827
  });
7523
7828
  var CatalogCapabilitiesSchema = z3.looseObject({
7524
7829
  acceptsSourceVideo: z3.boolean().optional(),
7830
+ customVoices: z3.boolean().optional(),
7525
7831
  videoEdit: z3.boolean().optional(),
7526
7832
  framesExcludeReferences: z3.boolean().optional(),
7527
7833
  sourceVideoExcludesFrames: z3.boolean().optional(),
@@ -9400,8 +9706,17 @@ function resolveSpeechOptions(model, request, onUnsupported = "error") {
9400
9706
  const warnings = [];
9401
9707
  checkScript(model, request.text);
9402
9708
  const resolved = { model: model.id, text: request.text };
9403
- const voice = resolveVoice(model, request.voice, onUnsupported, warnings);
9404
- if (voice !== void 0) resolved.voice = voice;
9709
+ const customVoice = request.customVoice?.trim();
9710
+ if (customVoice) {
9711
+ if (model.capabilities?.customVoices !== true) {
9712
+ throw unsupported3(model, "does not take a custom voice");
9713
+ }
9714
+ if (request.voice?.trim()) throw unsupported3(model, "takes voice or customVoice, not both");
9715
+ resolved.customVoice = customVoice;
9716
+ } else {
9717
+ const voice = resolveVoice(model, request.voice, onUnsupported, warnings);
9718
+ if (voice !== void 0) resolved.voice = voice;
9719
+ }
9405
9720
  const language = resolveListed(
9406
9721
  model,
9407
9722
  {
@@ -9523,6 +9838,106 @@ function resolveTranscriptionOptions(model, request, onUnsupported = "error") {
9523
9838
  return { request: resolved, warnings };
9524
9839
  }
9525
9840
 
9841
+ // src/resolve-voice.ts
9842
+ function refuse(model, code, message) {
9843
+ return new ModelKitError({ code, message: `${model.label} ${message}`, model: model.id });
9844
+ }
9845
+ function checkSample(model, sample, index) {
9846
+ const limits = model.constraints.references?.audioSeconds;
9847
+ const length = sample.audio.durationSeconds;
9848
+ if (limits === void 0 || length === void 0) return;
9849
+ if (limits.min !== void 0 && length < limits.min || limits.max !== void 0 && length > limits.max) {
9850
+ const range2 = `${limits.min ?? 0}-${limits.max ?? "any"} s`;
9851
+ throw refuse(
9852
+ model,
9853
+ "unsupported_option",
9854
+ `takes samples of ${range2}; sample ${index + 1} is ${length} s`
9855
+ );
9856
+ }
9857
+ }
9858
+ function resolveVoiceCloneOptions(model, request) {
9859
+ const title = request.title.trim();
9860
+ if (!title) throw refuse(model, "missing_input", "needs a title");
9861
+ if (request.samples.length === 0) throw refuse(model, "missing_input", "needs a voice sample");
9862
+ const max = model.constraints.references?.audios;
9863
+ if (max !== void 0 && request.samples.length > max) {
9864
+ throw refuse(
9865
+ model,
9866
+ "unsupported_option",
9867
+ `takes up to ${max} samples; got ${request.samples.length}`
9868
+ );
9869
+ }
9870
+ request.samples.forEach((sample, index) => checkSample(model, sample, index));
9871
+ const total = model.constraints.references?.audioSeconds?.total;
9872
+ const seconds3 = request.samples.reduce((sum, s) => sum + (s.audio.durationSeconds ?? 0), 0);
9873
+ if (total !== void 0 && seconds3 > total) {
9874
+ throw refuse(
9875
+ model,
9876
+ "unsupported_option",
9877
+ `takes up to ${total} s of samples; got ${seconds3} s`
9878
+ );
9879
+ }
9880
+ for (const field of ["transcript", "designSignature"]) {
9881
+ const set = request.samples.filter((s) => s[field]?.trim()).length;
9882
+ if (set !== 0 && set !== request.samples.length) {
9883
+ throw refuse(model, "missing_input", `needs ${field} on every sample or on none`);
9884
+ }
9885
+ }
9886
+ return { request: { ...request, model: model.id, title }, warnings: [] };
9887
+ }
9888
+ function resolveVoiceDesignOptions(model, request, onUnsupported = "error") {
9889
+ const warnings = [];
9890
+ const prompt = request.prompt.trim();
9891
+ if (!prompt) throw refuse(model, "missing_input", "needs a voice description");
9892
+ const maxChars = promptConventions(model).maxChars;
9893
+ if (maxChars !== void 0 && prompt.length > maxChars) {
9894
+ throw refuse(
9895
+ model,
9896
+ "unsupported_option",
9897
+ `takes up to ${maxChars} characters; got ${prompt.length}`
9898
+ );
9899
+ }
9900
+ const limits = model.constraints.voiceDesign;
9901
+ const resolved = { model: model.id, prompt };
9902
+ const preview2 = request.previewText?.trim();
9903
+ if (preview2) {
9904
+ if (limits !== void 0 && preview2.length > limits.previewMaxChars) {
9905
+ throw refuse(
9906
+ model,
9907
+ "unsupported_option",
9908
+ `takes a preview line of up to ${limits.previewMaxChars} characters; got ${preview2.length}`
9909
+ );
9910
+ }
9911
+ resolved.previewText = preview2;
9912
+ }
9913
+ const language = request.language?.trim();
9914
+ if (language) resolved.language = language;
9915
+ if (request.seed !== void 0) resolved.seed = request.seed;
9916
+ const count2 = request.count ?? model.defaults.count;
9917
+ if (count2 !== void 0) {
9918
+ const cap = limits?.maxCandidates ?? count2;
9919
+ if (!Number.isInteger(count2) || count2 < 1 || count2 > cap) {
9920
+ if (onUnsupported === "error" || !Number.isInteger(count2)) {
9921
+ throw refuse(
9922
+ model,
9923
+ "unsupported_option",
9924
+ `returns 1-${cap} candidates; got ${count2}`
9925
+ );
9926
+ }
9927
+ const to = Math.min(Math.max(count2, 1), cap);
9928
+ warnings.push({
9929
+ code: "option_ignored",
9930
+ field: "count",
9931
+ from: String(count2),
9932
+ to: String(to),
9933
+ message: `count ${count2} replaced by ${to} for ${model.id}`
9934
+ });
9935
+ resolved.count = to;
9936
+ } else resolved.count = count2;
9937
+ }
9938
+ return { request: resolved, warnings };
9939
+ }
9940
+
9526
9941
  // src/resolve-call.ts
9527
9942
  var MEDIA_METHODS = {
9528
9943
  text: [],
@@ -9538,6 +9953,9 @@ var MEDIA_METHODS = {
9538
9953
  lipsync: ["lipsync"],
9539
9954
  dubbing: ["dub"],
9540
9955
  transcription: ["transcribe"],
9956
+ // deleteVoice runs on the voice-clone row too; it checks with assertMethod("createVoice").
9957
+ "voice-clone": ["createVoice"],
9958
+ "voice-design": ["designVoice"],
9541
9959
  "video-tool": ["submitVideoTool"],
9542
9960
  // 3D jobs run through submitModel3d, which checks the row type itself; their preflight
9543
9961
  // is model3dInputRefusal, not resolveOptions.
@@ -9552,6 +9970,8 @@ var METHOD_NOUN = {
9552
9970
  lipsync: "a lipsync model",
9553
9971
  dub: "a dubbing model",
9554
9972
  transcribe: "a transcription model",
9973
+ createVoice: "a voice cloning model",
9974
+ designVoice: "a voice design model",
9555
9975
  submitVideoTool: "a video tool"
9556
9976
  };
9557
9977
  function assertMethod(model, method) {
@@ -9594,6 +10014,14 @@ function resolveTranscriptionCall(model, request, onUnsupported = "error") {
9594
10014
  assertMethod(model, "transcribe");
9595
10015
  return resolveTranscriptionOptions(model, request, onUnsupported);
9596
10016
  }
10017
+ function resolveVoiceCloneCall(model, request) {
10018
+ assertMethod(model, "createVoice");
10019
+ return resolveVoiceCloneOptions(model, request);
10020
+ }
10021
+ function resolveVoiceDesignCall(model, request, onUnsupported = "error") {
10022
+ assertMethod(model, "designVoice");
10023
+ return resolveVoiceDesignOptions(model, request, onUnsupported);
10024
+ }
9597
10025
  function resolveVideoToolCall(model, request) {
9598
10026
  assertMethod(model, "submitVideoTool");
9599
10027
  const plan = planVideoTool(model, request);
@@ -9640,6 +10068,13 @@ function resolveOptions(model, call, onUnsupported = "error") {
9640
10068
  method: call.method,
9641
10069
  ...resolveTranscriptionCall(model, call.request, onUnsupported)
9642
10070
  };
10071
+ case "createVoice":
10072
+ return { method: call.method, ...resolveVoiceCloneCall(model, call.request) };
10073
+ case "designVoice":
10074
+ return {
10075
+ method: call.method,
10076
+ ...resolveVoiceDesignCall(model, call.request, onUnsupported)
10077
+ };
9643
10078
  case "submitVideoTool":
9644
10079
  return { method: call.method, ...resolveVideoToolCall(model, call.request) };
9645
10080
  }
@@ -9699,6 +10134,8 @@ export {
9699
10134
  resolveTranscriptionOptions,
9700
10135
  resolveVideoOptions,
9701
10136
  resolveVoiceChangeOptions,
10137
+ resolveVoiceCloneOptions,
10138
+ resolveVoiceDesignOptions,
9702
10139
  sortCatalogRows,
9703
10140
  takesTargetAspectRatio,
9704
10141
  videoLengthViolation,