@brotu/ai 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -32,7 +32,9 @@ var index_exports = {};
32
32
  __export(index_exports, {
33
33
  BYTEPLUS_CATALOG: () => BYTEPLUS_CATALOG,
34
34
  BYTEPLUS_MODELS: () => BYTEPLUS_MODELS,
35
+ BrotuAdapter: () => BrotuAdapter,
35
36
  BytePlusAdapter: () => BytePlusAdapter,
37
+ DEFAULT_BROTU_API_URL: () => DEFAULT_BROTU_API_URL,
36
38
  ELEVENLABS_CATALOG: () => ELEVENLABS_CATALOG,
37
39
  ELEVENLABS_MODELS: () => ELEVENLABS_MODELS,
38
40
  ELEVENLABS_OUTPUT_FORMATS: () => ELEVENLABS_OUTPUT_FORMATS,
@@ -1267,8 +1269,16 @@ var PROVIDER_BASE_URLS = {
1267
1269
  elevenlabs: "https://api.elevenlabs.io",
1268
1270
  google: "https://generativelanguage.googleapis.com",
1269
1271
  openai: "https://api.openai.com",
1270
- qwen: "https://dashscope-intl.aliyuncs.com"
1272
+ qwen: "https://dashscope-intl.aliyuncs.com",
1273
+ brotu: "https://api.brotu.app"
1271
1274
  };
1275
+ var DEFAULT_BROTU_API_URL = PROVIDER_BASE_URLS.brotu;
1276
+ function vendorProviders(options) {
1277
+ return options.providers ?? {};
1278
+ }
1279
+ function brotuApiKey(options) {
1280
+ return options.apiKey?.trim() || void 0;
1281
+ }
1272
1282
  var BUILT_IN = [
1273
1283
  ...KLING_CATALOG,
1274
1284
  ...KLING_AUDIO_CATALOG,
@@ -1324,24 +1334,36 @@ function resolveProvider(modelId, options) {
1324
1334
  `Model "${modelId}" declares no provider, so nothing knows how to run it.`
1325
1335
  );
1326
1336
  }
1327
- const configured = options.providers[id];
1328
- if (!configured) {
1329
- const owned = Object.keys(options.providers).join(", ") || "none";
1330
- throw new Error(
1331
- `Model "${modelId}" runs on "${id}", but no API key was given for it (configured: ${owned}).`
1332
- );
1337
+ const configured = vendorProviders(options)[id];
1338
+ if (configured) {
1339
+ const baseUrl = configured.baseUrl ?? PROVIDER_BASE_URLS[id];
1340
+ if (!baseUrl) {
1341
+ throw new Error(
1342
+ `Provider "${id}" has no known base URL \u2014 pass providers.${id}.baseUrl.`
1343
+ );
1344
+ }
1345
+ return { id, apiKey: configured.apiKey, baseUrl: baseUrl.replace(/\/$/, "") };
1333
1346
  }
1334
- const baseUrl = configured.baseUrl ?? PROVIDER_BASE_URLS[id];
1335
- if (!baseUrl) {
1336
- throw new Error(
1337
- `Provider "${id}" has no known base URL \u2014 pass providers.${id}.baseUrl.`
1338
- );
1347
+ const platformKey = brotuApiKey(options);
1348
+ if (platformKey) {
1349
+ return {
1350
+ id: "brotu",
1351
+ apiKey: platformKey,
1352
+ baseUrl: (options.apiUrl ?? DEFAULT_BROTU_API_URL).replace(/\/$/, "")
1353
+ };
1339
1354
  }
1340
- return { id, apiKey: configured.apiKey, baseUrl: baseUrl.replace(/\/$/, "") };
1355
+ const owned = Object.keys(vendorProviders(options)).join(", ") || "none";
1356
+ throw new Error(
1357
+ `Model "${modelId}" runs on "${id}", but no API key was given for it (configured: ${owned}). Pass a Brotu key to generate on the platform.`
1358
+ );
1341
1359
  }
1342
1360
  function getAvailableModels(options) {
1361
+ if (brotuApiKey(options)) {
1362
+ return catalog.filter((model) => Boolean(model.provider));
1363
+ }
1364
+ const vendors = vendorProviders(options);
1343
1365
  return catalog.filter(
1344
- (model) => model.provider && model.provider in options.providers
1366
+ (model) => model.provider && model.provider in vendors
1345
1367
  );
1346
1368
  }
1347
1369
 
@@ -1371,12 +1393,316 @@ function estimateFor(provider, type, params, defaults) {
1371
1393
  };
1372
1394
  }
1373
1395
 
1396
+ // src/adapters/brotu.adapter.ts
1397
+ var DEFAULT_API_URL = "https://api.brotu.app";
1398
+ var POLL_INTERVAL_MS = 3e3;
1399
+ var DEFAULT_MAX_POLL_ATTEMPTS = 400;
1400
+ var PLATFORM_MODEL_IDS = {
1401
+ "kling/v2-6": "kling-2.6",
1402
+ "kling/v3": "kling-3.0/video",
1403
+ "dreamina-seedance-2-5-260628": "bytedance/seedance-2-5",
1404
+ "dreamina-seedance-2-0-260128": "bytedance/seedance-2",
1405
+ "dreamina-seedance-2-0-fast-260128": "bytedance/seedance-2-fast",
1406
+ "gpt-image-1.5": "gpt-image/1.5"
1407
+ };
1408
+ var UNSUPPORTED_VIA_CREDITS = "Speech and text generate on the vendor. Pass that provider's key.";
1409
+ var BrotuAdapter = class {
1410
+ constructor(opts) {
1411
+ this.opts = opts;
1412
+ }
1413
+ opts;
1414
+ providerName = "brotu";
1415
+ supportedTypes = ["image", "video"];
1416
+ workspaceIdCache;
1417
+ get origin() {
1418
+ return (this.opts.apiUrl ?? DEFAULT_API_URL).replace(/\/$/, "");
1419
+ }
1420
+ headers() {
1421
+ return {
1422
+ Authorization: `Bearer ${this.opts.apiKey}`,
1423
+ "Content-Type": "application/json"
1424
+ };
1425
+ }
1426
+ async workspaceId() {
1427
+ if (this.opts.workspaceId) return this.opts.workspaceId;
1428
+ if (this.workspaceIdCache) return this.workspaceIdCache;
1429
+ const payload = await this.request("/api/v1/studio/default-workspace");
1430
+ const id = payload.workspaceId?.trim();
1431
+ if (!id) {
1432
+ throw new Error(
1433
+ "This Brotu account has no workspace. Open https://brotu.app and create one."
1434
+ );
1435
+ }
1436
+ this.workspaceIdCache = id;
1437
+ return id;
1438
+ }
1439
+ studioPath(path, workspaceId) {
1440
+ const joiner = path.includes("?") ? "&" : "?";
1441
+ return `${path}${joiner}workspaceId=${encodeURIComponent(workspaceId)}`;
1442
+ }
1443
+ async request(path, init) {
1444
+ const response = await fetch(`${this.origin}${path}`, {
1445
+ ...init,
1446
+ headers: { ...this.headers(), ...init?.headers }
1447
+ });
1448
+ const text = await response.text();
1449
+ let body = void 0;
1450
+ if (text) {
1451
+ try {
1452
+ body = JSON.parse(text);
1453
+ } catch {
1454
+ body = text;
1455
+ }
1456
+ }
1457
+ if (!response.ok) {
1458
+ const parsed = body;
1459
+ const message = parsed && typeof parsed === "object" ? parsed.error?.message || parsed.message || text : text;
1460
+ throw new Error(message || `Brotu API ${response.status}`);
1461
+ }
1462
+ return body;
1463
+ }
1464
+ platformModel(modelId) {
1465
+ return PLATFORM_MODEL_IDS[modelId] ?? modelId;
1466
+ }
1467
+ async startGeneration(kind, params) {
1468
+ const modelId = params.model ?? "";
1469
+ const workspaceId = await this.workspaceId();
1470
+ const path = kind === "video" ? "/api/v1/studio/videos" : "/api/v1/studio/images";
1471
+ const body = kind === "video" ? this.videoBody(params, modelId) : this.imageBody(params, modelId);
1472
+ const result = await this.request(
1473
+ this.studioPath(path, workspaceId),
1474
+ { method: "POST", body: JSON.stringify(body) }
1475
+ );
1476
+ const generationId = result.generationId?.trim();
1477
+ if (!generationId) {
1478
+ throw new Error("Brotu accepted the request but returned no generation id.");
1479
+ }
1480
+ return { generationId, workspaceId };
1481
+ }
1482
+ imageBody(params, modelId) {
1483
+ return {
1484
+ prompt: params.prompt,
1485
+ model: this.platformModel(modelId),
1486
+ aspectRatio: params.aspectRatio,
1487
+ resolution: params.resolution,
1488
+ negativePrompt: params.negativePrompt,
1489
+ outputFormat: params.outputFormat,
1490
+ seed: params.seed,
1491
+ referenceImages: params.referenceImages
1492
+ };
1493
+ }
1494
+ videoBody(params, modelId) {
1495
+ return {
1496
+ prompt: params.prompt,
1497
+ model: this.platformModel(modelId),
1498
+ duration: params.duration,
1499
+ resolution: params.resolution,
1500
+ aspectRatio: params.aspectRatio,
1501
+ mode: params.mode,
1502
+ withAudio: params.withAudio,
1503
+ seed: params.seed,
1504
+ imageUrl: params.imageUrl,
1505
+ imageUrls: params.imageUrls,
1506
+ videoUrl: params.videoUrl,
1507
+ videoUrls: params.videoUrls,
1508
+ referenceImages: params.referenceImages
1509
+ };
1510
+ }
1511
+ async run(kind, params) {
1512
+ const startedAt = Date.now();
1513
+ const modelId = params.model ?? "(none)";
1514
+ const failure = (error) => ({
1515
+ success: false,
1516
+ outputs: [],
1517
+ creditsUsed: 0,
1518
+ provider: this.providerName,
1519
+ model: modelId,
1520
+ processingTimeMs: Date.now() - startedAt,
1521
+ error
1522
+ });
1523
+ let submitted;
1524
+ try {
1525
+ submitted = await this.startGeneration(kind, params);
1526
+ } catch (error) {
1527
+ return failure(error instanceof Error ? error.message : String(error));
1528
+ }
1529
+ const pollEndpoint = this.studioPath(
1530
+ `/api/v1/studio/generations/${submitted.generationId}`,
1531
+ submitted.workspaceId
1532
+ );
1533
+ if (isSubmitMode()) {
1534
+ throw new PendingJob(submitted.generationId, pollEndpoint);
1535
+ }
1536
+ const job = {
1537
+ id: submitted.generationId,
1538
+ provider: this.providerName,
1539
+ model: modelId,
1540
+ kind,
1541
+ pollEndpoint,
1542
+ params,
1543
+ submittedAt: new Date(startedAt).toISOString()
1544
+ };
1545
+ const maxAttempts = this.opts.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS;
1546
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
1547
+ const snapshot = await this.completeJob(job);
1548
+ if (snapshot.status === "failed") {
1549
+ return failure(snapshot.error ?? "Brotu generation failed.");
1550
+ }
1551
+ if (snapshot.status === "succeeded" && snapshot.result) {
1552
+ return { ...snapshot.result, processingTimeMs: Date.now() - startedAt };
1553
+ }
1554
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
1555
+ }
1556
+ return failure(
1557
+ `Brotu generation ${submitted.generationId} did not finish after ${maxAttempts} checks.`
1558
+ );
1559
+ }
1560
+ async completeJob(job) {
1561
+ const workspaceId = this.workspaceFromPoll(job.pollEndpoint) ?? await this.workspaceId();
1562
+ const path = this.studioPath(
1563
+ `/api/v1/studio/generations/${job.id}`,
1564
+ workspaceId
1565
+ );
1566
+ const generation = await this.request(path);
1567
+ const status = (generation.status ?? "").toLowerCase();
1568
+ if (status === "failed") {
1569
+ return {
1570
+ status: "failed",
1571
+ error: generation.errorMessage ?? "Brotu generation failed."
1572
+ };
1573
+ }
1574
+ if (status !== "completed") {
1575
+ return { status: "pending" };
1576
+ }
1577
+ const outputs = outputsFrom(generation, job.kind);
1578
+ return {
1579
+ status: "succeeded",
1580
+ result: {
1581
+ success: true,
1582
+ outputs,
1583
+ creditsUsed: generation.creditsUsed ?? 0,
1584
+ provider: this.providerName,
1585
+ model: job.model,
1586
+ processingTimeMs: 0
1587
+ }
1588
+ };
1589
+ }
1590
+ workspaceFromPoll(pollEndpoint) {
1591
+ if (!pollEndpoint) return void 0;
1592
+ try {
1593
+ const url = new URL(pollEndpoint, "https://brotu.invalid");
1594
+ return url.searchParams.get("workspaceId") ?? void 0;
1595
+ } catch {
1596
+ return void 0;
1597
+ }
1598
+ }
1599
+ generateImage(params) {
1600
+ return this.run("image", params);
1601
+ }
1602
+ generateVideo(params) {
1603
+ return this.run("video", params);
1604
+ }
1605
+ async generateText(params) {
1606
+ return {
1607
+ success: false,
1608
+ outputs: [],
1609
+ creditsUsed: 0,
1610
+ provider: this.providerName,
1611
+ model: params.model ?? "(none)",
1612
+ processingTimeMs: 0,
1613
+ error: UNSUPPORTED_VIA_CREDITS
1614
+ };
1615
+ }
1616
+ async generateAudio(params) {
1617
+ return {
1618
+ success: false,
1619
+ outputs: [],
1620
+ creditsUsed: 0,
1621
+ provider: this.providerName,
1622
+ model: params.model ?? "(none)",
1623
+ processingTimeMs: 0,
1624
+ error: UNSUPPORTED_VIA_CREDITS
1625
+ };
1626
+ }
1627
+ async estimateCost(type, params) {
1628
+ if (type === "audio" || type === "text") {
1629
+ const estimate = estimateFor(this.providerName, type, params);
1630
+ return {
1631
+ ...estimate,
1632
+ usd: null,
1633
+ note: UNSUPPORTED_VIA_CREDITS
1634
+ };
1635
+ }
1636
+ try {
1637
+ const workspaceId = await this.workspaceId();
1638
+ const video = params;
1639
+ const image = params;
1640
+ const payload = await this.request(
1641
+ this.studioPath("/api/v1/studio/estimate", workspaceId),
1642
+ {
1643
+ method: "POST",
1644
+ body: JSON.stringify({
1645
+ model: this.platformModel(params.model ?? ""),
1646
+ duration: video.duration,
1647
+ resolution: video.resolution ?? image.resolution,
1648
+ aspectRatio: video.aspectRatio ?? image.aspectRatio,
1649
+ withAudio: video.withAudio,
1650
+ hasReferenceImage: Boolean(
1651
+ video.imageUrl || video.imageUrls?.length || video.referenceImages?.length || image.referenceImages?.length
1652
+ ),
1653
+ hasReferenceVideo: Boolean(video.videoUrl || video.videoUrls?.length),
1654
+ quality: image.quality
1655
+ })
1656
+ }
1657
+ );
1658
+ const credits = payload.estimatedCredits ?? 0;
1659
+ return {
1660
+ unit: type === "video" ? "second" : "image",
1661
+ units: credits,
1662
+ usd: null,
1663
+ note: `${credits} Brotu credit${credits === 1 ? "" : "s"}. A vendor key generates on that provider.`,
1664
+ provider: this.providerName,
1665
+ model: params.model ?? ""
1666
+ };
1667
+ } catch (error) {
1668
+ const estimate = estimateFor(this.providerName, type, params);
1669
+ return {
1670
+ ...estimate,
1671
+ usd: null,
1672
+ note: error instanceof Error ? error.message : "Could not estimate Brotu credits."
1673
+ };
1674
+ }
1675
+ }
1676
+ supportsModel() {
1677
+ return true;
1678
+ }
1679
+ getAvailableModels() {
1680
+ return [];
1681
+ }
1682
+ };
1683
+ function outputsFrom(generation, kind) {
1684
+ const bucket = generation.outputs;
1685
+ const urls = kind === "video" ? bucket?.videos ?? bucket?.images ?? [] : bucket?.images ?? bucket?.videos ?? [];
1686
+ const copies = bucket?.copies ?? [];
1687
+ const mimeType = kind === "video" ? "video/mp4" : "image/png";
1688
+ const fromUrls = urls.filter(Boolean).map((url) => ({
1689
+ url,
1690
+ mimeType
1691
+ }));
1692
+ if (fromUrls.length > 0) return fromUrls;
1693
+ return copies.filter(Boolean).map((url) => ({
1694
+ url,
1695
+ mimeType: "text/plain",
1696
+ raw: { text: url }
1697
+ }));
1698
+ }
1699
+
1374
1700
  // src/adapters/byteplus.adapter.ts
1375
1701
  var DEFAULT_BASE_URL = "https://ark.ap-southeast.bytepluses.com";
1376
1702
  var TASKS_PATH = "/api/v3/contents/generations/tasks";
1377
1703
  var IMAGES_PATH = "/api/v3/images/generations";
1378
- var POLL_INTERVAL_MS = 5e3;
1379
- var DEFAULT_MAX_POLL_ATTEMPTS = 240;
1704
+ var POLL_INTERVAL_MS2 = 5e3;
1705
+ var DEFAULT_MAX_POLL_ATTEMPTS2 = 240;
1380
1706
  var BytePlusAdapter = class {
1381
1707
  providerName = "byteplus";
1382
1708
  supportedTypes = ["image", "video"];
@@ -1576,7 +1902,7 @@ var BytePlusAdapter = class {
1576
1902
  params,
1577
1903
  submittedAt: new Date(startedAt).toISOString()
1578
1904
  };
1579
- const maxAttempts = this.opts.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS;
1905
+ const maxAttempts = this.opts.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS2;
1580
1906
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
1581
1907
  const snapshot = await this.completeJob(job);
1582
1908
  if (snapshot.status === "failed") {
@@ -1585,7 +1911,7 @@ var BytePlusAdapter = class {
1585
1911
  if (snapshot.status === "succeeded" && snapshot.result) {
1586
1912
  return { ...snapshot.result, processingTimeMs: Date.now() - startedAt };
1587
1913
  }
1588
- await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
1914
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS2));
1589
1915
  }
1590
1916
  return failure(
1591
1917
  `Ark task ${taskId} did not finish after ${maxAttempts} checks.`
@@ -1862,8 +2188,8 @@ var ElevenLabsAdapter = class {
1862
2188
  // src/adapters/google.adapter.ts
1863
2189
  var DEFAULT_BASE_URL3 = "https://generativelanguage.googleapis.com";
1864
2190
  var INTERACTIONS_PATH = "/v1beta/interactions";
1865
- var POLL_INTERVAL_MS2 = 1e4;
1866
- var DEFAULT_MAX_POLL_ATTEMPTS2 = 120;
2191
+ var POLL_INTERVAL_MS3 = 1e4;
2192
+ var DEFAULT_MAX_POLL_ATTEMPTS3 = 120;
1867
2193
  var GoogleAdapter = class {
1868
2194
  providerName = "google";
1869
2195
  supportedTypes = [
@@ -2135,7 +2461,7 @@ var GoogleAdapter = class {
2135
2461
  params,
2136
2462
  submittedAt: new Date(startedAt).toISOString()
2137
2463
  };
2138
- const maxAttempts = this.opts.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS2;
2464
+ const maxAttempts = this.opts.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS3;
2139
2465
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
2140
2466
  const snapshot = await this.completeJob(job);
2141
2467
  if (snapshot.status === "failed") {
@@ -2144,7 +2470,7 @@ var GoogleAdapter = class {
2144
2470
  if (snapshot.status === "succeeded" && snapshot.result) {
2145
2471
  return { ...snapshot.result, processingTimeMs: Date.now() - startedAt };
2146
2472
  }
2147
- await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS2));
2473
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS3));
2148
2474
  }
2149
2475
  return failure(`Veo operation ${operation} did not finish in time.`);
2150
2476
  }
@@ -2359,8 +2685,8 @@ var GoogleAdapter = class {
2359
2685
 
2360
2686
  // src/adapters/kling.adapter.ts
2361
2687
  var DEFAULT_BASE_URL4 = "https://api-singapore.klingai.com";
2362
- var POLL_INTERVAL_MS3 = 5e3;
2363
- var DEFAULT_MAX_POLL_ATTEMPTS3 = 240;
2688
+ var POLL_INTERVAL_MS4 = 5e3;
2689
+ var DEFAULT_MAX_POLL_ATTEMPTS4 = 240;
2364
2690
  function stateOf(task) {
2365
2691
  const raw = (task.status ?? task.task_status ?? "").toLowerCase();
2366
2692
  if (raw === "failed") return "failed";
@@ -2557,7 +2883,7 @@ var KlingAdapter = class {
2557
2883
  params,
2558
2884
  submittedAt: new Date(startedAt).toISOString()
2559
2885
  };
2560
- const maxAttempts = this.opts.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS3;
2886
+ const maxAttempts = this.opts.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS4;
2561
2887
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
2562
2888
  const snapshot = await this.completeJob(job);
2563
2889
  if (snapshot.status === "failed") {
@@ -2566,7 +2892,7 @@ var KlingAdapter = class {
2566
2892
  if (snapshot.status === "succeeded" && snapshot.result) {
2567
2893
  return { ...snapshot.result, processingTimeMs: Date.now() - startedAt };
2568
2894
  }
2569
- await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS3));
2895
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS4));
2570
2896
  }
