@brotu/ai 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -42,7 +42,7 @@ import {
42
42
  resetCatalog,
43
43
  resolveProvider,
44
44
  videoPathFor
45
- } from "./chunk-QQIHMDJC.js";
45
+ } from "./chunk-OISTBZ64.js";
46
46
 
47
47
  // src/lib/jobs.ts
48
48
  import { AsyncLocalStorage } from "async_hooks";
@@ -1394,10 +1394,383 @@ var GoogleAdapter = class {
1394
1394
  }
1395
1395
  };
1396
1396
 
1397
+ // src/lib/webhook.ts
1398
+ function resolveWebhook(value) {
1399
+ if (!value) return void 0;
1400
+ if (typeof value === "string") {
1401
+ return isHttpUrl(value) ? { url: value } : void 0;
1402
+ }
1403
+ if (!isHttpUrl(value.url)) return void 0;
1404
+ return {
1405
+ url: value.url,
1406
+ secret: value.secret,
1407
+ headers: value.headers
1408
+ };
1409
+ }
1410
+ function isHttpUrl(value) {
1411
+ try {
1412
+ const parsed = new URL(value);
1413
+ return parsed.protocol === "https:" || parsed.protocol === "http:";
1414
+ } catch {
1415
+ return false;
1416
+ }
1417
+ }
1418
+ async function deliverWebhook(config, payload) {
1419
+ try {
1420
+ await fetch(config.url, {
1421
+ method: "POST",
1422
+ headers: {
1423
+ "content-type": "application/json",
1424
+ "user-agent": "@brotu/ai",
1425
+ "x-brotu-event": payload.event,
1426
+ ...config.secret ? { "x-brotu-webhook-secret": config.secret } : {},
1427
+ ...config.headers
1428
+ },
1429
+ body: JSON.stringify(payload),
1430
+ signal: AbortSignal.timeout(1e4)
1431
+ });
1432
+ } catch {
1433
+ }
1434
+ }
1435
+
1436
+ // src/adapters/kie.adapter.ts
1437
+ var DEFAULT_BASE_URL4 = "https://api.kie.ai/api/v1";
1438
+ var POLL_INTERVAL_MS4 = 3e3;
1439
+ var DEFAULT_MAX_POLL_ATTEMPTS4 = 400;
1440
+ var KIE_MODEL_IDS = {
1441
+ "kling/v2-6": "kling-2.6/{mode}-to-video",
1442
+ "kling/v3": "kling-3.0/video",
1443
+ "dreamina-seedance-2-5-260628": "bytedance/seedance-2-5",
1444
+ "dreamina-seedance-2-0-260128": "bytedance/seedance-2",
1445
+ "dreamina-seedance-2-0-fast-260128": "bytedance/seedance-2-fast",
1446
+ "dreamina-seedance-2-0-mini-260615": "bytedance/seedance-2-mini",
1447
+ "wan2.6-t2v": "wan/2-6-text-to-video",
1448
+ "wan2.6-i2v": "wan/2-6-image-to-video",
1449
+ "wan2.7-t2v": "wan/2-7-text-to-video",
1450
+ "wan2.7-i2v": "wan/2-7-image-to-video",
1451
+ "gpt-image-1.5": "gpt-image/1.5-{mode}-to-image",
1452
+ "gpt-image-2": "gpt-image-2-{mode}-to-image",
1453
+ "topaz/video-upscale": "topaz/video-upscale"
1454
+ };
1455
+ var KieAdapter = class {
1456
+ constructor(opts) {
1457
+ this.opts = opts;
1458
+ }
1459
+ opts;
1460
+ providerName = "kie";
1461
+ supportedTypes = [
1462
+ "image",
1463
+ "video",
1464
+ "text",
1465
+ "audio"
1466
+ ];
1467
+ get origin() {
1468
+ return (this.opts.baseUrl ?? DEFAULT_BASE_URL4).replace(/\/$/, "");
1469
+ }
1470
+ async request(path, init) {
1471
+ const response = await fetch(`${this.origin}${path}`, {
1472
+ ...init,
1473
+ headers: {
1474
+ Authorization: `Bearer ${this.opts.apiKey}`,
1475
+ "Content-Type": "application/json",
1476
+ ...init?.headers
1477
+ }
1478
+ });
1479
+ const text = await response.text();
1480
+ let body;
1481
+ try {
1482
+ body = text ? JSON.parse(text) : void 0;
1483
+ } catch {
1484
+ body = void 0;
1485
+ }
1486
+ const code = body?.code;
1487
+ if (!response.ok || typeof code === "number" && code !== 200) {
1488
+ const message = body?.msg || body?.message || text;
1489
+ throw new Error(message || `kie API ${response.status}`);
1490
+ }
1491
+ if (body?.data === void 0) {
1492
+ throw new Error("kie answered without a payload.");
1493
+ }
1494
+ return body.data;
1495
+ }
1496
+ /** Which market model serves this request. */
1497
+ marketModel(modelId, params) {
1498
+ const fromCatalog = getModel(modelId)?.endpoint?.replace(
1499
+ /^\/market\//,
1500
+ ""
1501
+ );
1502
+ const mapped = fromCatalog || KIE_MODEL_IDS[modelId] || modelId;
1503
+ if (!mapped.includes("{mode}")) return mapped;
1504
+ return mapped.replace("{mode}", hasInputImage(params) ? "image" : "text");
1505
+ }
1506
+ callbackFor(params) {
1507
+ const perRequest = resolveWebhook(params.webhook);
1508
+ return perRequest?.url ?? this.opts.callbackUrl;
1509
+ }
1510
+ async createTask(kind, params) {
1511
+ const modelId = params.model ?? "";
1512
+ const body = {
1513
+ model: this.marketModel(modelId, params),
1514
+ input: buildInput(kind, params, modelId)
1515
+ };
1516
+ const callBackUrl = this.callbackFor(params);
1517
+ if (callBackUrl) body.callBackUrl = callBackUrl;
1518
+ const data = await this.request("/jobs/createTask", {
1519
+ method: "POST",
1520
+ body: JSON.stringify(body)
1521
+ });
1522
+ const taskId = data.taskId?.trim();
1523
+ if (!taskId) {
1524
+ throw new Error("kie accepted the task but returned no taskId.");
1525
+ }
1526
+ return taskId;
1527
+ }
1528
+ /** Ask kie about one task. The same shape a callback carries. */
1529
+ async task(taskId) {
1530
+ const record = await this.request(
1531
+ `/jobs/recordInfo?taskId=${encodeURIComponent(taskId)}`
1532
+ );
1533
+ return readTaskRecord(record, taskId);
1534
+ }
1535
+ async run(kind, params) {
1536
+ const startedAt = Date.now();
1537
+ const modelId = params.model ?? "(none)";
1538
+ const failure = (error) => ({
1539
+ success: false,
1540
+ outputs: [],
1541
+ creditsUsed: 0,
1542
+ provider: this.providerName,
1543
+ model: modelId,
1544
+ processingTimeMs: Date.now() - startedAt,
1545
+ error
1546
+ });
1547
+ let taskId;
1548
+ try {
1549
+ taskId = await this.createTask(kind, params);
1550
+ } catch (error) {
1551
+ return failure(error instanceof Error ? error.message : String(error));
1552
+ }
1553
+ const pollEndpoint = `/jobs/recordInfo?taskId=${encodeURIComponent(taskId)}`;
1554
+ if (isSubmitMode()) {
1555
+ throw new PendingJob(taskId, pollEndpoint);
1556
+ }
1557
+ const job = {
1558
+ id: taskId,
1559
+ provider: this.providerName,
1560
+ model: modelId,
1561
+ kind,
1562
+ pollEndpoint,
1563
+ params,
1564
+ submittedAt: new Date(startedAt).toISOString()
1565
+ };
1566
+ const maxAttempts = this.opts.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS4;
1567
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
1568
+ const snapshot = await this.completeJob(job);
1569
+ if (snapshot.status === "failed") {
1570
+ return failure(snapshot.error ?? "kie task failed.");
1571
+ }
1572
+ if (snapshot.status === "succeeded" && snapshot.result) {
1573
+ return { ...snapshot.result, processingTimeMs: Date.now() - startedAt };
1574
+ }
1575
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS4));
1576
+ }
1577
+ return failure(
1578
+ `kie task ${taskId} did not finish after ${maxAttempts} checks.`
1579
+ );
1580
+ }
1581
+ async completeJob(job) {
1582
+ const settled = await this.task(job.id);
1583
+ return snapshotFrom(settled, job.model);
1584
+ }
1585
+ generateImage(params) {
1586
+ return this.run("image", params);
1587
+ }
1588
+ generateVideo(params) {
1589
+ return this.run("video", params);
1590
+ }
1591
+ generateText(params) {
1592
+ return this.run("text", params);
1593
+ }
1594
+ generateAudio(params) {
1595
+ return this.run("audio", params);
1596
+ }
1597
+ async estimateCost(type, params) {
1598
+ return estimateFor(this.providerName, type, params);
1599
+ }
1600
+ supportsModel() {
1601
+ return true;
1602
+ }
1603
+ getAvailableModels() {
1604
+ return [];
1605
+ }
1606
+ };
1607
+ function parseKieCallback(body) {
1608
+ let parsed = body;
1609
+ if (typeof body === "string") {
1610
+ try {
1611
+ parsed = JSON.parse(body);
1612
+ } catch {
1613
+ throw new Error("kie callback body is not JSON.");
1614
+ }
1615
+ }
1616
+ const root = asRecord(parsed);
1617
+ const record = asRecord(root?.data) ?? root ?? {};
1618
+ const taskId = record.taskId?.trim();
1619
+ if (!taskId) {
1620
+ throw new Error("kie callback carries no taskId.");
1621
+ }
1622
+ return readTaskRecord(record, taskId);
1623
+ }
1624
+ function kieSnapshot(settled, model) {
1625
+ return snapshotFrom(settled, model ?? settled.model ?? "");
1626
+ }
1627
+ function snapshotFrom(settled, model) {
1628
+ if (settled.status === "failed") {
1629
+ return { status: "failed", error: settled.error ?? "kie task failed." };
1630
+ }
1631
+ if (settled.status === "pending") return { status: "pending" };
1632
+ return {
1633
+ status: "succeeded",
1634
+ result: {
1635
+ success: true,
1636
+ outputs: settled.outputs,
1637
+ creditsUsed: settled.creditsUsed,
1638
+ provider: "kie",
1639
+ model,
1640
+ processingTimeMs: 0
1641
+ }
1642
+ };
1643
+ }
1644
+ function readTaskRecord(record, taskId) {
1645
+ const state = (record.state ?? "").toLowerCase();
1646
+ const creditsUsed = record.creditsConsumed ?? 0;
1647
+ if (state === "fail") {
1648
+ return {
1649
+ taskId,
1650
+ model: record.model,
1651
+ status: "failed",
1652
+ outputs: [],
1653
+ creditsUsed,
1654
+ error: record.failMsg || (record.failCode ? `kie failCode ${record.failCode}` : void 0) || "kie task failed."
1655
+ };
1656
+ }
1657
+ if (state !== "success") {
1658
+ return {
1659
+ taskId,
1660
+ model: record.model,
1661
+ status: "pending",
1662
+ outputs: [],
1663
+ creditsUsed
1664
+ };
1665
+ }
1666
+ return {
1667
+ taskId,
1668
+ model: record.model,
1669
+ status: "succeeded",
1670
+ outputs: outputsFrom2(record.resultJson),
1671
+ creditsUsed
1672
+ };
1673
+ }
1674
+ function outputsFrom2(resultJson) {
1675
+ if (!resultJson) return [];
1676
+ let parsed;
1677
+ try {
1678
+ parsed = JSON.parse(resultJson);
1679
+ } catch {
1680
+ return [];
1681
+ }
1682
+ const record = asRecord(parsed);
1683
+ const urls = Array.isArray(record?.resultUrls) ? record.resultUrls.filter(
1684
+ (url) => typeof url === "string" && url.length > 0
1685
+ ) : [];
1686
+ if (urls.length > 0) return urls.map((url) => ({ url, mimeType: mimeFor(url) }));
1687
+ const object = record?.resultObject;
1688
+ if (object !== void 0) {
1689
+ const text = typeof object === "string" ? object : JSON.stringify(object);
1690
+ return [
1691
+ {
1692
+ url: `data:text/plain;base64,${Buffer.from(text).toString("base64")}`,
1693
+ mimeType: "text/plain",
1694
+ raw: { text }
1695
+ }
1696
+ ];
1697
+ }
1698
+ return [];
1699
+ }
1700
+ function mimeFor(url) {
1701
+ const path = url.split("?")[0]?.toLowerCase() ?? "";
1702
+ if (/\.(mp4|mov|webm|m4v)$/.test(path)) return "video/mp4";
1703
+ if (/\.(mp3|wav|m4a|aac)$/.test(path)) return "audio/mpeg";
1704
+ if (/\.jpe?g$/.test(path)) return "image/jpeg";
1705
+ if (/\.webp$/.test(path)) return "image/webp";
1706
+ return "image/png";
1707
+ }
1708
+ function asRecord(value) {
1709
+ return value && typeof value === "object" ? value : void 0;
1710
+ }
1711
+ function hasInputImage(params) {
1712
+ const p = params;
1713
+ return Boolean(
1714
+ p.imageUrl || p.imageUrls?.length || p.referenceImages?.length
1715
+ );
1716
+ }
1717
+ function buildInput(kind, params, modelId) {
1718
+ const raw = params.providerOptions?.kie ?? {};
1719
+ const base = modelId.startsWith("kling/") ? klingInput(params) : modelId.includes("seedance") ? seedanceInput(params) : commonInput(kind, params);
1720
+ return prune({ ...base, ...raw });
1721
+ }
1722
+ function klingInput(params) {
1723
+ const p = params;
1724
+ return {
1725
+ prompt: p.prompt,
1726
+ negative_prompt: p.negativePrompt,
1727
+ aspect_ratio: p.aspectRatio,
1728
+ // kie's kling enum is a string, unlike every other family.
1729
+ duration: p.duration === void 0 ? void 0 : String(p.duration),
1730
+ sound: p.withAudio,
1731
+ image_url: p.imageUrl ?? p.referenceImages?.[0]
1732
+ };
1733
+ }
1734
+ function seedanceInput(params) {
1735
+ const p = params;
1736
+ return {
1737
+ prompt: p.prompt,
1738
+ duration: p.duration,
1739
+ resolution: p.resolution,
1740
+ aspect_ratio: p.aspectRatio,
1741
+ first_frame_url: p.imageUrl ?? p.referenceImages?.[0],
1742
+ last_frame_url: p.referenceImages?.[1],
1743
+ reference_image_urls: p.imageUrls,
1744
+ reference_video_urls: p.videoUrls ?? (p.videoUrl ? [p.videoUrl] : void 0),
1745
+ generate_audio: p.withAudio
1746
+ };
1747
+ }
1748
+ function commonInput(kind, params) {
1749
+ const p = params;
1750
+ const images = p.imageUrls ?? p.referenceImages;
1751
+ return {
1752
+ prompt: p.prompt,
1753
+ negative_prompt: p.negativePrompt,
1754
+ aspect_ratio: p.aspectRatio,
1755
+ resolution: p.resolution,
1756
+ duration: kind === "video" ? p.duration : void 0,
1757
+ seed: p.seed,
1758
+ output_format: p.outputFormat,
1759
+ image_url: p.imageUrl ?? images?.[0],
1760
+ image_urls: images && images.length > 1 ? images : void 0,
1761
+ video_url: p.videoUrl
1762
+ };
1763
+ }
1764
+ function prune(input) {
1765
+ return Object.fromEntries(
1766
+ Object.entries(input).filter(([, value]) => value !== void 0)
1767
+ );
1768
+ }
1769
+
1397
1770
  // src/adapters/kling.adapter.ts
