@brotu/ai 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/README.md +57 -12
- package/dist/adapters/brotu.adapter.d.ts +46 -0
- package/dist/adapters/brotu.adapter.d.ts.map +1 -0
- package/dist/catalog.cjs +72 -16
- package/dist/catalog.d.ts +23 -1
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +7 -1
- package/dist/{chunk-PL534BFN.js → chunk-3MC3RDAJ.js} +69 -16
- package/dist/client.d.ts +14 -3
- package/dist/client.d.ts.map +1 -1
- package/dist/helpers/result.d.ts +1 -1
- package/dist/helpers/result.d.ts.map +1 -1
- package/dist/index.cjs +504 -76
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +409 -32
- package/dist/lib/hooks.d.ts +32 -0
- package/dist/lib/hooks.d.ts.map +1 -0
- package/dist/types.d.ts +23 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -30,9 +30,12 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
+
BROTU_SUPPORTED_CATEGORIES: () => BROTU_SUPPORTED_CATEGORIES,
|
|
33
34
|
BYTEPLUS_CATALOG: () => BYTEPLUS_CATALOG,
|
|
34
35
|
BYTEPLUS_MODELS: () => BYTEPLUS_MODELS,
|
|
36
|
+
BrotuAdapter: () => BrotuAdapter,
|
|
35
37
|
BytePlusAdapter: () => BytePlusAdapter,
|
|
38
|
+
DEFAULT_BROTU_API_URL: () => DEFAULT_BROTU_API_URL,
|
|
36
39
|
ELEVENLABS_CATALOG: () => ELEVENLABS_CATALOG,
|
|
37
40
|
ELEVENLABS_MODELS: () => ELEVENLABS_MODELS,
|
|
38
41
|
ELEVENLABS_OUTPUT_FORMATS: () => ELEVENLABS_OUTPUT_FORMATS,
|
|
@@ -59,9 +62,10 @@ __export(index_exports, {
|
|
|
59
62
|
QWEN_TEXT_MODELS: () => QWEN_TEXT_MODELS,
|
|
60
63
|
QWEN_VIDEO_MODELS: () => QWEN_VIDEO_MODELS,
|
|
61
64
|
QwenAdapter: () => QwenAdapter,
|
|
62
|
-
|
|
65
|
+
brotu: () => brotu,
|
|
63
66
|
createS3Storage: () => createS3Storage,
|
|
64
67
|
deliverWebhook: () => deliverWebhook,
|
|
68
|
+
describeModels: () => describeModels,
|
|
65
69
|
fail: () => fail,
|
|
66
70
|
getAvailableModels: () => getAvailableModels,
|
|
67
71
|
getModel: () => getModel,
|
|
@@ -78,34 +82,6 @@ __export(index_exports, {
|
|
|
78
82
|
});
|
|
79
83
|
module.exports = __toCommonJS(index_exports);
|
|
80
84
|
|
|
81
|
-
// src/lib/jobs.ts
|
|
82
|
-
var import_node_async_hooks = require("async_hooks");
|
|
83
|
-
var PendingJob = class extends Error {
|
|
84
|
-
taskId;
|
|
85
|
-
pollEndpoint;
|
|
86
|
-
constructor(taskId, pollEndpoint) {
|
|
87
|
-
super(`Job ${taskId} was submitted and is still running.`);
|
|
88
|
-
this.name = "PendingJob";
|
|
89
|
-
this.taskId = taskId;
|
|
90
|
-
this.pollEndpoint = pollEndpoint;
|
|
91
|
-
}
|
|
92
|
-
};
|
|
93
|
-
function isPendingJob(error) {
|
|
94
|
-
return error instanceof PendingJob;
|
|
95
|
-
}
|
|
96
|
-
var submitMode = new import_node_async_hooks.AsyncLocalStorage();
|
|
97
|
-
function runInSubmitMode(fn) {
|
|
98
|
-
return submitMode.run(true, fn);
|
|
99
|
-
}
|
|
100
|
-
function isSubmitMode() {
|
|
101
|
-
return submitMode.getStore() === true;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// src/ports/content-generator.port.ts
|
|
105
|
-
function expiresInHours(hours) {
|
|
106
|
-
return new Date(Date.now() + hours * 36e5).toISOString();
|
|
107
|
-
}
|
|
108
|
-
|
|
109
85
|
// src/providers/byteplus.models.ts
|
|
110
86
|
var FPS = 24;
|
|
111
87
|
var PIXELS = {
|
|
@@ -1267,8 +1243,16 @@ var PROVIDER_BASE_URLS = {
|
|
|
1267
1243
|
elevenlabs: "https://api.elevenlabs.io",
|
|
1268
1244
|
google: "https://generativelanguage.googleapis.com",
|
|
1269
1245
|
openai: "https://api.openai.com",
|
|
1270
|
-
qwen: "https://dashscope-intl.aliyuncs.com"
|
|
1246
|
+
qwen: "https://dashscope-intl.aliyuncs.com",
|
|
1247
|
+
brotu: "https://api.brotu.app"
|
|
1271
1248
|
};
|
|
1249
|
+
var DEFAULT_BROTU_API_URL = PROVIDER_BASE_URLS.brotu;
|
|
1250
|
+
function vendorProviders(options) {
|
|
1251
|
+
return options.providers ?? {};
|
|
1252
|
+
}
|
|
1253
|
+
function brotuApiKey(options) {
|
|
1254
|
+
return options.apiKey?.trim() || void 0;
|
|
1255
|
+
}
|
|
1272
1256
|
var BUILT_IN = [
|
|
1273
1257
|
...KLING_CATALOG,
|
|
1274
1258
|
...KLING_AUDIO_CATALOG,
|
|
@@ -1324,25 +1308,95 @@ function resolveProvider(modelId, options) {
|
|
|
1324
1308
|
`Model "${modelId}" declares no provider, so nothing knows how to run it.`
|
|
1325
1309
|
);
|
|
1326
1310
|
}
|
|
1327
|
-
const configured = options
|
|
1328
|
-
if (
|
|
1329
|
-
const
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1311
|
+
const configured = vendorProviders(options)[id];
|
|
1312
|
+
if (configured) {
|
|
1313
|
+
const baseUrl = configured.baseUrl ?? PROVIDER_BASE_URLS[id];
|
|
1314
|
+
if (!baseUrl) {
|
|
1315
|
+
throw new Error(
|
|
1316
|
+
`Provider "${id}" has no known base URL \u2014 pass providers.${id}.baseUrl.`
|
|
1317
|
+
);
|
|
1318
|
+
}
|
|
1319
|
+
return { id, apiKey: configured.apiKey, baseUrl: baseUrl.replace(/\/$/, "") };
|
|
1333
1320
|
}
|
|
1334
|
-
const
|
|
1335
|
-
if (
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1321
|
+
const platformKey = brotuApiKey(options);
|
|
1322
|
+
if (platformKey) {
|
|
1323
|
+
return {
|
|
1324
|
+
id: "brotu",
|
|
1325
|
+
apiKey: platformKey,
|
|
1326
|
+
baseUrl: (options.apiUrl ?? DEFAULT_BROTU_API_URL).replace(/\/$/, "")
|
|
1327
|
+
};
|
|
1339
1328
|
}
|
|
1340
|
-
|
|
1329
|
+
const owned = Object.keys(vendorProviders(options)).join(", ") || "none";
|
|
1330
|
+
throw new Error(
|
|
1331
|
+
`Model "${modelId}" runs on "${id}", but no API key was given for it (configured: ${owned}). Pass a Brotu key to generate on the platform.`
|
|
1332
|
+
);
|
|
1333
|
+
}
|
|
1334
|
+
var BROTU_SUPPORTED_CATEGORIES = ["image", "video"];
|
|
1335
|
+
function describeModels(options) {
|
|
1336
|
+
const vendors = vendorProviders(options);
|
|
1337
|
+
const platformKey = brotuApiKey(options);
|
|
1338
|
+
return catalog.map((model) => {
|
|
1339
|
+
const provider = model.provider;
|
|
1340
|
+
if (!provider) {
|
|
1341
|
+
return {
|
|
1342
|
+
model,
|
|
1343
|
+
runsOn: "(none)",
|
|
1344
|
+
runnable: false,
|
|
1345
|
+
reason: "The model declares no provider."
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
if (provider in vendors) {
|
|
1349
|
+
return { model, runsOn: provider, runnable: true };
|
|
1350
|
+
}
|
|
1351
|
+
if (platformKey) {
|
|
1352
|
+
if (BROTU_SUPPORTED_CATEGORIES.includes(model.category)) {
|
|
1353
|
+
return { model, runsOn: "brotu", runnable: true };
|
|
1354
|
+
}
|
|
1355
|
+
return {
|
|
1356
|
+
model,
|
|
1357
|
+
runsOn: provider,
|
|
1358
|
+
runnable: false,
|
|
1359
|
+
reason: `Brotu generates ${BROTU_SUPPORTED_CATEGORIES.join(" and ")} only. Pass providers.${provider} to run this one.`
|
|
1360
|
+
};
|
|
1361
|
+
}
|
|
1362
|
+
return {
|
|
1363
|
+
model,
|
|
1364
|
+
runsOn: provider,
|
|
1365
|
+
runnable: false,
|
|
1366
|
+
reason: `No key for "${provider}". Pass a Brotu key or providers.${provider}.`
|
|
1367
|
+
};
|
|
1368
|
+
});
|
|
1341
1369
|
}
|
|
1342
1370
|
function getAvailableModels(options) {
|
|
1343
|
-
return
|
|
1344
|
-
|
|
1345
|
-
|
|
1371
|
+
return describeModels(options).filter((entry) => entry.runnable).map((entry) => entry.model);
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
// src/lib/jobs.ts
|
|
1375
|
+
var import_node_async_hooks = require("async_hooks");
|
|
1376
|
+
var PendingJob = class extends Error {
|
|
1377
|
+
taskId;
|
|
1378
|
+
pollEndpoint;
|
|
1379
|
+
constructor(taskId, pollEndpoint) {
|
|
1380
|
+
super(`Job ${taskId} was submitted and is still running.`);
|
|
1381
|
+
this.name = "PendingJob";
|
|
1382
|
+
this.taskId = taskId;
|
|
1383
|
+
this.pollEndpoint = pollEndpoint;
|
|
1384
|
+
}
|
|
1385
|
+
};
|
|
1386
|
+
function isPendingJob(error) {
|
|
1387
|
+
return error instanceof PendingJob;
|
|
1388
|
+
}
|
|
1389
|
+
var submitMode = new import_node_async_hooks.AsyncLocalStorage();
|
|
1390
|
+
function runInSubmitMode(fn) {
|
|
1391
|
+
return submitMode.run(true, fn);
|
|
1392
|
+
}
|
|
1393
|
+
function isSubmitMode() {
|
|
1394
|
+
return submitMode.getStore() === true;
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
// src/ports/content-generator.port.ts
|
|
1398
|
+
function expiresInHours(hours) {
|
|
1399
|
+
return new Date(Date.now() + hours * 36e5).toISOString();
|
|
1346
1400
|
}
|
|
1347
1401
|
|
|
1348
1402
|
// src/adapters/estimate.ts
|
|
@@ -1371,12 +1425,316 @@ function estimateFor(provider, type, params, defaults) {
|
|
|
1371
1425
|
};
|
|
1372
1426
|
}
|
|
1373
1427
|
|
|
1428
|
+
// src/adapters/brotu.adapter.ts
|
|
1429
|
+
var DEFAULT_API_URL = "https://api.brotu.app";
|
|
1430
|
+
var POLL_INTERVAL_MS = 3e3;
|
|
1431
|
+
var DEFAULT_MAX_POLL_ATTEMPTS = 400;
|
|
1432
|
+
var PLATFORM_MODEL_IDS = {
|
|
1433
|
+
"kling/v2-6": "kling-2.6",
|
|
1434
|
+
"kling/v3": "kling-3.0/video",
|
|
1435
|
+
"dreamina-seedance-2-5-260628": "bytedance/seedance-2-5",
|
|
1436
|
+
"dreamina-seedance-2-0-260128": "bytedance/seedance-2",
|
|
1437
|
+
"dreamina-seedance-2-0-fast-260128": "bytedance/seedance-2-fast",
|
|
1438
|
+
"gpt-image-1.5": "gpt-image/1.5"
|
|
1439
|
+
};
|
|
1440
|
+
var UNSUPPORTED_VIA_CREDITS = "Speech and text generate on the vendor. Pass that provider's key.";
|
|
1441
|
+
var BrotuAdapter = class {
|
|
1442
|
+
constructor(opts) {
|
|
1443
|
+
this.opts = opts;
|
|
1444
|
+
}
|
|
1445
|
+
opts;
|
|
1446
|
+
providerName = "brotu";
|
|
1447
|
+
supportedTypes = BROTU_SUPPORTED_CATEGORIES;
|
|
1448
|
+
workspaceIdCache;
|
|
1449
|
+
get origin() {
|
|
1450
|
+
return (this.opts.apiUrl ?? DEFAULT_API_URL).replace(/\/$/, "");
|
|
1451
|
+
}
|
|
1452
|
+
headers() {
|
|
1453
|
+
return {
|
|
1454
|
+
Authorization: `Bearer ${this.opts.apiKey}`,
|
|
1455
|
+
"Content-Type": "application/json"
|
|
1456
|
+
};
|
|
1457
|
+
}
|
|
1458
|
+
async workspaceId() {
|
|
1459
|
+
if (this.opts.workspaceId) return this.opts.workspaceId;
|
|
1460
|
+
if (this.workspaceIdCache) return this.workspaceIdCache;
|
|
1461
|
+
const payload = await this.request("/api/v1/studio/default-workspace");
|
|
1462
|
+
const id = payload.workspaceId?.trim();
|
|
1463
|
+
if (!id) {
|
|
1464
|
+
throw new Error(
|
|
1465
|
+
"This Brotu account has no workspace. Open https://brotu.app and create one."
|
|
1466
|
+
);
|
|
1467
|
+
}
|
|
1468
|
+
this.workspaceIdCache = id;
|
|
1469
|
+
return id;
|
|
1470
|
+
}
|
|
1471
|
+
studioPath(path, workspaceId) {
|
|
1472
|
+
const joiner = path.includes("?") ? "&" : "?";
|
|
1473
|
+
return `${path}${joiner}workspaceId=${encodeURIComponent(workspaceId)}`;
|
|
1474
|
+
}
|
|
1475
|
+
async request(path, init) {
|
|
1476
|
+
const response = await fetch(`${this.origin}${path}`, {
|
|
1477
|
+
...init,
|
|
1478
|
+
headers: { ...this.headers(), ...init?.headers }
|
|
1479
|
+
});
|
|
1480
|
+
const text = await response.text();
|
|
1481
|
+
let body = void 0;
|
|
1482
|
+
if (text) {
|
|
1483
|
+
try {
|
|
1484
|
+
body = JSON.parse(text);
|
|
1485
|
+
} catch {
|
|
1486
|
+
body = text;
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
if (!response.ok) {
|
|
1490
|
+
const parsed = body;
|
|
1491
|
+
const message = parsed && typeof parsed === "object" ? parsed.error?.message || parsed.message || text : text;
|
|
1492
|
+
throw new Error(message || `Brotu API ${response.status}`);
|
|
1493
|
+
}
|
|
1494
|
+
return body;
|
|
1495
|
+
}
|
|
1496
|
+
platformModel(modelId) {
|
|
1497
|
+
return PLATFORM_MODEL_IDS[modelId] ?? modelId;
|
|
1498
|
+
}
|
|
1499
|
+
async startGeneration(kind, params) {
|
|
1500
|
+
const modelId = params.model ?? "";
|
|
1501
|
+
const workspaceId = await this.workspaceId();
|
|
1502
|
+
const path = kind === "video" ? "/api/v1/studio/videos" : "/api/v1/studio/images";
|
|
1503
|
+
const body = kind === "video" ? this.videoBody(params, modelId) : this.imageBody(params, modelId);
|
|
1504
|
+
const result = await this.request(
|
|
1505
|
+
this.studioPath(path, workspaceId),
|
|
1506
|
+
{ method: "POST", body: JSON.stringify(body) }
|
|
1507
|
+
);
|
|
1508
|
+
const generationId = result.generationId?.trim();
|
|
1509
|
+
if (!generationId) {
|
|
1510
|
+
throw new Error("Brotu accepted the request but returned no generation id.");
|
|
1511
|
+
}
|
|
1512
|
+
return { generationId, workspaceId };
|
|
1513
|
+
}
|
|
1514
|
+
imageBody(params, modelId) {
|
|
1515
|
+
return {
|
|
1516
|
+
prompt: params.prompt,
|
|
1517
|
+
model: this.platformModel(modelId),
|
|
1518
|
+
aspectRatio: params.aspectRatio,
|
|
1519
|
+
resolution: params.resolution,
|
|
1520
|
+
negativePrompt: params.negativePrompt,
|
|
1521
|
+
outputFormat: params.outputFormat,
|
|
1522
|
+
seed: params.seed,
|
|
1523
|
+
referenceImages: params.referenceImages
|
|
1524
|
+
};
|
|
1525
|
+
}
|
|
1526
|
+
videoBody(params, modelId) {
|
|
1527
|
+
return {
|
|
1528
|
+
prompt: params.prompt,
|
|
1529
|
+
model: this.platformModel(modelId),
|
|
1530
|
+
duration: params.duration,
|
|
1531
|
+
resolution: params.resolution,
|
|
1532
|
+
aspectRatio: params.aspectRatio,
|
|
1533
|
+
mode: params.mode,
|
|
1534
|
+
withAudio: params.withAudio,
|
|
1535
|
+
seed: params.seed,
|
|
1536
|
+
imageUrl: params.imageUrl,
|
|
1537
|
+
imageUrls: params.imageUrls,
|
|
1538
|
+
videoUrl: params.videoUrl,
|
|
1539
|
+
videoUrls: params.videoUrls,
|
|
1540
|
+
referenceImages: params.referenceImages
|
|
1541
|
+
};
|
|
1542
|
+
}
|
|
1543
|
+
async run(kind, params) {
|
|
1544
|
+
const startedAt = Date.now();
|
|
1545
|
+
const modelId = params.model ?? "(none)";
|
|
1546
|
+
const failure = (error) => ({
|
|
1547
|
+
success: false,
|
|
1548
|
+
outputs: [],
|
|
1549
|
+
creditsUsed: 0,
|
|
1550
|
+
provider: this.providerName,
|
|
1551
|
+
model: modelId,
|
|
1552
|
+
processingTimeMs: Date.now() - startedAt,
|
|
1553
|
+
error
|
|
1554
|
+
});
|
|
1555
|
+
let submitted;
|
|
1556
|
+
try {
|
|
1557
|
+
submitted = await this.startGeneration(kind, params);
|
|
1558
|
+
} catch (error) {
|
|
1559
|
+
return failure(error instanceof Error ? error.message : String(error));
|
|
1560
|
+
}
|
|
1561
|
+
const pollEndpoint = this.studioPath(
|
|
1562
|
+
`/api/v1/studio/generations/${submitted.generationId}`,
|
|
1563
|
+
submitted.workspaceId
|
|
1564
|
+
);
|
|
1565
|
+
if (isSubmitMode()) {
|
|
1566
|
+
throw new PendingJob(submitted.generationId, pollEndpoint);
|
|
1567
|
+
}
|
|
1568
|
+
const job = {
|
|
1569
|
+
id: submitted.generationId,
|
|
1570
|
+
provider: this.providerName,
|
|
1571
|
+
model: modelId,
|
|
1572
|
+
kind,
|
|
1573
|
+
pollEndpoint,
|
|
1574
|
+
params,
|
|
1575
|
+
submittedAt: new Date(startedAt).toISOString()
|
|
1576
|
+
};
|
|
1577
|
+
const maxAttempts = this.opts.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS;
|
|
1578
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
1579
|
+
const snapshot = await this.completeJob(job);
|
|
1580
|
+
if (snapshot.status === "failed") {
|
|
1581
|
+
return failure(snapshot.error ?? "Brotu generation failed.");
|
|
1582
|
+
}
|
|
1583
|
+
if (snapshot.status === "succeeded" && snapshot.result) {
|
|
1584
|
+
return { ...snapshot.result, processingTimeMs: Date.now() - startedAt };
|
|
1585
|
+
}
|
|
1586
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
|
|
1587
|
+
}
|
|
1588
|
+
return failure(
|
|
1589
|
+
`Brotu generation ${submitted.generationId} did not finish after ${maxAttempts} checks.`
|
|
1590
|
+
);
|
|
1591
|
+
}
|
|
1592
|
+
async completeJob(job) {
|
|
1593
|
+
const workspaceId = this.workspaceFromPoll(job.pollEndpoint) ?? await this.workspaceId();
|
|
1594
|
+
const path = this.studioPath(
|
|
1595
|
+
`/api/v1/studio/generations/${job.id}`,
|
|
1596
|
+
workspaceId
|
|
1597
|
+
);
|
|
1598
|
+
const generation = await this.request(path);
|
|
1599
|
+
const status = (generation.status ?? "").toLowerCase();
|
|
1600
|
+
if (status === "failed") {
|
|
1601
|
+
return {
|
|
1602
|
+
status: "failed",
|
|
1603
|
+
error: generation.errorMessage ?? "Brotu generation failed."
|
|
1604
|
+
};
|
|
1605
|
+
}
|
|
1606
|
+
if (status !== "completed") {
|
|
1607
|
+
return { status: "pending" };
|
|
1608
|
+
}
|
|
1609
|
+
const outputs = outputsFrom(generation, job.kind);
|
|
1610
|
+
return {
|
|
1611
|
+
status: "succeeded",
|
|
1612
|
+
result: {
|
|
1613
|
+
success: true,
|
|
1614
|
+
outputs,
|
|
1615
|
+
creditsUsed: generation.creditsUsed ?? 0,
|
|
1616
|
+
provider: this.providerName,
|
|
1617
|
+
model: job.model,
|
|
1618
|
+
processingTimeMs: 0
|
|
1619
|
+
}
|
|
1620
|
+
};
|
|
1621
|
+
}
|
|
1622
|
+
workspaceFromPoll(pollEndpoint) {
|
|
1623
|
+
if (!pollEndpoint) return void 0;
|
|
1624
|
+
try {
|
|
1625
|
+
const url = new URL(pollEndpoint, "https://brotu.invalid");
|
|
1626
|
+
return url.searchParams.get("workspaceId") ?? void 0;
|
|
1627
|
+
} catch {
|
|
1628
|
+
return void 0;
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
generateImage(params) {
|
|
1632
|
+
return this.run("image", params);
|
|
1633
|
+
}
|
|
1634
|
+
generateVideo(params) {
|
|
1635
|
+
return this.run("video", params);
|
|
1636
|
+
}
|
|
1637
|
+
async generateText(params) {
|
|
1638
|
+
return {
|
|
1639
|
+
success: false,
|
|
1640
|
+
outputs: [],
|
|
1641
|
+
creditsUsed: 0,
|
|
1642
|
+
provider: this.providerName,
|
|
1643
|
+
model: params.model ?? "(none)",
|
|
1644
|
+
processingTimeMs: 0,
|
|
1645
|
+
error: UNSUPPORTED_VIA_CREDITS
|
|
1646
|
+
};
|
|
1647
|
+
}
|
|
1648
|
+
async generateAudio(params) {
|
|
1649
|
+
return {
|
|
1650
|
+
success: false,
|
|
1651
|
+
outputs: [],
|
|
1652
|
+
creditsUsed: 0,
|
|
1653
|
+
provider: this.providerName,
|
|
1654
|
+
model: params.model ?? "(none)",
|
|
1655
|
+
processingTimeMs: 0,
|
|
1656
|
+
error: UNSUPPORTED_VIA_CREDITS
|
|
1657
|
+
};
|
|
1658
|
+
}
|
|
1659
|
+
async estimateCost(type, params) {
|
|
1660
|
+
if (type === "audio" || type === "text") {
|
|
1661
|
+
const estimate = estimateFor(this.providerName, type, params);
|
|
1662
|
+
return {
|
|
1663
|
+
...estimate,
|
|
1664
|
+
usd: null,
|
|
1665
|
+
note: UNSUPPORTED_VIA_CREDITS
|
|
1666
|
+
};
|
|
1667
|
+
}
|
|
1668
|
+
try {
|
|
1669
|
+
const workspaceId = await this.workspaceId();
|
|
1670
|
+
const video = params;
|
|
1671
|
+
const image = params;
|
|
1672
|
+
const payload = await this.request(
|
|
1673
|
+
this.studioPath("/api/v1/studio/estimate", workspaceId),
|
|
1674
|
+
{
|
|
1675
|
+
method: "POST",
|
|
1676
|
+
body: JSON.stringify({
|
|
1677
|
+
model: this.platformModel(params.model ?? ""),
|
|
1678
|
+
duration: video.duration,
|
|
1679
|
+
resolution: video.resolution ?? image.resolution,
|
|
1680
|
+
aspectRatio: video.aspectRatio ?? image.aspectRatio,
|
|
1681
|
+
withAudio: video.withAudio,
|
|
1682
|
+
hasReferenceImage: Boolean(
|
|
1683
|
+
video.imageUrl || video.imageUrls?.length || video.referenceImages?.length || image.referenceImages?.length
|
|
1684
|
+
),
|
|
1685
|
+
hasReferenceVideo: Boolean(video.videoUrl || video.videoUrls?.length),
|
|
1686
|
+
quality: image.quality
|
|
1687
|
+
})
|
|
1688
|
+
}
|
|
1689
|
+
);
|
|
1690
|
+
const credits = payload.estimatedCredits ?? 0;
|
|
1691
|
+
return {
|
|
1692
|
+
unit: type === "video" ? "second" : "image",
|
|
1693
|
+
units: credits,
|
|
1694
|
+
usd: null,
|
|
1695
|
+
note: `${credits} Brotu credit${credits === 1 ? "" : "s"}. A vendor key generates on that provider.`,
|
|
1696
|
+
provider: this.providerName,
|
|
1697
|
+
model: params.model ?? ""
|
|
1698
|
+
};
|
|
1699
|
+
} catch (error) {
|
|
1700
|
+
const estimate = estimateFor(this.providerName, type, params);
|
|
1701
|
+
return {
|
|
1702
|
+
...estimate,
|
|
1703
|
+
usd: null,
|
|
1704
|
+
note: error instanceof Error ? error.message : "Could not estimate Brotu credits."
|
|
1705
|
+
};
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
supportsModel() {
|
|
1709
|
+
return true;
|
|
1710
|
+
}
|
|
1711
|
+
getAvailableModels() {
|
|
1712
|
+
return [];
|
|
1713
|
+
}
|
|
1714
|
+
};
|
|
1715
|
+
function outputsFrom(generation, kind) {
|
|
1716
|
+
const bucket = generation.outputs;
|
|
1717
|
+
const urls = kind === "video" ? bucket?.videos ?? bucket?.images ?? [] : bucket?.images ?? bucket?.videos ?? [];
|
|
1718
|
+
const copies = bucket?.copies ?? [];
|
|
1719
|
+
const mimeType = kind === "video" ? "video/mp4" : "image/png";
|
|
1720
|
+
const fromUrls = urls.filter(Boolean).map((url) => ({
|
|
1721
|
+
url,
|
|
1722
|
+
mimeType
|
|
1723
|
+
}));
|
|
1724
|
+
if (fromUrls.length > 0) return fromUrls;
|
|
1725
|
+
return copies.filter(Boolean).map((url) => ({
|
|
1726
|
+
url,
|
|
1727
|
+
mimeType: "text/plain",
|
|
1728
|
+
raw: { text: url }
|
|
1729
|
+
}));
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1374
1732
|
// src/adapters/byteplus.adapter.ts
|
|
1375
1733
|
var DEFAULT_BASE_URL = "https://ark.ap-southeast.bytepluses.com";
|
|
1376
1734
|
var TASKS_PATH = "/api/v3/contents/generations/tasks";
|
|
1377
1735
|
var IMAGES_PATH = "/api/v3/images/generations";
|
|
1378
|
-
var
|
|
1379
|
-
var
|
|
1736
|
+
var POLL_INTERVAL_MS2 = 5e3;
|
|
1737
|
+
var DEFAULT_MAX_POLL_ATTEMPTS2 = 240;
|
|
1380
1738
|
var BytePlusAdapter = class {
|
|
1381
1739
|
providerName = "byteplus";
|
|
1382
1740
|
supportedTypes = ["image", "video"];
|
|
@@ -1576,7 +1934,7 @@ var BytePlusAdapter = class {
|
|
|
1576
1934
|
params,
|
|
1577
1935
|
submittedAt: new Date(startedAt).toISOString()
|
|
1578
1936
|
};
|
|
1579
|
-
const maxAttempts = this.opts.maxPollAttempts ??
|
|
1937
|
+
const maxAttempts = this.opts.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS2;
|
|
1580
1938
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
1581
1939
|
const snapshot = await this.completeJob(job);
|
|
1582
1940
|
if (snapshot.status === "failed") {
|
|
@@ -1585,7 +1943,7 @@ var BytePlusAdapter = class {
|
|
|
1585
1943
|
if (snapshot.status === "succeeded" && snapshot.result) {
|
|
1586
1944
|
return { ...snapshot.result, processingTimeMs: Date.now() - startedAt };
|
|
1587
1945
|
}
|
|
1588
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
1946
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS2));
|
|
1589
1947
|
}
|
|
1590
1948
|
return failure(
|
|
1591
1949
|
`Ark task ${taskId} did not finish after ${maxAttempts} checks.`
|
|
@@ -1862,8 +2220,8 @@ var ElevenLabsAdapter = class {
|
|
|
1862
2220
|
// src/adapters/google.adapter.ts
|
|
1863
2221
|
var DEFAULT_BASE_URL3 = "https://generativelanguage.googleapis.com";
|
|
1864
2222
|
var INTERACTIONS_PATH = "/v1beta/interactions";
|
|
1865
|
-
var
|
|
1866
|
-
var
|
|
2223
|
+
var POLL_INTERVAL_MS3 = 1e4;
|
|
2224
|
+
var DEFAULT_MAX_POLL_ATTEMPTS3 = 120;
|
|
1867
2225
|
var GoogleAdapter = class {
|
|
1868
2226
|
providerName = "google";
|
|
1869
2227
|
supportedTypes = [
|
|
@@ -2135,7 +2493,7 @@ var GoogleAdapter = class {
|
|
|
2135
2493
|
params,
|
|
2136
2494
|
submittedAt: new Date(startedAt).toISOString()
|
|
2137
2495
|
};
|
|
2138
|
-
const maxAttempts = this.opts.maxPollAttempts ??
|
|
2496
|
+
const maxAttempts = this.opts.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS3;
|
|
2139
2497
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
2140
2498
|
const snapshot = await this.completeJob(job);
|
|
2141
2499
|
if (snapshot.status === "failed") {
|
|
@@ -2144,7 +2502,7 @@ var GoogleAdapter = class {
|
|
|
2144
2502
|
if (snapshot.status === "succeeded" && snapshot.result) {
|
|
2145
2503
|
return { ...snapshot.result, processingTimeMs: Date.now() - startedAt };
|
|
2146
2504
|
}
|
|
2147
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
2505
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS3));
|
|
2148
2506
|
}
|
|
2149
2507
|
return failure(`Veo operation ${operation} did not finish in time.`);
|
|
2150
2508
|
}
|
|
@@ -2359,8 +2717,8 @@ var GoogleAdapter = class {
|
|
|
2359
2717
|
|
|
2360
2718
|
// src/adapters/kling.adapter.ts
|
|
2361
2719
|
var DEFAULT_BASE_URL4 = "https://api-singapore.klingai.com";
|
|
2362
|
-
var
|
|
2363
|
-
var
|
|
2720
|
+
var POLL_INTERVAL_MS4 = 5e3;
|
|
2721
|
+
var DEFAULT_MAX_POLL_ATTEMPTS4 = 240;
|
|
2364
2722
|
function stateOf(task) {
|
|
2365
2723
|
const raw = (task.status ?? task.task_status ?? "").toLowerCase();
|
|
2366
2724
|
if (raw === "failed") return "failed";
|
|
@@ -2557,7 +2915,7 @@ var KlingAdapter = class {
|
|
|
2557
2915
|
params,
|
|
2558
2916
|
submittedAt: new Date(startedAt).toISOString()
|
|
2559
2917
|
};
|
|
2560
|
-
const maxAttempts = this.opts.maxPollAttempts ??
|
|
2918
|
+
const maxAttempts = this.opts.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS4;
|
|
2561
2919
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
2562
2920
|
const snapshot = await this.completeJob(job);
|
|
2563
2921
|
if (snapshot.status === "failed") {
|
|
@@ -2566,7 +2924,7 @@ var KlingAdapter = class {
|
|
|
2566
2924
|
if (snapshot.status === "succeeded" && snapshot.result) {
|
|
2567
2925
|
return { ...snapshot.result, processingTimeMs: Date.now() - startedAt };
|
|
2568
2926
|
}
|
|
2569
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
2927
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS4));
|
|
2570
2928
|
}
|
|
2571
2929
|
return failure(
|
|
2572
2930
|
`Kling task ${submitted.taskId} did not finish after ${maxAttempts} checks.`
|
|
@@ -2937,8 +3295,8 @@ var OpenAIAdapter = class {
|
|
|
2937
3295
|
// src/adapters/qwen.adapter.ts
|
|
2938
3296
|
var DEFAULT_BASE_URL6 = "https://dashscope-intl.aliyuncs.com";
|
|
2939
3297
|
var TASKS_PATH2 = "/api/v1/tasks";
|
|
2940
|
-
var
|
|
2941
|
-
var
|
|
3298
|
+
var POLL_INTERVAL_MS5 = 5e3;
|
|
3299
|
+
var DEFAULT_MAX_POLL_ATTEMPTS5 = 240;
|
|
2942
3300
|
var QwenAdapter = class {
|
|
2943
3301
|
providerName = "qwen";
|
|
2944
3302
|
supportedTypes = ["image", "video"];
|
|
@@ -3145,7 +3503,7 @@ var QwenAdapter = class {
|
|
|
3145
3503
|
params,
|
|
3146
3504
|
submittedAt: new Date(startedAt).toISOString()
|
|
3147
3505
|
};
|
|
3148
|
-
const maxAttempts = this.opts.maxPollAttempts ??
|
|
3506
|
+
const maxAttempts = this.opts.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS5;
|
|
3149
3507
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
3150
3508
|
const snapshot = await this.completeJob(job);
|
|
3151
3509
|
if (snapshot.status === "failed") {
|
|
@@ -3154,7 +3512,7 @@ var QwenAdapter = class {
|
|
|
3154
3512
|
if (snapshot.status === "succeeded" && snapshot.result) {
|
|
3155
3513
|
return { ...snapshot.result, processingTimeMs: Date.now() - startedAt };
|
|
3156
3514
|
}
|
|
3157
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
3515
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS5));
|
|
3158
3516
|
}
|
|
3159
3517
|
return failure(
|
|
3160
3518
|
`DashScope task ${taskId} did not finish after ${maxAttempts} checks.`
|
|
@@ -3414,6 +3772,22 @@ function failFrom(code, cause, context) {
|
|
|
3414
3772
|
});
|
|
3415
3773
|
}
|
|
3416
3774
|
|
|
3775
|
+
// src/lib/hooks.ts
|
|
3776
|
+
function hookName(kind, stage) {
|
|
3777
|
+
return `on${kind[0].toUpperCase()}${kind.slice(1)}${stage}`;
|
|
3778
|
+
}
|
|
3779
|
+
function findHook(hooks, kind, stage) {
|
|
3780
|
+
if (!hooks || !kind) return void 0;
|
|
3781
|
+
return hooks[hookName(kind, stage)];
|
|
3782
|
+
}
|
|
3783
|
+
async function runHook(hook, event) {
|
|
3784
|
+
if (!hook) return;
|
|
3785
|
+
try {
|
|
3786
|
+
await hook(event);
|
|
3787
|
+
} catch {
|
|
3788
|
+
}
|
|
3789
|
+
}
|
|
3790
|
+
|
|
3417
3791
|
// src/lib/storage.ts
|
|
3418
3792
|
async function loadS3(config) {
|
|
3419
3793
|
let s3;
|
|
@@ -3584,10 +3958,15 @@ var NATIVE_PROVIDERS = [
|
|
|
3584
3958
|
"openai",
|
|
3585
3959
|
"qwen"
|
|
3586
3960
|
];
|
|
3587
|
-
function
|
|
3588
|
-
|
|
3589
|
-
|
|
3961
|
+
function brotu(options) {
|
|
3962
|
+
const apiKey = options.apiKey?.trim();
|
|
3963
|
+
if (!apiKey) {
|
|
3964
|
+
throw new Error(
|
|
3965
|
+
"Pass a Brotu API key (brotu_sk_\u2026). Get one at https://brotu.app."
|
|
3966
|
+
);
|
|
3590
3967
|
}
|
|
3968
|
+
const optionsWithKey = { ...options, apiKey };
|
|
3969
|
+
const vendors = optionsWithKey.providers ?? {};
|
|
3591
3970
|
registerModels(options.models);
|
|
3592
3971
|
let registeredWebhook = resolveWebhook(options.webhook);
|
|
3593
3972
|
const notifiedJobIds = /* @__PURE__ */ new Set();
|
|
@@ -3611,7 +3990,7 @@ function brotuClient(options) {
|
|
|
3611
3990
|
}
|
|
3612
3991
|
let provider;
|
|
3613
3992
|
try {
|
|
3614
|
-
provider = resolveProvider(modelId,
|
|
3993
|
+
provider = resolveProvider(modelId, optionsWithKey);
|
|
3615
3994
|
} catch (error) {
|
|
3616
3995
|
return failFrom("missing_key", error, { model: modelId });
|
|
3617
3996
|
}
|
|
@@ -3632,6 +4011,13 @@ function brotuClient(options) {
|
|
|
3632
4011
|
return ok({ adapter, provider: provider.id, model: modelId });
|
|
3633
4012
|
}
|
|
3634
4013
|
function buildAdapter(provider) {
|
|
4014
|
+
if (provider.id === "brotu") {
|
|
4015
|
+
return new BrotuAdapter({
|
|
4016
|
+
apiKey,
|
|
4017
|
+
apiUrl: optionsWithKey.apiUrl,
|
|
4018
|
+
workspaceId: optionsWithKey.workspaceId
|
|
4019
|
+
});
|
|
4020
|
+
}
|
|
3635
4021
|
if (provider.id === "kling") {
|
|
3636
4022
|
return new KlingAdapter({
|
|
3637
4023
|
apiKey: provider.apiKey,
|
|
@@ -3685,7 +4071,10 @@ function brotuClient(options) {
|
|
|
3685
4071
|
}
|
|
3686
4072
|
async function notifySettled(input) {
|
|
3687
4073
|
const config = webhookFor(input.params);
|
|
3688
|
-
|
|
4074
|
+
const kind = input.kind ?? input.job?.kind;
|
|
4075
|
+
const stage = input.event === "generation.succeeded" ? "Success" : "Error";
|
|
4076
|
+
const hook = findHook(options.hooks, kind, stage);
|
|
4077
|
+
if (!config && !hook) return;
|
|
3689
4078
|
const jobId = input.job?.id;
|
|
3690
4079
|
if (jobId) {
|
|
3691
4080
|
if (notifiedJobIds.has(jobId)) return;
|
|
@@ -3696,14 +4085,38 @@ function brotuClient(options) {
|
|
|
3696
4085
|
jobId,
|
|
3697
4086
|
provider: input.provider ?? input.job?.provider,
|
|
3698
4087
|
model: input.model ?? input.job?.model,
|
|
3699
|
-
kind
|
|
4088
|
+
kind,
|
|
3700
4089
|
outputs: input.outputs,
|
|
3701
4090
|
error: input.error ? { code: input.error.code, message: input.error.message } : void 0,
|
|
3702
4091
|
metadata: input.metadata ?? input.job?.metadata,
|
|
3703
4092
|
processingTimeMs: input.processingTimeMs,
|
|
3704
4093
|
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3705
4094
|
};
|
|
3706
|
-
|
|
4095
|
+
if (hook && kind) {
|
|
4096
|
+
await runHook(hook, {
|
|
4097
|
+
kind,
|
|
4098
|
+
stage,
|
|
4099
|
+
provider: payload.provider ?? "",
|
|
4100
|
+
model: payload.model ?? "",
|
|
4101
|
+
jobId: payload.jobId,
|
|
4102
|
+
outputs: payload.outputs,
|
|
4103
|
+
error: payload.error,
|
|
4104
|
+
metadata: payload.metadata,
|
|
4105
|
+
processingTimeMs: payload.processingTimeMs,
|
|
4106
|
+
at: payload.completedAt
|
|
4107
|
+
});
|
|
4108
|
+
}
|
|
4109
|
+
if (config) await deliverWebhook(config, payload);
|
|
4110
|
+
}
|
|
4111
|
+
function notifyLoading(kind, routed, params) {
|
|
4112
|
+
return runHook(findHook(options.hooks, kind, "Loading"), {
|
|
4113
|
+
kind,
|
|
4114
|
+
stage: "Loading",
|
|
4115
|
+
provider: routed.provider,
|
|
4116
|
+
model: routed.model,
|
|
4117
|
+
metadata: params.metadata,
|
|
4118
|
+
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
4119
|
+
});
|
|
3707
4120
|
}
|
|
3708
4121
|
async function toGeneration(raw, metadata) {
|
|
3709
4122
|
if (!raw.success) {
|
|
@@ -3726,6 +4139,7 @@ function brotuClient(options) {
|
|
|
3726
4139
|
async function generate(kind, params) {
|
|
3727
4140
|
const routed = route(params.model);
|
|
3728
4141
|
if (routed.error) return fail(routed.error);
|
|
4142
|
+
await notifyLoading(kind, routed.data, params);
|
|
3729
4143
|
try {
|
|
3730
4144
|
const result = await toGeneration(
|
|
3731
4145
|
await generateWith(routed.data.adapter, kind, params),
|
|
@@ -3776,6 +4190,7 @@ function brotuClient(options) {
|
|
|
3776
4190
|
async function submit(kind, params) {
|
|
3777
4191
|
const routed = route(params.model);
|
|
3778
4192
|
if (routed.error) return fail(routed.error);
|
|
4193
|
+
await notifyLoading(kind, routed.data, params);
|
|
3779
4194
|
const base = {
|
|
3780
4195
|
provider: routed.data.provider,
|
|
3781
4196
|
model: routed.data.model,
|
|
@@ -3906,7 +4321,7 @@ function brotuClient(options) {
|
|
|
3906
4321
|
}
|
|
3907
4322
|
}
|
|
3908
4323
|
function klingNamespace() {
|
|
3909
|
-
const configured =
|
|
4324
|
+
const configured = vendors.kling;
|
|
3910
4325
|
if (!configured) return void 0;
|
|
3911
4326
|
const adapter = new KlingAdapter({
|
|
3912
4327
|
apiKey: configured.apiKey,
|
|
@@ -3931,7 +4346,7 @@ function brotuClient(options) {
|
|
|
3931
4346
|
};
|
|
3932
4347
|
}
|
|
3933
4348
|
function googleNamespace() {
|
|
3934
|
-
const configured =
|
|
4349
|
+
const configured = vendors.google;
|
|
3935
4350
|
if (!configured) return void 0;
|
|
3936
4351
|
const adapter = new GoogleAdapter({
|
|
3937
4352
|
apiKey: configured.apiKey,
|
|
@@ -3947,24 +4362,33 @@ function brotuClient(options) {
|
|
|
3947
4362
|
}
|
|
3948
4363
|
};
|
|
3949
4364
|
}
|
|
4365
|
+
function listFor(category) {
|
|
4366
|
+
return describeModels(optionsWithKey).filter(
|
|
4367
|
+
(entry) => entry.model.category === category
|
|
4368
|
+
);
|
|
4369
|
+
}
|
|
3950
4370
|
return {
|
|
3951
4371
|
google: googleNamespace(),
|
|
3952
4372
|
kling: klingNamespace(),
|
|
3953
4373
|
image: {
|
|
3954
4374
|
submit: (params) => submit("image", params),
|
|
3955
|
-
generate: (params) => generate("image", params)
|
|
4375
|
+
generate: (params) => generate("image", params),
|
|
4376
|
+
list: () => listFor("image")
|
|
3956
4377
|
},
|
|
3957
4378
|
video: {
|
|
3958
4379
|
submit: (params) => submit("video", params),
|
|
3959
|
-
generate: (params) => generate("video", params)
|
|
4380
|
+
generate: (params) => generate("video", params),
|
|
4381
|
+
list: () => listFor("video")
|
|
3960
4382
|
},
|
|
3961
4383
|
text: {
|
|
3962
4384
|
submit: (params) => submit("text", params),
|
|
3963
|
-
generate: (params) => generate("text", params)
|
|
4385
|
+
generate: (params) => generate("text", params),
|
|
4386
|
+
list: () => listFor("text")
|
|
3964
4387
|
},
|
|
3965
4388
|
audio: {
|
|
3966
4389
|
submit: (params) => submit("audio", params),
|
|
3967
|
-
generate: (params) => generate("audio", params)
|
|
4390
|
+
generate: (params) => generate("audio", params),
|
|
4391
|
+
list: () => listFor("audio")
|
|
3968
4392
|
},
|
|
3969
4393
|
webhook: {
|
|
3970
4394
|
set(value) {
|
|
@@ -4014,7 +4438,7 @@ function brotuClient(options) {
|
|
|
4014
4438
|
}
|
|
4015
4439
|
}
|
|
4016
4440
|
},
|
|
4017
|
-
models: () => getAvailableModels(
|
|
4441
|
+
models: () => getAvailableModels(optionsWithKey),
|
|
4018
4442
|
estimateCost: async (type, params) => {
|
|
4019
4443
|
const routed = route(params.model);
|
|
4020
4444
|
if (routed.error) return fail(routed.error);
|
|
@@ -4031,9 +4455,12 @@ function brotuClient(options) {
|
|
|
4031
4455
|
}
|
|
4032
4456
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4033
4457
|
0 && (module.exports = {
|
|
4458
|
+
BROTU_SUPPORTED_CATEGORIES,
|
|
4034
4459
|
BYTEPLUS_CATALOG,
|
|
4035
4460
|
BYTEPLUS_MODELS,
|
|
4461
|
+
BrotuAdapter,
|
|
4036
4462
|
BytePlusAdapter,
|
|
4463
|
+
DEFAULT_BROTU_API_URL,
|
|
4037
4464
|
ELEVENLABS_CATALOG,
|
|
4038
4465
|
ELEVENLABS_MODELS,
|
|
4039
4466
|
ELEVENLABS_OUTPUT_FORMATS,
|
|
@@ -4060,9 +4487,10 @@ function brotuClient(options) {
|
|
|
4060
4487
|
QWEN_TEXT_MODELS,
|
|
4061
4488
|
QWEN_VIDEO_MODELS,
|
|
4062
4489
|
QwenAdapter,
|
|
4063
|
-
|
|
4490
|
+
brotu,
|
|
4064
4491
|
createS3Storage,
|
|
4065
4492
|
deliverWebhook,
|
|
4493
|
+
describeModels,
|
|
4066
4494
|
fail,
|
|
4067
4495
|
getAvailableModels,
|
|
4068
4496
|
getModel,
|