2571
2897
  return failure(
2572
2898
  `Kling task ${submitted.taskId} did not finish after ${maxAttempts} checks.`
@@ -2937,8 +3263,8 @@ var OpenAIAdapter = class {
2937
3263
  // src/adapters/qwen.adapter.ts
2938
3264
  var DEFAULT_BASE_URL6 = "https://dashscope-intl.aliyuncs.com";
2939
3265
  var TASKS_PATH2 = "/api/v1/tasks";
2940
- var POLL_INTERVAL_MS4 = 5e3;
2941
- var DEFAULT_MAX_POLL_ATTEMPTS4 = 240;
3266
+ var POLL_INTERVAL_MS5 = 5e3;
3267
+ var DEFAULT_MAX_POLL_ATTEMPTS5 = 240;
2942
3268
  var QwenAdapter = class {
2943
3269
  providerName = "qwen";
2944
3270
  supportedTypes = ["image", "video"];
@@ -3145,7 +3471,7 @@ var QwenAdapter = class {
3145
3471
  params,
3146
3472
  submittedAt: new Date(startedAt).toISOString()
3147
3473
  };
3148
- const maxAttempts = this.opts.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS4;
3474
+ const maxAttempts = this.opts.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS5;
3149
3475
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
3150
3476
  const snapshot = await this.completeJob(job);
3151
3477
  if (snapshot.status === "failed") {
@@ -3154,7 +3480,7 @@ var QwenAdapter = class {
3154
3480
  if (snapshot.status === "succeeded" && snapshot.result) {
3155
3481
  return { ...snapshot.result, processingTimeMs: Date.now() - startedAt };
3156
3482
  }
3157
- await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS4));
3483
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS5));
3158
3484
  }
