@shop-os/modelkit 0.7.11 → 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/CHANGELOG.md +27 -0
- package/README.md +8 -2
- package/dist/catalog.cjs +450 -13
- package/dist/catalog.cjs.map +1 -1
- package/dist/catalog.d.cts +110 -5
- package/dist/catalog.d.ts +110 -5
- package/dist/catalog.js +450 -13
- package/dist/catalog.js.map +1 -1
- package/dist/index.cjs +955 -183
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +110 -4
- package/dist/index.d.ts +110 -4
- package/dist/index.js +952 -183
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,33 @@ header in the release PR.
|
|
|
9
9
|
|
|
10
10
|
## [Unreleased]
|
|
11
11
|
|
|
12
|
+
## [0.9.0] - Unreleased
|
|
13
|
+
|
|
14
|
+
Fish Audio: text to speech, voice cloning and voice design (ENG-3400).
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- Adapter `fish` for the Fish Audio REST API (`https://api.fish.audio`, base-url override `endpoints.fish`). Key `fish` from `FISH_AUDIO_API_KEY`, with the `ENTERPRISE_` / `SLOOSH_` prefixes; a sloosh call without `SLOOSH_FISH_AUDIO_API_KEY` fails with `auth`.
|
|
18
|
+
- Speech rows `fish-s2.1-pro`, `fish-s2.1-pro-free` and `fish-drama-3` (the Fish `model` header `s2.1-pro`, `s2.1-pro-free`, `drama-3`). 24 Fish Official library voices (voice-designed or licensed, none a real-person clone; default Hannah), `mp3` (default) or `wav` output returned as bytes, a 10,000-character kit cap (Fish states none). Fish auto-detects the language, so the rows list none and `language` is dropped with a warning. Usage is one `images`-unit call, like the other speech rows; Fish bills per UTF-8 byte of the script ($15 / M bytes, Drama 3 $50 / M list, the free tier $0).
|
|
19
|
+
- `SpeechRequest.customVoice`: a voice id `createVoice` returned, on rows with the new `capabilities.customVoices` (the Fish speech rows). It replaces `voice`; sending both, or a custom voice to another row, is `unsupported_option`.
|
|
20
|
+
- Row types `voice-clone` and `voice-design`, and kit methods `createVoice` (row `fish-voice-clone`: 1-20 samples, optional transcripts and voice-design signatures on every sample or none, a private fast clone; returns `voiceId` and, when Fish reports a known one, `state`), `designVoice` (row `fish-voice-design-1`: a description, an optional preview line of up to 150 characters (the Fish OpenAPI limit), 1-4 candidates returned as WAV bytes with a `signature`; $0.01 per request) and `deleteVoice` (on the clone row; a 404 is `provider_rejected`, because Fish also answers 404 for a private voice of another account, except after a failed attempt of the same call, which may have deleted it). Usage unit `requests`. `resolveVoiceCloneOptions` / `resolveVoiceDesignOptions` and `resolveOptions` cover the new methods.
|
|
21
|
+
- A cloned voice lives in the Fish account of the key set that made it: create, speak and delete with the same `tier`.
|
|
22
|
+
- `constraints.voiceDesign` (`maxCandidates`, `previewMaxChars`).
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
- A 403 whose text says "Top up API credit" (Fish, out of API credit) is `provider_billing`, not `auth`.
|
|
26
|
+
|
|
27
|
+
### Notes
|
|
28
|
+
- Every Fish row ships with `exposure: []`: callable by id, listed nowhere, unpriced. Price and expose them in pro-backend per env when ENG-3400 is ready.
|
|
29
|
+
- `createVoice` retries only a 429: after a 5xx Fish may already hold the voice, and a retry would make a second one.
|
|
30
|
+
- A kit before 0.9.0 drops `voice-clone` / `voice-design` rows on load with a warning, and refuses the `fish` adapter id, so those rows stay invisible to consumers that have not upgraded.
|
|
31
|
+
|
|
32
|
+
## [0.8.0] - Unreleased
|
|
33
|
+
|
|
34
|
+
The BytePlus ModelArk key has one env name.
|
|
35
|
+
|
|
36
|
+
### Removed
|
|
37
|
+
- `keysFromEnv` reads the `ark` key from `BYTEDANCE_API_KEY` only (with the `ENTERPRISE_` / `SLOOSH_` prefixes); the two older alias names are no longer read. An env that sets only an alias gets no `ark` key, so an Ark call fails with a `ModelKitError` code `auth`. Set `BYTEDANCE_API_KEY` before a consumer upgrades.
|
|
38
|
+
|
|
12
39
|
## [0.7.11] - Unreleased
|
|
13
40
|
|
|
14
41
|
Kling 4.0 on fal.
|
package/README.md
CHANGED
|
@@ -47,6 +47,12 @@ const synced = await kit.lipsync({ model: "lipsync-2", sourceVideo: { url }, aud
|
|
|
47
47
|
const dubbed = await kit.dub({ model: "elevenlabs-dubbing", sourceVideo: { url }, targetLanguage: "es" });
|
|
48
48
|
const transcript = await kit.transcribe({ model: "elevenlabs-scribe-v2", source: { url }, timestamps: true });
|
|
49
49
|
|
|
50
|
+
// Custom voices (Fish Audio): clone once per workspace, keep `voiceId`, speak with it on the
|
|
51
|
+
// same tier. `designVoice` returns candidates to keep the same way (pass `signature`).
|
|
52
|
+
const { voiceId } = await kit.createVoice({ model: "fish-voice-clone", title, samples: [{ audio: { url } }] }, { tier });
|
|
53
|
+
const mine = await kit.generateSpeech({ model: "fish-s2.1-pro", text, customVoice: voiceId }, { tier });
|
|
54
|
+
await kit.deleteVoice({ model: "fish-voice-clone", voiceId }, { tier });
|
|
55
|
+
|
|
50
56
|
const clip = await kit.prepareMediaPart(
|
|
51
57
|
"gemini-2.5-flash",
|
|
52
58
|
{ bytes, mime: "video/mp4" },
|
|
@@ -108,7 +114,7 @@ before submit), and `model3dPricingSku(row, options, inputs)` names the flat pri
|
|
|
108
114
|
bills under; `model3dSkus(row)` lists them all, and pro-backend keeps a `credit_pricing_flat`
|
|
109
115
|
row for each. All three are in the browser-safe catalog entry.
|
|
110
116
|
|
|
111
|
-
Keys come from `process.env` by default: bare names for pro (`OPENAI_API_KEY`, `FAL_KEY`, `
|
|
117
|
+
Keys come from `process.env` by default: bare names for pro (`OPENAI_API_KEY`, `FAL_KEY`, `BYTEDANCE_API_KEY`, `LUMA_API_KEY` ...), the `ENTERPRISE_` prefix for enterprise and the `SLOOSH_` prefix for sloosh (sloosh reads `SLOOSH_LUMA_API_KEY` for Luma). Pass `keys: { pro, enterprise, sloosh }` to override. `tier: "sloosh"` uses the sloosh keys only: a missing key is a `ModelKitError` with code `auth`, never a fallback to pro.
|
|
112
118
|
|
|
113
119
|
## Browser-safe catalog
|
|
114
120
|
|
|
@@ -344,7 +350,7 @@ the app's secrets, not a Cloudflare gateway token.
|
|
|
344
350
|
|
|
345
351
|
## Adapters
|
|
346
352
|
|
|
347
|
-
`ai-sdk-text`, `google-image`, `openai-image`, `google-genai` (Veo, Omni with `delivery: "uri"`, Lyria 3, Files API media parts), `ark` (Seedream, Seedance), `fal`, `replicate` (images, video, Lyria 2, MMAudio), `prodia`, `higgsfield`, `xai-video`, `luma` (Uni images, Ray 3.2 video via the Agents API; `LUMA_API_KEY`, base-url override `endpoints.luma`). See `src/adapters/`.
|
|
353
|
+
`ai-sdk-text`, `google-image`, `openai-image`, `google-genai` (Veo, Omni with `delivery: "uri"`, Lyria 3, Files API media parts), `ark` (Seedream, Seedance), `fal`, `replicate` (images, video, Lyria 2, MMAudio), `prodia`, `higgsfield`, `xai-video`, `luma` (Uni images, Ray 3.2 video via the Agents API; `LUMA_API_KEY`, base-url override `endpoints.luma`), `fish` (Fish Audio speech, `createVoice` / `designVoice` / `deleteVoice`; `FISH_AUDIO_API_KEY`, base-url override `endpoints.fish`). See `src/adapters/`.
|
|
348
354
|
|
|
349
355
|
## Not in the package
|
|
350
356
|
|
package/dist/catalog.cjs
CHANGED
|
@@ -74,6 +74,8 @@ __export(catalog_entry_exports, {
|
|
|
74
74
|
resolveTranscriptionOptions: () => resolveTranscriptionOptions,
|
|
75
75
|
resolveVideoOptions: () => resolveVideoOptions,
|
|
76
76
|
resolveVoiceChangeOptions: () => resolveVoiceChangeOptions,
|
|
77
|
+
resolveVoiceCloneOptions: () => resolveVoiceCloneOptions,
|
|
78
|
+
resolveVoiceDesignOptions: () => resolveVoiceDesignOptions,
|
|
77
79
|
sortCatalogRows: () => sortCatalogRows,
|
|
78
80
|
takesTargetAspectRatio: () => takesTargetAspectRatio,
|
|
79
81
|
videoLengthViolation: () => videoLengthViolation,
|
|
@@ -1302,6 +1304,303 @@ var VEED_SUBTITLES = {
|
|
|
1302
1304
|
}
|
|
1303
1305
|
};
|
|
1304
1306
|
|
|
1307
|
+
// src/catalog/voices-fish.ts
|
|
1308
|
+
var FISH_VOICES = [
|
|
1309
|
+
{
|
|
1310
|
+
id: "933563129e564b19a115bedd57b7406a",
|
|
1311
|
+
name: "Sarah",
|
|
1312
|
+
gender: "female",
|
|
1313
|
+
languages: ["en"],
|
|
1314
|
+
description: "An engaged speaker.",
|
|
1315
|
+
previewUrl: "https://platform.r2.fish.audio/task/4e204945670c460c86fdab57512cb308.mp3"
|
|
1316
|
+
},
|
|
1317
|
+
{
|
|
1318
|
+
id: "bf322df2096a46f18c579d0baa36f41d",
|
|
1319
|
+
name: "Adrian",
|
|
1320
|
+
gender: "male",
|
|
1321
|
+
languages: ["en"],
|
|
1322
|
+
description: "A steady and reliable narrator.",
|
|
1323
|
+
previewUrl: "https://platform.r2.fish.audio/task/d730b09f4a484316a77ecc86137e85fb.mp3"
|
|
1324
|
+
},
|
|
1325
|
+
{
|
|
1326
|
+
id: "536d3a5e000945adb7038665781a4aca",
|
|
1327
|
+
name: "Ethan",
|
|
1328
|
+
gender: "male",
|
|
1329
|
+
languages: ["en"],
|
|
1330
|
+
description: "A curious explainer.",
|
|
1331
|
+
previewUrl: "https://platform.r2.fish.audio/task/bdd677ed767744fc91f166468786264b.mp3"
|
|
1332
|
+
},
|
|
1333
|
+
{
|
|
1334
|
+
id: "b347db033a6549378b48d00acb0d06cd",
|
|
1335
|
+
name: "Selene",
|
|
1336
|
+
gender: "female",
|
|
1337
|
+
languages: ["en"],
|
|
1338
|
+
description: "A meditative female voice.",
|
|
1339
|
+
previewUrl: "https://platform.r2.fish.audio/task/fcf629b4a0b343558f461338ba1b9da4.mp3"
|
|
1340
|
+
},
|
|
1341
|
+
{
|
|
1342
|
+
id: "9a9cf47702da476aa4629e2506d4a857",
|
|
1343
|
+
name: "Hannah",
|
|
1344
|
+
gender: "female",
|
|
1345
|
+
languages: ["en"],
|
|
1346
|
+
description: "A conversation specialist.",
|
|
1347
|
+
previewUrl: "https://platform.r2.fish.audio/task/08d658835feb427db808e37060b78eb0.mp3"
|
|
1348
|
+
},
|
|
1349
|
+
{
|
|
1350
|
+
id: "79d0bd3e4e5444b18f7b6d89b5927bf1",
|
|
1351
|
+
name: "Jordan",
|
|
1352
|
+
gender: "male",
|
|
1353
|
+
languages: ["en"],
|
|
1354
|
+
description: "A motivational speaker.",
|
|
1355
|
+
previewUrl: "https://platform.r2.fish.audio/task/edecbc0bdd4c46c39eda8520a0f729ef.mp3"
|
|
1356
|
+
},
|
|
1357
|
+
{
|
|
1358
|
+
id: "e3cd384158934cc9a01029cd7d278634",
|
|
1359
|
+
name: "Laura",
|
|
1360
|
+
gender: "female",
|
|
1361
|
+
languages: ["en"],
|
|
1362
|
+
description: "A confident female narrator.",
|
|
1363
|
+
previewUrl: "https://platform.r2.fish.audio/task/e821bef995344d519caa1c93d808db23.mp3"
|
|
1364
|
+
},
|
|
1365
|
+
{
|
|
1366
|
+
id: "f3a2b90078d54a65af4b95f63bc7798e",
|
|
1367
|
+
name: "Sheila",
|
|
1368
|
+
gender: "female",
|
|
1369
|
+
accent: "american",
|
|
1370
|
+
languages: ["en"],
|
|
1371
|
+
description: "A young American English woman's voice.",
|
|
1372
|
+
previewUrl: "https://platform.r2.fish.audio/tasks/2026-08-24/9bb9e1a1cf3f47f38700da1c5c0a678a.mp3"
|
|
1373
|
+
},
|
|
1374
|
+
{
|
|
1375
|
+
id: "0db6e93af420470b9d9b9a886dbaa954",
|
|
1376
|
+
name: "Kai",
|
|
1377
|
+
gender: "male",
|
|
1378
|
+
languages: ["en"],
|
|
1379
|
+
description: "A friendly English man's voice for companion apps and everyday conversation.",
|
|
1380
|
+
previewUrl: "https://platform.r2.fish.audio/tasks/2026-08-24/13306f1172544c3ba3106d1e363feaa6.mp3"
|
|
1381
|
+
},
|
|
1382
|
+
{
|
|
1383
|
+
id: "8abfa9ebd4c24a3399b68c3cdac8c7b5",
|
|
1384
|
+
name: "Freya",
|
|
1385
|
+
gender: "female",
|
|
1386
|
+
accent: "british",
|
|
1387
|
+
languages: ["en"],
|
|
1388
|
+
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.",
|
|
1389
|
+
previewUrl: "https://platform.r2.fish.audio/tasks/2026-08-25/07918705a56b42daaab149f65392cea7.mp3"
|
|
1390
|
+
},
|
|
1391
|
+
{
|
|
1392
|
+
id: "2f64e528a4f2406691925ab1e0ed5b7f",
|
|
1393
|
+
name: "Ananya",
|
|
1394
|
+
gender: "female",
|
|
1395
|
+
languages: ["hi"],
|
|
1396
|
+
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",
|
|
1397
|
+
previewUrl: "https://platform.r2.fish.audio/tasks/2026-08-25/a17a700668d8405f8316db120733145c.mp3"
|
|
1398
|
+
},
|
|
1399
|
+
{
|
|
1400
|
+
id: "cefd91a07b99468f9148e2580990082d",
|
|
1401
|
+
name: "Arjun",
|
|
1402
|
+
gender: "male",
|
|
1403
|
+
languages: ["hi"],
|
|
1404
|
+
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",
|
|
1405
|
+
previewUrl: "https://platform.r2.fish.audio/tasks/2026-08-24/a0c4a8eadb7841cdae8e5e68031a2dfb.mp3"
|
|
1406
|
+
},
|
|
1407
|
+
{
|
|
1408
|
+
id: "3f065d660212478a9c697fa965b4d098",
|
|
1409
|
+
name: "Carmen",
|
|
1410
|
+
gender: "female",
|
|
1411
|
+
languages: ["es"],
|
|
1412
|
+
description: "Una voz clara y segura para asistentes de voz, atenci\xF3n al cliente y presentaciones.",
|
|
1413
|
+
previewUrl: "https://platform.r2.fish.audio/tasks/2026-08-25/3421be48b6cb4c28bf829c25188e7e74.mp3"
|
|
1414
|
+
},
|
|
1415
|
+
{
|
|
1416
|
+
id: "ec0718028a2445fb9be8e8bfb56a0150",
|
|
1417
|
+
name: "Rodrigo",
|
|
1418
|
+
gender: "male",
|
|
1419
|
+
languages: ["es"],
|
|
1420
|
+
description: "Una voz clara y segura para asistentes de voz, atenci\xF3n al cliente y presentaciones.",
|
|
1421
|
+
previewUrl: "https://platform.r2.fish.audio/tasks/2026-08-25/5a77c32365604800ad5b4eb13c5585df.mp3"
|
|
1422
|
+
},
|
|
1423
|
+
{
|
|
1424
|
+
id: "10a3a20742114a4ea6dd441e7591850f",
|
|
1425
|
+
name: "Manon",
|
|
1426
|
+
gender: "female",
|
|
1427
|
+
languages: ["fr"],
|
|
1428
|
+
description: "Une voix claire et vive, adapt\xE9e aux assistants vocaux et au support client.",
|
|
1429
|
+
previewUrl: "https://platform.r2.fish.audio/tasks/2026-08-25/f7b75ee1fa524e48a8cefb003cf72952.mp3"
|
|
1430
|
+
},
|
|
1431
|
+
{
|
|
1432
|
+
id: "f69bca092b674168a8d02d61ca20943c",
|
|
1433
|
+
name: "Lucas",
|
|
1434
|
+
gender: "male",
|
|
1435
|
+
languages: ["fr"],
|
|
1436
|
+
description: "Une voix claire et assur\xE9e, adapt\xE9e aux assistants vocaux et au service client.",
|
|
1437
|
+
previewUrl: "https://platform.r2.fish.audio/tasks/2026-08-25/86fca3e56a014a3d8a11450854bb8311.mp3"
|
|
1438
|
+
},
|
|
1439
|
+
{
|
|
1440
|
+
id: "3937b44b604249f18b94913daef94043",
|
|
1441
|
+
name: "Lena",
|
|
1442
|
+
gender: "female",
|
|
1443
|
+
languages: ["de"],
|
|
1444
|
+
description: "Eine klare, souver\xE4ne Stimme f\xFCr Sprachassistenten, Kundenservice und Pr\xE4sentationen.",
|
|
1445
|
+
previewUrl: "https://platform.r2.fish.audio/tasks/2026-08-25/8659fec80b74442dab68a7755716a401.mp3"
|
|
1446
|
+
},
|
|
1447
|
+
{
|
|
1448
|
+
id: "c1e7cc926aa5469aa966c353094d4b0b",
|
|
1449
|
+
name: "Lukas",
|
|
1450
|
+
gender: "male",
|
|
1451
|
+
languages: ["de"],
|
|
1452
|
+
description: "Eine klare, souver\xE4ne Stimme f\xFCr Sprachassistenten, Kundenservice und Pr\xE4sentationen.",
|
|
1453
|
+
previewUrl: "https://platform.r2.fish.audio/tasks/2026-08-25/fb1d538126bb4fe38f8366250f6a2136.mp3"
|
|
1454
|
+
},
|
|
1455
|
+
{
|
|
1456
|
+
id: "b3c060cc41544a7da94a2c487647c902",
|
|
1457
|
+
name: "Let\xEDcia",
|
|
1458
|
+
gender: "female",
|
|
1459
|
+
accent: "brazilian",
|
|
1460
|
+
languages: ["pt"],
|
|
1461
|
+
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.",
|
|
1462
|
+
previewUrl: "https://platform.r2.fish.audio/tasks/2026-10-12/13c6c0d96b5c4069951f89868ee15360.mp3"
|
|
1463
|
+
},
|
|
1464
|
+
{
|
|
1465
|
+
id: "add7bcb8d3c8423bbfa152ca232aad6e",
|
|
1466
|
+
name: "Ricardo",
|
|
1467
|
+
gender: "male",
|
|
1468
|
+
accent: "brazilian",
|
|
1469
|
+
languages: ["pt"],
|
|
1470
|
+
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.",
|
|
1471
|
+
previewUrl: "https://platform.r2.fish.audio/tasks/2026-10-12/67475fc717ae45cbb38e647a2dd37fe1.mp3"
|
|
1472
|
+
},
|
|
1473
|
+
{
|
|
1474
|
+
id: "b2d9d8db057042688a5e318b8f405bc2",
|
|
1475
|
+
name: "Kyoko",
|
|
1476
|
+
gender: "female",
|
|
1477
|
+
languages: ["ja"],
|
|
1478
|
+
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",
|
|
1479
|
+
previewUrl: "https://platform.r2.fish.audio/task/993b3ccb52124341aff79580f1ce5a5a.mp3"
|
|
1480
|
+
},
|
|
1481
|
+
{
|
|
1482
|
+
id: "297a6fd278df47c3b9da9bfdf55ac89a",
|
|
1483
|
+
name: "Satoru",
|
|
1484
|
+
gender: "male",
|
|
1485
|
+
languages: ["ja"],
|
|
1486
|
+
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",
|
|
1487
|
+
previewUrl: "https://platform.r2.fish.audio/task/1b4712fb831f4c149eef6b0133157418.mp3"
|
|
1488
|
+
},
|
|
1489
|
+
{
|
|
1490
|
+
id: "0b14f34a13a94ed88fe6113193749bb0",
|
|
1491
|
+
name: "Fatima",
|
|
1492
|
+
gender: "female",
|
|
1493
|
+
languages: ["ar"],
|
|
1494
|
+
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.",
|
|
1495
|
+
previewUrl: "https://platform.r2.fish.audio/tasks/2026-08-24/93406e1b905a4c4994306e3421243390.mp3"
|
|
1496
|
+
},
|
|
1497
|
+
{
|
|
1498
|
+
id: "74c6aba5cbf94a15bbdc547ffce5cb38",
|
|
1499
|
+
name: "Yutong",
|
|
1500
|
+
gender: "female",
|
|
1501
|
+
languages: ["zh"],
|
|
1502
|
+
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",
|
|
1503
|
+
previewUrl: "https://platform.r2.fish.audio/tasks/2026-08-25/a48320f37fe44c4699549a1afc3778e8.mp3"
|
|
1504
|
+
}
|
|
1505
|
+
];
|
|
1506
|
+
|
|
1507
|
+
// src/catalog/speech-fish.ts
|
|
1508
|
+
var FISH_LOGO = "https://cdn.jsdelivr.net/npm/@lobehub/icons-static-png@1.97.0/light/fishaudio.png";
|
|
1509
|
+
var PER_CALL = { unit: "images" };
|
|
1510
|
+
var PER_REQUEST = { unit: "requests" };
|
|
1511
|
+
var TTS_MAX_CHARS = 1e4;
|
|
1512
|
+
function fishTts(id, label, providerModelId, description) {
|
|
1513
|
+
return {
|
|
1514
|
+
id,
|
|
1515
|
+
aliases: [],
|
|
1516
|
+
label,
|
|
1517
|
+
type: "speech",
|
|
1518
|
+
adapter: "fish",
|
|
1519
|
+
providerModelId,
|
|
1520
|
+
lifecycle: "active",
|
|
1521
|
+
exposure: [],
|
|
1522
|
+
constraints: {
|
|
1523
|
+
voices: FISH_VOICES,
|
|
1524
|
+
outputFormats: ["mp3", "wav"],
|
|
1525
|
+
supports: [],
|
|
1526
|
+
prompt: { referenceSyntax: "numbered", maxChars: TTS_MAX_CHARS }
|
|
1527
|
+
},
|
|
1528
|
+
defaults: { voice: "9a9cf47702da476aa4629e2506d4a857", outputFormat: "mp3" },
|
|
1529
|
+
usage: PER_CALL,
|
|
1530
|
+
pricingKind: "flat",
|
|
1531
|
+
display: { provider: "fishaudio", logo: FISH_LOGO, description },
|
|
1532
|
+
capabilities: { customVoices: true }
|
|
1533
|
+
};
|
|
1534
|
+
}
|
|
1535
|
+
var FISH_MODELS = [
|
|
1536
|
+
fishTts(
|
|
1537
|
+
"fish-s2.1-pro",
|
|
1538
|
+
"Fish Audio S2.1 Pro",
|
|
1539
|
+
"s2.1-pro",
|
|
1540
|
+
"Fish Audio S2.1 Pro text to speech in 83 languages, with [bracket] directions and your own cloned voices."
|
|
1541
|
+
),
|
|
1542
|
+
fishTts(
|
|
1543
|
+
"fish-s2.1-pro-free",
|
|
1544
|
+
"Fish Audio S2.1 Pro Free",
|
|
1545
|
+
"s2.1-pro-free",
|
|
1546
|
+
"Fish Audio S2.1 Pro on the free developer tier: the same voices at no cost, without latency or data-processing guarantees."
|
|
1547
|
+
),
|
|
1548
|
+
fishTts(
|
|
1549
|
+
"fish-drama-3",
|
|
1550
|
+
"Fish Audio Drama 3",
|
|
1551
|
+
"drama-3",
|
|
1552
|
+
"Fish Audio Drama 3 text to speech you direct like an actor: [directions], sounds, pauses and per-word tags."
|
|
1553
|
+
),
|
|
1554
|
+
{
|
|
1555
|
+
id: "fish-voice-clone",
|
|
1556
|
+
aliases: [],
|
|
1557
|
+
label: "Fish Audio Voice Clone",
|
|
1558
|
+
type: "voice-clone",
|
|
1559
|
+
adapter: "fish",
|
|
1560
|
+
providerModelId: "voice-clone",
|
|
1561
|
+
lifecycle: "active",
|
|
1562
|
+
exposure: [],
|
|
1563
|
+
constraints: {
|
|
1564
|
+
// POST /model takes 1-20 voice files; Fish advises 10 s or more of clean speech each.
|
|
1565
|
+
references: { audios: 20, audioFormats: ["wav", "mp3", "m4a", "opus"] },
|
|
1566
|
+
supports: []
|
|
1567
|
+
},
|
|
1568
|
+
defaults: {},
|
|
1569
|
+
usage: PER_REQUEST,
|
|
1570
|
+
pricingKind: "flat",
|
|
1571
|
+
display: {
|
|
1572
|
+
provider: "fishaudio",
|
|
1573
|
+
logo: FISH_LOGO,
|
|
1574
|
+
description: "Clone a private, reusable voice from one or more clean recordings, then speak with it on the Fish Audio speech models."
|
|
1575
|
+
},
|
|
1576
|
+
capabilities: {}
|
|
1577
|
+
},
|
|
1578
|
+
{
|
|
1579
|
+
id: "fish-voice-design-1",
|
|
1580
|
+
aliases: [],
|
|
1581
|
+
label: "Fish Audio Voice Design",
|
|
1582
|
+
type: "voice-design",
|
|
1583
|
+
adapter: "fish",
|
|
1584
|
+
providerModelId: "voice-design-1",
|
|
1585
|
+
lifecycle: "active",
|
|
1586
|
+
exposure: [],
|
|
1587
|
+
constraints: {
|
|
1588
|
+
voiceDesign: { maxCandidates: 4, previewMaxChars: 150 },
|
|
1589
|
+
supports: [],
|
|
1590
|
+
prompt: { referenceSyntax: "numbered", maxChars: 2e3 }
|
|
1591
|
+
},
|
|
1592
|
+
defaults: {},
|
|
1593
|
+
usage: PER_REQUEST,
|
|
1594
|
+
pricingKind: "flat",
|
|
1595
|
+
display: {
|
|
1596
|
+
provider: "fishaudio",
|
|
1597
|
+
logo: FISH_LOGO,
|
|
1598
|
+
description: "Describe a voice and get up to four short candidates; keep one as a cloned voice."
|
|
1599
|
+
},
|
|
1600
|
+
capabilities: {}
|
|
1601
|
+
}
|
|
1602
|
+
];
|
|
1603
|
+
|
|
1305
1604
|
// src/catalog/languages-audio.ts
|
|
1306
1605
|
var ELEVENLABS_V4_LANGUAGES = [
|
|
1307
1606
|
{ code: "af", name: "Afrikaans" },
|
|
@@ -3383,7 +3682,7 @@ var ELEVENLABS_LOGO = "https://cdn.jsdelivr.net/npm/@lobehub/icons-static-png@1.
|
|
|
3383
3682
|
var GOOGLE_LOGO = "https://d31vvfpk2ppdyq.cloudfront.net/assets/image/192485d2-a76a-4657-9c06-930b2c70badd_compressed.png";
|
|
3384
3683
|
var MINIMAX_LOGO = "https://d31vvfpk2ppdyq.cloudfront.net/assets/image/f4c2e906-28b7-4a4a-b491-df8fef7808ed.png";
|
|
3385
3684
|
var SYNC_LOGO = "https://cdn.jsdelivr.net/npm/@lobehub/icons-static-png@1.97.0/light/sync.png";
|
|
3386
|
-
var
|
|
3685
|
+
var PER_CALL2 = { unit: "images" };
|
|
3387
3686
|
var EXPOSURE4 = ["cowork", "space_builder"];
|
|
3388
3687
|
var LIPSYNC_AUDIO = { audioSeconds: { max: 30 } };
|
|
3389
3688
|
function elevenLabsTts(tts) {
|
|
@@ -3404,7 +3703,7 @@ function elevenLabsTts(tts) {
|
|
|
3404
3703
|
prompt: { referenceSyntax: "numbered", maxChars: tts.maxChars }
|
|
3405
3704
|
},
|
|
3406
3705
|
defaults: { voice: "Rachel", ...tts.formats && { outputFormat: "mp3_44100_128" } },
|
|
3407
|
-
usage:
|
|
3706
|
+
usage: PER_CALL2,
|
|
3408
3707
|
pricingKind: "flat",
|
|
3409
3708
|
display: { provider: "elevenlabs", logo: ELEVENLABS_LOGO, description: tts.description },
|
|
3410
3709
|
capabilities: {}
|
|
@@ -3428,7 +3727,7 @@ function geminiTts(id, label, description) {
|
|
|
3428
3727
|
prompt: { referenceSyntax: "numbered", maxChars: 8192 }
|
|
3429
3728
|
},
|
|
3430
3729
|
defaults: { voice: "Kore" },
|
|
3431
|
-
usage:
|
|
3730
|
+
usage: PER_CALL2,
|
|
3432
3731
|
pricingKind: "flat",
|
|
3433
3732
|
display: { provider: "google", logo: GOOGLE_LOGO, description },
|
|
3434
3733
|
capabilities: {}
|
|
@@ -3451,7 +3750,7 @@ function syncLipsync(id, label, providerModelId, syncModel, description) {
|
|
|
3451
3750
|
},
|
|
3452
3751
|
defaults: {},
|
|
3453
3752
|
...syncModel !== void 0 && { providerOptions: { fal: { model: syncModel } } },
|
|
3454
|
-
usage:
|
|
3753
|
+
usage: PER_CALL2,
|
|
3455
3754
|
pricingKind: "flat",
|
|
3456
3755
|
display: { provider: "sync", logo: SYNC_LOGO, description },
|
|
3457
3756
|
capabilities: { acceptsSourceVideo: true }
|
|
@@ -3512,7 +3811,7 @@ var MEDIA_NODE_MODELS = [
|
|
|
3512
3811
|
prompt: { referenceSyntax: "numbered", maxChars: 1e4 }
|
|
3513
3812
|
},
|
|
3514
3813
|
defaults: { voice: "Wise_Woman" },
|
|
3515
|
-
usage:
|
|
3814
|
+
usage: PER_CALL2,
|
|
3516
3815
|
pricingKind: "flat",
|
|
3517
3816
|
display: {
|
|
3518
3817
|
provider: "minimax",
|
|
@@ -3539,7 +3838,7 @@ var MEDIA_NODE_MODELS = [
|
|
|
3539
3838
|
supports: []
|
|
3540
3839
|
},
|
|
3541
3840
|
defaults: { voice: "Rachel", outputFormat: "mp3_44100_128" },
|
|
3542
|
-
usage:
|
|
3841
|
+
usage: PER_CALL2,
|
|
3543
3842
|
pricingKind: "flat",
|
|
3544
3843
|
display: {
|
|
3545
3844
|
provider: "elevenlabs",
|
|
@@ -3566,7 +3865,7 @@ var MEDIA_NODE_MODELS = [
|
|
|
3566
3865
|
supports: []
|
|
3567
3866
|
},
|
|
3568
3867
|
defaults: {},
|
|
3569
|
-
usage:
|
|
3868
|
+
usage: PER_CALL2,
|
|
3570
3869
|
pricingKind: "flat",
|
|
3571
3870
|
display: {
|
|
3572
3871
|
provider: "elevenlabs",
|
|
@@ -3613,7 +3912,7 @@ var MEDIA_NODE_MODELS = [
|
|
|
3613
3912
|
supports: []
|
|
3614
3913
|
},
|
|
3615
3914
|
defaults: {},
|
|
3616
|
-
usage:
|
|
3915
|
+
usage: PER_CALL2,
|
|
3617
3916
|
pricingKind: "flat",
|
|
3618
3917
|
display: {
|
|
3619
3918
|
provider: "veed",
|
|
@@ -3665,7 +3964,7 @@ var MEDIA_NODE_MODELS = [
|
|
|
3665
3964
|
supports: []
|
|
3666
3965
|
},
|
|
3667
3966
|
defaults: {},
|
|
3668
|
-
usage:
|
|
3967
|
+
usage: PER_CALL2,
|
|
3669
3968
|
pricingKind: "flat",
|
|
3670
3969
|
display: {
|
|
3671
3970
|
provider: "elevenlabs",
|
|
@@ -3674,7 +3973,8 @@ var MEDIA_NODE_MODELS = [
|
|
|
3674
3973
|
},
|
|
3675
3974
|
capabilities: {}
|
|
3676
3975
|
},
|
|
3677
|
-
SAM_3_IMAGE
|
|
3976
|
+
SAM_3_IMAGE,
|
|
3977
|
+
...FISH_MODELS
|
|
3678
3978
|
];
|
|
3679
3979
|
|
|
3680
3980
|
// src/catalog/model3d.ts
|
|
@@ -7315,7 +7615,8 @@ var ADAPTER_IDS = [
|
|
|
7315
7615
|
"prodia",
|
|
7316
7616
|
"higgsfield",
|
|
7317
7617
|
"xai-video",
|
|
7318
|
-
"luma"
|
|
7618
|
+
"luma",
|
|
7619
|
+
"fish"
|
|
7319
7620
|
];
|
|
7320
7621
|
var ModelTypeSchema = import_zod3.z.enum([
|
|
7321
7622
|
"text",
|
|
@@ -7332,6 +7633,8 @@ var ModelTypeSchema = import_zod3.z.enum([
|
|
|
7332
7633
|
"dubbing",
|
|
7333
7634
|
"lipsync",
|
|
7334
7635
|
"transcription",
|
|
7636
|
+
"voice-clone",
|
|
7637
|
+
"voice-design",
|
|
7335
7638
|
"model3d"
|
|
7336
7639
|
]);
|
|
7337
7640
|
var LifecycleSchema = import_zod3.z.enum(["active", "deprecated", "retired"]);
|
|
@@ -7507,6 +7810,10 @@ var ModelConstraintsSchema = import_zod3.z.looseObject({
|
|
|
7507
7810
|
supports: import_zod3.z.array(SupportedOptionSchema).optional(),
|
|
7508
7811
|
ratioIgnoredWith: import_zod3.z.array(import_zod3.z.enum(["firstFrame", "sourceVideo"])).optional(),
|
|
7509
7812
|
voices: import_zod3.z.array(VoiceOptionSchema).optional(),
|
|
7813
|
+
voiceDesign: import_zod3.z.object({
|
|
7814
|
+
maxCandidates: import_zod3.z.number().int().positive(),
|
|
7815
|
+
previewMaxChars: import_zod3.z.number().int().positive()
|
|
7816
|
+
}).optional(),
|
|
7510
7817
|
languages: import_zod3.z.array(LanguageOptionSchema).optional(),
|
|
7511
7818
|
outputFormats: import_zod3.z.array(import_zod3.z.string().min(1)).min(1).optional(),
|
|
7512
7819
|
sourceVideoSeconds: VideoSecondsLimitsSchema.omit({ total: true }).extend({ inReferenceTotal: import_zod3.z.boolean().optional() }).optional(),
|
|
@@ -7605,6 +7912,7 @@ var CatalogDisplaySchema = import_zod3.z.looseObject({
|
|
|
7605
7912
|
});
|
|
7606
7913
|
var CatalogCapabilitiesSchema = import_zod3.z.looseObject({
|
|
7607
7914
|
acceptsSourceVideo: import_zod3.z.boolean().optional(),
|
|
7915
|
+
customVoices: import_zod3.z.boolean().optional(),
|
|
7608
7916
|
videoEdit: import_zod3.z.boolean().optional(),
|
|
7609
7917
|
framesExcludeReferences: import_zod3.z.boolean().optional(),
|
|
7610
7918
|
sourceVideoExcludesFrames: import_zod3.z.boolean().optional(),
|
|
@@ -9483,8 +9791,17 @@ function resolveSpeechOptions(model, request, onUnsupported = "error") {
|
|
|
9483
9791
|
const warnings = [];
|
|
9484
9792
|
checkScript(model, request.text);
|
|
9485
9793
|
const resolved = { model: model.id, text: request.text };
|
|
9486
|
-
const
|
|
9487
|
-
if (
|
|
9794
|
+
const customVoice = request.customVoice?.trim();
|
|
9795
|
+
if (customVoice) {
|
|
9796
|
+
if (model.capabilities?.customVoices !== true) {
|
|
9797
|
+
throw unsupported3(model, "does not take a custom voice");
|
|
9798
|
+
}
|
|
9799
|
+
if (request.voice?.trim()) throw unsupported3(model, "takes voice or customVoice, not both");
|
|
9800
|
+
resolved.customVoice = customVoice;
|
|
9801
|
+
} else {
|
|
9802
|
+
const voice = resolveVoice(model, request.voice, onUnsupported, warnings);
|
|
9803
|
+
if (voice !== void 0) resolved.voice = voice;
|
|
9804
|
+
}
|
|
9488
9805
|
const language = resolveListed(
|
|
9489
9806
|
model,
|
|
9490
9807
|
{
|
|
@@ -9606,6 +9923,106 @@ function resolveTranscriptionOptions(model, request, onUnsupported = "error") {
|
|
|
9606
9923
|
return { request: resolved, warnings };
|
|
9607
9924
|
}
|
|
9608
9925
|
|
|
9926
|
+
// src/resolve-voice.ts
|
|
9927
|
+
function refuse(model, code, message) {
|
|
9928
|
+
return new ModelKitError({ code, message: `${model.label} ${message}`, model: model.id });
|
|
9929
|
+
}
|
|
9930
|
+
function checkSample(model, sample, index) {
|
|
9931
|
+
const limits = model.constraints.references?.audioSeconds;
|
|
9932
|
+
const length = sample.audio.durationSeconds;
|
|
9933
|
+
if (limits === void 0 || length === void 0) return;
|
|
9934
|
+
if (limits.min !== void 0 && length < limits.min || limits.max !== void 0 && length > limits.max) {
|
|
9935
|
+
const range2 = `${limits.min ?? 0}-${limits.max ?? "any"} s`;
|
|
9936
|
+
throw refuse(
|
|
9937
|
+
model,
|
|
9938
|
+
"unsupported_option",
|
|
9939
|
+
`takes samples of ${range2}; sample ${index + 1} is ${length} s`
|
|
9940
|
+
);
|
|
9941
|
+
}
|
|
9942
|
+
}
|
|
9943
|
+
function resolveVoiceCloneOptions(model, request) {
|
|
9944
|
+
const title = request.title.trim();
|
|
9945
|
+
if (!title) throw refuse(model, "missing_input", "needs a title");
|
|
9946
|
+
if (request.samples.length === 0) throw refuse(model, "missing_input", "needs a voice sample");
|
|
9947
|
+
const max = model.constraints.references?.audios;
|
|
9948
|
+
if (max !== void 0 && request.samples.length > max) {
|
|
9949
|
+
throw refuse(
|
|
9950
|
+
model,
|
|
9951
|
+
"unsupported_option",
|
|
9952
|
+
`takes up to ${max} samples; got ${request.samples.length}`
|
|
9953
|
+
);
|
|
9954
|
+
}
|
|
9955
|
+
request.samples.forEach((sample, index) => checkSample(model, sample, index));
|
|
9956
|
+
const total = model.constraints.references?.audioSeconds?.total;
|
|
9957
|
+
const seconds3 = request.samples.reduce((sum, s) => sum + (s.audio.durationSeconds ?? 0), 0);
|
|
9958
|
+
if (total !== void 0 && seconds3 > total) {
|
|
9959
|
+
throw refuse(
|
|
9960
|
+
model,
|
|
9961
|
+
"unsupported_option",
|
|
9962
|
+
`takes up to ${total} s of samples; got ${seconds3} s`
|
|
9963
|
+
);
|
|
9964
|
+
}
|
|
9965
|
+
for (const field of ["transcript", "designSignature"]) {
|
|
9966
|
+
const set = request.samples.filter((s) => s[field]?.trim()).length;
|
|
9967
|
+
if (set !== 0 && set !== request.samples.length) {
|
|
9968
|
+
throw refuse(model, "missing_input", `needs ${field} on every sample or on none`);
|
|
9969
|
+
}
|
|
9970
|
+
}
|
|
9971
|
+
return { request: { ...request, model: model.id, title }, warnings: [] };
|
|
9972
|
+
}
|
|
9973
|
+
function resolveVoiceDesignOptions(model, request, onUnsupported = "error") {
|
|
9974
|
+
const warnings = [];
|
|
9975
|
+
const prompt = request.prompt.trim();
|
|
9976
|
+
if (!prompt) throw refuse(model, "missing_input", "needs a voice description");
|
|
9977
|
+
const maxChars = promptConventions(model).maxChars;
|
|
9978
|
+
if (maxChars !== void 0 && prompt.length > maxChars) {
|
|
9979
|
+
throw refuse(
|
|
9980
|
+
model,
|
|
9981
|
+
"unsupported_option",
|
|
9982
|
+
`takes up to ${maxChars} characters; got ${prompt.length}`
|
|
9983
|
+
);
|
|
9984
|
+
}
|
|
9985
|
+
const limits = model.constraints.voiceDesign;
|
|
9986
|
+
const resolved = { model: model.id, prompt };
|
|
9987
|
+
const preview2 = request.previewText?.trim();
|
|
9988
|
+
if (preview2) {
|
|
9989
|
+
if (limits !== void 0 && preview2.length > limits.previewMaxChars) {
|
|
9990
|
+
throw refuse(
|
|
9991
|
+
model,
|
|
9992
|
+
"unsupported_option",
|
|
9993
|
+
`takes a preview line of up to ${limits.previewMaxChars} characters; got ${preview2.length}`
|
|
9994
|
+
);
|
|
9995
|
+
}
|
|
9996
|
+
resolved.previewText = preview2;
|
|
9997
|
+
}
|
|
9998
|
+
const language = request.language?.trim();
|
|
9999
|
+
if (language) resolved.language = language;
|
|
10000
|
+
if (request.seed !== void 0) resolved.seed = request.seed;
|
|
10001
|
+
const count2 = request.count ?? model.defaults.count;
|
|
10002
|
+
if (count2 !== void 0) {
|
|
10003
|
+
const cap = limits?.maxCandidates ?? count2;
|
|
10004
|
+
if (!Number.isInteger(count2) || count2 < 1 || count2 > cap) {
|
|
10005
|
+
if (onUnsupported === "error" || !Number.isInteger(count2)) {
|
|
10006
|
+
throw refuse(
|
|
10007
|
+
model,
|
|
10008
|
+
"unsupported_option",
|
|
10009
|
+
`returns 1-${cap} candidates; got ${count2}`
|
|
10010
|
+
);
|
|
10011
|
+
}
|
|
10012
|
+
const to = Math.min(Math.max(count2, 1), cap);
|
|
10013
|
+
warnings.push({
|
|
10014
|
+
code: "option_ignored",
|
|
10015
|
+
field: "count",
|
|
10016
|
+
from: String(count2),
|
|
10017
|
+
to: String(to),
|
|
10018
|
+
message: `count ${count2} replaced by ${to} for ${model.id}`
|
|
10019
|
+
});
|
|
10020
|
+
resolved.count = to;
|
|
10021
|
+
} else resolved.count = count2;
|
|
10022
|
+
}
|
|
10023
|
+
return { request: resolved, warnings };
|
|
10024
|
+
}
|
|
10025
|
+
|
|
9609
10026
|
// src/resolve-call.ts
|
|
9610
10027
|
var MEDIA_METHODS = {
|
|
9611
10028
|
text: [],
|
|
@@ -9621,6 +10038,9 @@ var MEDIA_METHODS = {
|
|
|
9621
10038
|
lipsync: ["lipsync"],
|
|
9622
10039
|
dubbing: ["dub"],
|
|
9623
10040
|
transcription: ["transcribe"],
|
|
10041
|
+
// deleteVoice runs on the voice-clone row too; it checks with assertMethod("createVoice").
|
|
10042
|
+
"voice-clone": ["createVoice"],
|
|
10043
|
+
"voice-design": ["designVoice"],
|
|
9624
10044
|
"video-tool": ["submitVideoTool"],
|
|
9625
10045
|
// 3D jobs run through submitModel3d, which checks the row type itself; their preflight
|
|
9626
10046
|
// is model3dInputRefusal, not resolveOptions.
|
|
@@ -9635,6 +10055,8 @@ var METHOD_NOUN = {
|
|
|
9635
10055
|
lipsync: "a lipsync model",
|
|
9636
10056
|
dub: "a dubbing model",
|
|
9637
10057
|
transcribe: "a transcription model",
|
|
10058
|
+
createVoice: "a voice cloning model",
|
|
10059
|
+
designVoice: "a voice design model",
|
|
9638
10060
|
submitVideoTool: "a video tool"
|
|
9639
10061
|
};
|
|
9640
10062
|
function assertMethod(model, method) {
|
|
@@ -9677,6 +10099,14 @@ function resolveTranscriptionCall(model, request, onUnsupported = "error") {
|
|
|
9677
10099
|
assertMethod(model, "transcribe");
|
|
9678
10100
|
return resolveTranscriptionOptions(model, request, onUnsupported);
|
|
9679
10101
|
}
|
|
10102
|
+
function resolveVoiceCloneCall(model, request) {
|
|
10103
|
+
assertMethod(model, "createVoice");
|
|
10104
|
+
return resolveVoiceCloneOptions(model, request);
|
|
10105
|
+
}
|
|
10106
|
+
function resolveVoiceDesignCall(model, request, onUnsupported = "error") {
|
|
10107
|
+
assertMethod(model, "designVoice");
|
|
10108
|
+
return resolveVoiceDesignOptions(model, request, onUnsupported);
|
|
10109
|
+
}
|
|
9680
10110
|
function resolveVideoToolCall(model, request) {
|
|
9681
10111
|
assertMethod(model, "submitVideoTool");
|
|
9682
10112
|
const plan = planVideoTool(model, request);
|
|
@@ -9723,6 +10153,13 @@ function resolveOptions(model, call, onUnsupported = "error") {
|
|
|
9723
10153
|
method: call.method,
|
|
9724
10154
|
...resolveTranscriptionCall(model, call.request, onUnsupported)
|
|
9725
10155
|
};
|
|
10156
|
+
case "createVoice":
|
|
10157
|
+
return { method: call.method, ...resolveVoiceCloneCall(model, call.request) };
|
|
10158
|
+
case "designVoice":
|
|
10159
|
+
return {
|
|
10160
|
+
method: call.method,
|
|
10161
|
+
...resolveVoiceDesignCall(model, call.request, onUnsupported)
|
|
10162
|
+
};
|
|
9726
10163
|
case "submitVideoTool":
|
|
9727
10164
|
return { method: call.method, ...resolveVideoToolCall(model, call.request) };
|
|
9728
10165
|
}
|