1398
- var DEFAULT_BASE_URL4 = "https://api-singapore.klingai.com";
1399
- var POLL_INTERVAL_MS4 = 5e3;
1400
- var DEFAULT_MAX_POLL_ATTEMPTS4 = 240;
1771
+ var DEFAULT_BASE_URL5 = "https://api-singapore.klingai.com";
1772
+ var POLL_INTERVAL_MS5 = 5e3;
1773
+ var DEFAULT_MAX_POLL_ATTEMPTS5 = 240;
1401
1774
  function stateOf(task) {
1402
1775
  const raw = (task.status ?? task.task_status ?? "").toLowerCase();
1403
1776
  if (raw === "failed") return "failed";
@@ -1412,7 +1785,7 @@ var KlingAdapter = class {
1412
1785
  this.opts = opts;
1413
1786
  }
1414
1787
  get baseUrl() {
1415
- return (this.opts.baseUrl ?? DEFAULT_BASE_URL4).replace(/\/$/, "");
1788
+ return (this.opts.baseUrl ?? DEFAULT_BASE_URL5).replace(/\/$/, "");
1416
1789
  }
1417
1790
  async request(path, init) {
1418
1791
  const response = await fetch(`${this.baseUrl}${path}`, {
@@ -1594,7 +1967,7 @@ var KlingAdapter = class {
1594
1967
  params,
1595
1968
  submittedAt: new Date(startedAt).toISOString()
1596
1969
  };
1597
- const maxAttempts = this.opts.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS4;
1970
+ const maxAttempts = this.opts.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS5;
1598
1971
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
1599
1972
  const snapshot = await this.completeJob(job);
1600
1973
  if (snapshot.status === "failed") {
@@ -1603,7 +1976,7 @@ var KlingAdapter = class {
1603
1976
  if (snapshot.status === "succeeded" && snapshot.result) {
1604
1977
  return { ...snapshot.result, processingTimeMs: Date.now() - startedAt };
1605
1978
  }
1606
- await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS4));
1979
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS5));
1607
1980
  }
1608
1981
  return failure(
1609
1982
  `Kling task ${submitted.taskId} did not finish after ${maxAttempts} checks.`
@@ -1740,7 +2113,7 @@ var KlingAdapter = class {
1740
2113
  };
1741
2114
 
1742
2115
  // src/adapters/openai.adapter.ts
1743
- var DEFAULT_BASE_URL5 = "https://api.openai.com";
2116
+ var DEFAULT_BASE_URL6 = "https://api.openai.com";
1744
2117
  var IMAGES_PATH2 = "/v1/images/generations";
1745
2118
  var OpenAIAdapter = class {
1746
2119
  providerName = "openai";
@@ -1751,7 +2124,7 @@ var OpenAIAdapter = class {
1751
2124
  this.opts = opts;
1752
2125
  }
1753
2126
  get baseUrl() {
1754
- return (this.opts.baseUrl ?? DEFAULT_BASE_URL5).replace(/\/$/, "");
2127
+ return (this.opts.baseUrl ?? DEFAULT_BASE_URL6).replace(/\/$/, "");
1755
2128
  }
1756
2129
  binding(modelId) {
1757
2130
  const id = modelId ?? "";
@@ -1972,10 +2345,10 @@ var OpenAIAdapter = class {
1972
2345
  };
1973
2346
 
1974
2347
  // src/adapters/qwen.adapter.ts
1975
- var DEFAULT_BASE_URL6 = "https://dashscope-intl.aliyuncs.com";
2348
+ var DEFAULT_BASE_URL7 = "https://dashscope-intl.aliyuncs.com";
1976
2349
  var TASKS_PATH2 = "/api/v1/tasks";
1977
- var POLL_INTERVAL_MS5 = 5e3;
1978
- var DEFAULT_MAX_POLL_ATTEMPTS5 = 240;
2350
+ var POLL_INTERVAL_MS6 = 5e3;
2351
+ var DEFAULT_MAX_POLL_ATTEMPTS6 = 240;
1979
2352
  var QwenAdapter = class {
1980
2353
  providerName = "qwen";
1981
2354
  supportedTypes = ["image", "video"];
@@ -1984,7 +2357,7 @@ var QwenAdapter = class {
1984
2357
  this.opts = opts;
1985
2358
  }
1986
2359
  get baseUrl() {
1987
- return (this.opts.baseUrl ?? DEFAULT_BASE_URL6).replace(/\/$/, "");
2360
+ return (this.opts.baseUrl ?? DEFAULT_BASE_URL7).replace(/\/$/, "");
1988
2361
  }
1989
2362
  async request(path, init) {
1990
2363
  const headers = {
@@ -2182,7 +2555,7 @@ var QwenAdapter = class {
2182
2555
  params,
2183
2556
  submittedAt: new Date(startedAt).toISOString()
2184
2557
  };
2185
- const maxAttempts = this.opts.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS5;
2558
+ const maxAttempts = this.opts.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS6;
2186
2559
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
2187
2560
  const snapshot = await this.completeJob(job);
2188
2561
  if (snapshot.status === "failed") {
@@ -2191,7 +2564,7 @@ var QwenAdapter = class {
2191
2564
  if (snapshot.status === "succeeded" && snapshot.result) {
2192
2565
  return { ...snapshot.result, processingTimeMs: Date.now() - startedAt };
2193
2566
  }
2194
- await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS5));
2567
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS6));
2195
2568
  }
2196
2569
  return failure(
2197
2570
  `DashScope task ${taskId} did not finish after ${maxAttempts} checks.`
@@ -2436,9 +2809,9 @@ var QwenAdapter = class {
2436
2809
  };
2437
2810
 
2438
2811
  // src/adapters/topaz.adapter.ts
2439
- var DEFAULT_BASE_URL7 = "https://api.topazlabs.com";
2440
- var POLL_INTERVAL_MS6 = 5e3;
2441
- var DEFAULT_MAX_POLL_ATTEMPTS6 = 240;
2812
+ var DEFAULT_BASE_URL8 = "https://api.topazlabs.com";
2813
+ var POLL_INTERVAL_MS7 = 5e3;
2814
+ var DEFAULT_MAX_POLL_ATTEMPTS7 = 240;
2442
2815
  var DEFAULT_FPS = 30;
2443
2816
  var PIXELS = {
2444
2817
  "720p": { width: 1280, height: 720 },
@@ -2458,7 +2831,7 @@ var TopazAdapter = class {
2458
2831
  this.opts = opts;
2459
2832
  }
2460
2833
  get baseUrl() {
2461
- return (this.opts.baseUrl ?? DEFAULT_BASE_URL7).replace(/\/$/, "");
2834
+ return (this.opts.baseUrl ?? DEFAULT_BASE_URL8).replace(/\/$/, "");
2462
2835
  }
2463
2836
  get headers() {
2464
2837
  return {
@@ -2671,7 +3044,7 @@ var TopazAdapter = class {
2671
3044
  params,
2672
3045
  submittedAt: new Date(startedAt).toISOString()
2673
3046
  };
2674
- const maxAttempts = this.opts.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS6;
3047
+ const maxAttempts = this.opts.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS7;
2675
3048
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
2676
3049
  const snapshot = await this.completeJob(job);
2677
3050
  if (snapshot.status === "failed") {
@@ -2680,7 +3053,7 @@ var TopazAdapter = class {
2680
3053
  if (snapshot.status === "succeeded" && snapshot.result) {
2681
3054
  return { ...snapshot.result, processingTimeMs: Date.now() - startedAt };
2682
3055
  }
2683
- await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS6));
3056
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS7));
2684
3057
  }
2685
3058
  return failure(
2686
3059
  `Topaz request ${submitted.taskId} did not finish after ${maxAttempts} checks.`
@@ -2846,7 +3219,7 @@ var TopazAdapter = class {
2846
3219
  params,
2847
3220
  submittedAt: new Date(startedAt).toISOString()
2848
3221
  };
2849
- const maxAttempts = this.opts.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS6;
3222
+ const maxAttempts = this.opts.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS7;
2850
3223
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
2851
3224
  const snapshot = await this.completeJob(job);
2852
3225
  if (snapshot.status === "failed") {
@@ -2855,7 +3228,7 @@ var TopazAdapter = class {
2855
3228
  if (snapshot.status === "succeeded" && snapshot.result) {
2856
3229
  return { ...snapshot.result, processingTimeMs: Date.now() - startedAt };
2857
3230
  }
2858
- await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS6));
3231
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS7));
2859
3232
  }
2860
3233
  return failure(
2861
3234
  `Topaz image ${submitted.taskId} did not finish after ${maxAttempts} checks.`
@@ -3058,50 +3431,12 @@ async function persistOutputs(storage, outputs) {
3058
3431
  );
3059
3432
  }
3060
3433
 
3061
- // src/lib/webhook.ts
3062
- function resolveWebhook(value) {
3063
- if (!value) return void 0;
3064
- if (typeof value === "string") {
3065
- return isHttpUrl(value) ? { url: value } : void 0;
3066
- }
3067
- if (!isHttpUrl(value.url)) return void 0;
3068
- return {
3069
- url: value.url,
3070
- secret: value.secret,
3071
- headers: value.headers
3072
- };
3073
- }
3074
- function isHttpUrl(value) {
3075
- try {
3076
- const parsed = new URL(value);
3077
- return parsed.protocol === "https:" || parsed.protocol === "http:";
3078
- } catch {
3079
- return false;
3080
- }
3081
- }
3082
- async function deliverWebhook(config, payload) {
3083
- try {
3084
- await fetch(config.url, {
3085
- method: "POST",
3086
- headers: {
3087
- "content-type": "application/json",
3088
- "user-agent": "@brotu/ai",
3089
- "x-brotu-event": payload.event,
3090
- ...config.secret ? { "x-brotu-webhook-secret": config.secret } : {},
3091
- ...config.headers
3092
- },
3093
- body: JSON.stringify(payload),
3094
- signal: AbortSignal.timeout(1e4)
3095
- });
3096
- } catch {
3097
- }
3098
- }
3099
-
3100
3434
  // src/client.ts
3101
3435
  var NATIVE_PROVIDERS = [
3102
3436
  "byteplus",
3103
3437
  "elevenlabs",
3104
3438
  "google",
3439
+ "kie",
3105
3440
  "kling",
3106
3441
  "openai",
3107
3442
  "qwen",
@@ -3167,6 +3502,16 @@ function brotu(options) {
3167
3502
  workspaceId: optionsWithKey.workspaceId
3168
3503
  });
3169
3504
  }
3505
+ if (provider.id === "kie") {
3506
+ return new KieAdapter({
3507
+ apiKey: provider.apiKey,
3508
+ baseUrl: provider.baseUrl,
3509
+ // The client-level webhook doubles as kie's callback, so a submit
3510
+ // never has to be polled. kie posts its own payload there — read it
3511
+ // with `parseKieCallback`.
3512
+ callbackUrl: registeredWebhook?.url
3513
+ });
3514
+ }
3170
3515
  if (provider.id === "kling") {
3171
3516
  return new KlingAdapter({
3172
3517
  apiKey: provider.apiKey,
@@ -3697,6 +4042,7 @@ export {
3697
4042
  KLING_CAPABILITIES,
3698
4043
  KLING_CATALOG,
3699
4044
  KLING_MODELS,
4045
+ KieAdapter,
3700
4046
  KlingAdapter,
3701
4047
  OPENAI_CATALOG,
3702
4048
  OPENAI_IMAGE_MODELS,
@@ -3725,7 +4071,9 @@ export {
3725
4071
  getProviders,
3726
4072
  hasModel,
3727
4073
  isPendingJob,
4074
+ kieSnapshot,
3728
4075
  ok,
4076
+ parseKieCallback,
3729
4077
  persistOutputs,
3730
4078
  registerModels,
3731
4079
  resetCatalog,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brotu/ai",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.cjs",
6
6
  "exports": {