3159
3485
  return failure(
3160
3486
  `DashScope task ${taskId} did not finish after ${maxAttempts} checks.`
@@ -3585,9 +3911,14 @@ var NATIVE_PROVIDERS = [
3585
3911
  "qwen"
3586
3912
  ];
3587
3913
  function brotuClient(options) {
3588
- if (Object.keys(options.providers).length === 0) {
3589
- throw new Error("brotuClient requires at least one provider API key.");
3914
+ const apiKey = options.apiKey?.trim();
3915
+ if (!apiKey) {
3916
+ throw new Error(
3917
+ "Pass a Brotu API key (brotu_sk_\u2026). Get one at https://brotu.app."
3918
+ );
3590
3919
  }
3920
+ const optionsWithKey = { ...options, apiKey };
3921
+ const vendors = optionsWithKey.providers ?? {};
3591
3922
  registerModels(options.models);
3592
3923
  let registeredWebhook = resolveWebhook(options.webhook);
3593
3924
  const notifiedJobIds = /* @__PURE__ */ new Set();
@@ -3611,7 +3942,7 @@ function brotuClient(options) {
3611
3942
  }
3612
3943
  let provider;
3613
3944
  try {
3614
- provider = resolveProvider(modelId, options);
3945
+ provider = resolveProvider(modelId, optionsWithKey);
3615
3946
  } catch (error) {
3616
3947
  return failFrom("missing_key", error, { model: modelId });
3617
3948
  }
@@ -3632,6 +3963,13 @@ function brotuClient(options) {
3632
3963
  return ok({ adapter, provider: provider.id, model: modelId });
3633
3964
  }
3634
3965
  function buildAdapter(provider) {
3966
+ if (provider.id === "brotu") {
3967
+ return new BrotuAdapter({
3968
+ apiKey,
3969
+ apiUrl: optionsWithKey.apiUrl,
3970
+ workspaceId: optionsWithKey.workspaceId
3971
+ });
3972
+ }
3635
3973
  if (provider.id === "kling") {
3636
3974
  return new KlingAdapter({
3637
3975
  apiKey: provider.apiKey,
@@ -3906,7 +4244,7 @@ function brotuClient(options) {
3906
4244
  }
3907
4245
  }
3908
4246
  function klingNamespace() {
3909
- const configured = options.providers.kling;
4247
+ const configured = vendors.kling;
3910
4248
  if (!configured) return void 0;
3911
4249
  const adapter = new KlingAdapter({
3912
4250
  apiKey: configured.apiKey,
@@ -3931,7 +4269,7 @@ function brotuClient(options) {
3931
4269
  };
3932
4270
  }
3933
4271
  function googleNamespace() {
3934
- const configured = options.providers.google;
4272
+ const configured = vendors.google;
3935
4273
  if (!configured) return void 0;
3936
4274
  const adapter = new GoogleAdapter({
3937
4275
  apiKey: configured.apiKey,
@@ -4014,7 +4352,7 @@ function brotuClient(options) {
4014
4352
  }
4015
4353
  }
4016
4354
  },
4017
- models: () => getAvailableModels(options),
4355
+ models: () => getAvailableModels(optionsWithKey),
4018
4356
  estimateCost: async (type, params) => {
4019
4357
  const routed = route(params.model);
4020
4358
  if (routed.error) return fail(routed.error);
@@ -4033,7 +4371,9 @@ function brotuClient(options) {
4033
4371
  0 && (module.exports = {
4034
4372
  BYTEPLUS_CATALOG,
4035
4373
  BYTEPLUS_MODELS,
4374
+ BrotuAdapter,
4036
4375
  BytePlusAdapter,
4376
+ DEFAULT_BROTU_API_URL,
4037
4377
  ELEVENLABS_CATALOG,
4038
4378
  ELEVENLABS_MODELS,
4039
4379
  ELEVENLABS_OUTPUT_FORMATS,
package/dist/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ export type { BrotuAdapterOptions } from "./adapters/brotu.adapter";
2
+ export { BrotuAdapter } from "./adapters/brotu.adapter";
1
3
  export type { BytePlusAdapterOptions } from "./adapters/byteplus.adapter";
2
4
  export { BytePlusAdapter } from "./adapters/byteplus.adapter";
3
5
  export type { ElevenLabsAdapterOptions, ElevenLabsVoice, } from "./adapters/elevenlabs.adapter";
@@ -10,7 +12,7 @@ export type { OpenAIAdapterOptions } from "./adapters/openai.adapter";
10
12
  export { OpenAIAdapter } from "./adapters/openai.adapter";
11
13
  export type { QwenAdapterOptions } from "./adapters/qwen.adapter";
12
14
  export { QwenAdapter } from "./adapters/qwen.adapter";
13
- export { getAvailableModels, getModel, getModels, getProviders, hasModel, registerModels, resetCatalog, resolveProvider, } from "./catalog";
15
+ export { DEFAULT_BROTU_API_URL, getAvailableModels, getModel, getModels, getProviders, hasModel, registerModels, resetCatalog, resolveProvider, } from "./catalog";
14
16
  export type { BrotuAI } from "./client";
15
17
  export { brotuClient } from "./client";
16
18
  export type * from "./constants/model.types";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAC1E,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,YAAY,EACX,wBAAwB,EACxB,eAAe,GACf,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAClE,YAAY,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACtE,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,YAAY,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,YAAY,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACtE,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,YAAY,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EACN,kBAAkB,EAClB,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,QAAQ,EACR,cAAc,EACd,YAAY,EACZ,eAAe,GACf,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,mBAAmB,yBAAyB,CAAC;AAC7C,OAAO,EACN,KAAK,OAAO,EACZ,KAAK,WAAW,EAChB,IAAI,EACJ,KAAK,UAAU,EACf,EAAE,EACF,KAAK,MAAM,GACX,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACN,YAAY,EACZ,KAAK,GAAG,EACR,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,UAAU,GACV,MAAM,YAAY,CAAC;AACpB,OAAO,EACN,eAAe,EACf,cAAc,EACd,KAAK,eAAe,EACpB,KAAK,OAAO,GACZ,MAAM,eAAe,CAAC;AACvB,OAAO,EACN,cAAc,EACd,cAAc,EACd,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,gBAAgB,GACrB,MAAM,eAAe,CAAC;AACvB,mBAAmB,gCAAgC,CAAC;AACpD,OAAO,EACN,gBAAgB,EAChB,eAAe,EACf,KAAK,oBAAoB,GACzB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACN,kBAAkB,EAClB,iBAAiB,EACjB,yBAAyB,GACzB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACN,mBAAmB,EACnB,cAAc,EACd,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,EACjB,mBAAmB,GACnB,MAAM,2BAA2B,CAAC;AACnC,YAAY,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EACN,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,kBAAkB,EAClB,aAAa,EACb,YAAY,EACZ,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,gBAAgB,GACrB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACN,cAAc,EACd,mBAAmB,EACnB,kBAAkB,GAClB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACN,iBAAiB,EACjB,YAAY,EACZ,iBAAiB,EACjB,gBAAgB,EAChB,iBAAiB,EACjB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,GACrB,MAAM,yBAAyB,CAAC;AACjC,YAAY,EACX,cAAc,EACd,cAAc,EACd,kBAAkB,EAClB,gBAAgB,GAChB,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}