@brotu/ai 0.4.0 → 0.6.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/CATALOG.md +298 -4
- package/README.md +47 -8
- package/dist/adapters/brotu.adapter.d.ts.map +1 -1
- package/dist/adapters/topaz.adapter.d.ts +71 -0
- package/dist/adapters/topaz.adapter.d.ts.map +1 -0
- package/dist/catalog.cjs +143 -8
- package/dist/catalog.d.ts +22 -1
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +5 -1
- package/dist/{chunk-QOWHHKSI.js → chunk-QQIHMDJC.js} +146 -9
- package/dist/client.d.ts +25 -4
- package/dist/client.d.ts.map +1 -1
- package/dist/index.cjs +805 -75
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +610 -12
- package/dist/lib/hooks.d.ts +32 -0
- package/dist/lib/hooks.d.ts.map +1 -0
- package/dist/ports/content-generator.port.d.ts +12 -0
- package/dist/ports/content-generator.port.d.ts.map +1 -1
- package/dist/providers/topaz.models.d.ts +24 -0
- package/dist/providers/topaz.models.d.ts.map +1 -0
- package/dist/types.d.ts +9 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -30,6 +30,7 @@ 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,
|
|
35
36
|
BrotuAdapter: () => BrotuAdapter,
|
|
@@ -61,9 +62,15 @@ __export(index_exports, {
|
|
|
61
62
|
QWEN_TEXT_MODELS: () => QWEN_TEXT_MODELS,
|
|
62
63
|
QWEN_VIDEO_MODELS: () => QWEN_VIDEO_MODELS,
|
|
63
64
|
QwenAdapter: () => QwenAdapter,
|
|
64
|
-
|
|
65
|
+
TOPAZ_CATALOG: () => TOPAZ_CATALOG,
|
|
66
|
+
TOPAZ_IMAGE_MODELS: () => TOPAZ_IMAGE_MODELS,
|
|
67
|
+
TOPAZ_MODELS: () => TOPAZ_MODELS,
|
|
68
|
+
TOPAZ_VIDEO_MODELS: () => TOPAZ_VIDEO_MODELS,
|
|
69
|
+
TopazAdapter: () => TopazAdapter,
|
|
70
|
+
brotu: () => brotu,
|
|
65
71
|
createS3Storage: () => createS3Storage,
|
|
66
72
|
deliverWebhook: () => deliverWebhook,
|
|
73
|
+
describeModels: () => describeModels,
|
|
67
74
|
fail: () => fail,
|
|
68
75
|
getAvailableModels: () => getAvailableModels,
|
|
69
76
|
getModel: () => getModel,
|
|
@@ -80,34 +87,6 @@ __export(index_exports, {
|
|
|
80
87
|
});
|
|
81
88
|
module.exports = __toCommonJS(index_exports);
|
|
82
89
|
|
|
83
|
-
// src/lib/jobs.ts
|
|
84
|
-
var import_node_async_hooks = require("async_hooks");
|
|
85
|
-
var PendingJob = class extends Error {
|
|
86
|
-
taskId;
|
|
87
|
-
pollEndpoint;
|
|
88
|
-
constructor(taskId, pollEndpoint) {
|
|
89
|
-
super(`Job ${taskId} was submitted and is still running.`);
|
|
90
|
-
this.name = "PendingJob";
|
|
91
|
-
this.taskId = taskId;
|
|
92
|
-
this.pollEndpoint = pollEndpoint;
|
|
93
|
-
}
|
|
94
|
-
};
|
|
95
|
-
function isPendingJob(error) {
|
|
96
|
-
return error instanceof PendingJob;
|
|
97
|
-
}
|
|
98
|
-
var submitMode = new import_node_async_hooks.AsyncLocalStorage();
|
|
99
|
-
function runInSubmitMode(fn) {
|
|
100
|
-
return submitMode.run(true, fn);
|
|
101
|
-
}
|
|
102
|
-
function isSubmitMode() {
|
|
103
|
-
return submitMode.getStore() === true;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// src/ports/content-generator.port.ts
|
|
107
|
-
function expiresInHours(hours) {
|
|
108
|
-
return new Date(Date.now() + hours * 36e5).toISOString();
|
|
109
|
-
}
|
|
110
|
-
|
|
111
90
|
// src/providers/byteplus.models.ts
|
|
112
91
|
var FPS = 24;
|
|
113
92
|
var PIXELS = {
|
|
@@ -860,7 +839,7 @@ var imageOmni = {
|
|
|
860
839
|
prompt: input.prompt
|
|
861
840
|
};
|
|
862
841
|
if (images.length > 0) {
|
|
863
|
-
body.image_list = images.map((
|
|
842
|
+
body.image_list = images.map((image2) => ({ image: image2 }));
|
|
864
843
|
}
|
|
865
844
|
if (elements.length > 0) {
|
|
866
845
|
body.element_list = elements.map((id) => ({ element_id: id }));
|
|
@@ -1262,6 +1241,105 @@ var QWEN_CATALOG = [
|
|
|
1262
1241
|
)
|
|
1263
1242
|
];
|
|
1264
1243
|
|
|
1244
|
+
// src/providers/topaz.models.ts
|
|
1245
|
+
var VIDEO_RES = ["720p", "1080p", "4k"];
|
|
1246
|
+
function video(id, name, kind, description) {
|
|
1247
|
+
return [id, { name, vendorModel: id, surface: "video", kind, description }];
|
|
1248
|
+
}
|
|
1249
|
+
function image(id, name, vendorModel, imagePath, description) {
|
|
1250
|
+
return [
|
|
1251
|
+
id,
|
|
1252
|
+
{ name, vendorModel, surface: "image", imagePath, description }
|
|
1253
|
+
];
|
|
1254
|
+
}
|
|
1255
|
+
var ENTRIES = [
|
|
1256
|
+
// Precision video upscale (Proteus family)
|
|
1257
|
+
video("prob-4", "Proteus", "upscale", "Default video upscale. Preserves the source."),
|
|
1258
|
+
video("pnat-1", "Proteus Natural", "upscale", "Softer Proteus. 2\xD7 only."),
|
|
1259
|
+
video("rhea-1", "Rhea", "upscale", "High-fidelity 4\xD7 upscale for fine detail."),
|
|
1260
|
+
video("thd-3", "Theia Fine Tune Detail", "upscale", "More aggressive detail."),
|
|
1261
|
+
video("thf-4", "Theia Fine Tune Fidelity", "upscale", "Lighter touch, closer to the source."),
|
|
1262
|
+
video("thm-2", "Themis 2", "upscale", "Motion deblur."),
|
|
1263
|
+
video("aaa-9", "Artemis Aliasing / Moire", "upscale", "Aliasing and moire cleanup."),
|
|
1264
|
+
video("ahq-12", "Artemis High Quality", "upscale", "Artemis for clean footage."),
|
|
1265
|
+
video("amq-13", "Artemis Medium Quality", "upscale", "Artemis for typical compression."),
|
|
1266
|
+
video("amqs-2", "Artemis Medium Halo", "upscale", "Medium halo reduction."),
|
|
1267
|
+
video("alq-13", "Artemis Low Quality", "upscale", "Artemis for degraded sources."),
|
|
1268
|
+
video("alqs-2", "Artemis Strong Halo", "upscale", "Strong halo reduction."),
|
|
1269
|
+
video("iris-3", "Iris Medium Quality", "upscale", "Face recovery, medium quality."),
|
|
1270
|
+
video("iris-2", "Iris Low Quality", "upscale", "Face recovery, low quality."),
|
|
1271
|
+
video("dtv-4", "Dione TV", "upscale", "Deinterlace broadcast footage."),
|
|
1272
|
+
video("ddv-3", "Dione DV", "upscale", "Deinterlace camcorder footage."),
|
|
1273
|
+
video("dtvs-2", "Dione Robust", "upscale", "Deinterlace degraded interlaced sources."),
|
|
1274
|
+
video("dtd-4", "Dione Dehalo", "upscale", "Deinterlace with halo cleanup."),
|
|
1275
|
+
video("dtds-2", "Dione Robust Dehalo", "upscale", "Deinterlace, degraded, with halo cleanup."),
|
|
1276
|
+
video("ghq-5", "Gaia High Quality", "upscale", "CGI and animation, high quality."),
|
|
1277
|
+
video("gcg-5", "Gaia CG", "upscale", "General CGI and renders."),
|
|
1278
|
+
video("nyx-3", "Nyx", "upscale", "Default video denoise."),
|
|
1279
|
+
video("nxl-1", "Nyx XL", "upscale", "Heavy video denoise."),
|
|
1280
|
+
video("nxhf-1", "Nyx High Fidelity", "upscale", "Light denoise, keep texture."),
|
|
1281
|
+
video("nxf-1", "Nyx Fast", "upscale", "Fast video denoise."),
|
|
1282
|
+
video("color-1", "Video Colorization", "upscale", "Black-and-white to color."),
|
|
1283
|
+
// Generative / creative video
|
|
1284
|
+
video("slp-2.6", "Starlight Precise 2.6", "upscale", "Generative upscale to 4K. Faces, texture, text."),
|
|
1285
|
+
video("slhq-1", "Starlight HQ", "upscale", "Maximum quality generative enhance."),
|
|
1286
|
+
video("slf-2", "Starlight Fast 2", "upscale", "Faster generative upscale."),
|
|
1287
|
+
video("slm-1", "Starlight Mini", "upscale", "Archival and interlaced restoration."),
|
|
1288
|
+
video("wonder-1", "Starlight Sharp", "upscale", "Local-first sharpening restore."),
|
|
1289
|
+
video("ast-2", "Astra 2", "upscale", "Creative GenAI video upscale."),
|
|
1290
|
+
// Frame interpolation — not upscalers
|
|
1291
|
+
video("aion-1", "Aion", "interpolation", "Extreme slow motion on complex, high-res motion."),
|
|
1292
|
+
video("apo-8", "Apollo", "interpolation", "Default slow-mo and frame-rate conversion."),
|
|
1293
|
+
video("apf-2", "Apollo Fast", "interpolation", "Faster Apollo."),
|
|
1294
|
+
video("chr-2", "Chronos", "interpolation", "Interpolation for harder motion."),
|
|
1295
|
+
video("chf-3", "Chronos Fast", "interpolation", "Faster Chronos."),
|
|
1296
|
+
// Image — Gigapixel / enhance
|
|
1297
|
+
image("standard-v2", "Standard 2", "Standard V2", "/image/v1/enhance/async", "Default image upscale."),
|
|
1298
|
+
image("high-fidelity-v2", "High Fidelity 2", "High Fidelity V2", "/image/v1/enhance/async", "Maximum source preservation."),
|
|
1299
|
+
image("low-resolution-v2", "Low Resolution 2", "Low Resolution V2", "/image/v1/enhance/async", "Tiny or heavily compressed sources."),
|
|
1300
|
+
image("cgi", "Art & CGI", "CGI", "/image/v1/enhance/async", "Illustrations, renders, game art."),
|
|
1301
|
+
image("text-refine", "Text & Shapes", "Text Refine", "/image/v1/enhance/async", "Documents, screenshots, type."),
|
|
1302
|
+
// Image — generative enhance
|
|
1303
|
+
image("standard-max", "Standard Max", "Standard MAX", "/image/v1/enhance-gen/async", "Default generative image upscale."),
|
|
1304
|
+
image("wonder", "Wonder", "Wonder", "/image/v1/enhance-gen/async", "One-click generative restore."),
|
|
1305
|
+
image("wonder-3.5", "Wonder 3.5", "Wonder 3.5", "/image/v1/enhance-gen/async", "Highest-quality generative image upscale."),
|
|
1306
|
+
image("redefine", "Redefine", "Redefine", "/image/v1/enhance-gen/async", "Prompt-guided generative enhance."),
|
|
1307
|
+
image("recovery-v2", "Recover 3", "Recovery V2", "/image/v1/enhance-gen/async", "Heavily damaged sources."),
|
|
1308
|
+
image("bloom-2", "Bloom 2", "Bloom 2", "/image/v1/enhance-gen/async", "Creative upscale for GenAI images."),
|
|
1309
|
+
image("denoise-max", "Denoise Max", "Denoise Max", "/image/v1/denoise-gen/async", "Highest-quality image denoise.")
|
|
1310
|
+
];
|
|
1311
|
+
var TOPAZ_MODELS = Object.fromEntries(
|
|
1312
|
+
ENTRIES.flatMap(([id, binding]) => {
|
|
1313
|
+
const rows = [[id, binding]];
|
|
1314
|
+
if (binding.surface === "image" && binding.vendorModel !== id) {
|
|
1315
|
+
rows.push([binding.vendorModel, binding]);
|
|
1316
|
+
}
|
|
1317
|
+
return rows;
|
|
1318
|
+
})
|
|
1319
|
+
);
|
|
1320
|
+
function nodeTypes(binding) {
|
|
1321
|
+
if (binding.surface === "image") return ["image_upscale"];
|
|
1322
|
+
return binding.kind === "interpolation" ? ["video_gen"] : ["video_upscale"];
|
|
1323
|
+
}
|
|
1324
|
+
var TOPAZ_CATALOG = ENTRIES.map(([id, binding]) => ({
|
|
1325
|
+
id,
|
|
1326
|
+
name: binding.name,
|
|
1327
|
+
category: binding.surface,
|
|
1328
|
+
inputType: binding.surface === "video" ? "video_required" : "image_required",
|
|
1329
|
+
nodeTypes: nodeTypes(binding),
|
|
1330
|
+
creditsPerUnit: 0,
|
|
1331
|
+
creditUnit: binding.surface === "video" ? "second" : "image",
|
|
1332
|
+
supportedResolutions: binding.surface === "video" ? [...VIDEO_RES] : void 0,
|
|
1333
|
+
provider: "topaz",
|
|
1334
|
+
description: binding.description
|
|
1335
|
+
}));
|
|
1336
|
+
var TOPAZ_VIDEO_MODELS = Object.fromEntries(
|
|
1337
|
+
ENTRIES.filter(([, binding]) => binding.surface === "video")
|
|
1338
|
+
);
|
|
1339
|
+
var TOPAZ_IMAGE_MODELS = Object.fromEntries(
|
|
1340
|
+
ENTRIES.filter(([, binding]) => binding.surface === "image")
|
|
1341
|
+
);
|
|
1342
|
+
|
|
1265
1343
|
// src/catalog.ts
|
|
1266
1344
|
var PROVIDER_BASE_URLS = {
|
|
1267
1345
|
byteplus: "https://ark.ap-southeast.bytepluses.com",
|
|
@@ -1270,6 +1348,7 @@ var PROVIDER_BASE_URLS = {
|
|
|
1270
1348
|
google: "https://generativelanguage.googleapis.com",
|
|
1271
1349
|
openai: "https://api.openai.com",
|
|
1272
1350
|
qwen: "https://dashscope-intl.aliyuncs.com",
|
|
1351
|
+
topaz: "https://api.topazlabs.com",
|
|
1273
1352
|
brotu: "https://api.brotu.app"
|
|
1274
1353
|
};
|
|
1275
1354
|
var DEFAULT_BROTU_API_URL = PROVIDER_BASE_URLS.brotu;
|
|
@@ -1287,7 +1366,8 @@ var BUILT_IN = [
|
|
|
1287
1366
|
...QWEN_CATALOG,
|
|
1288
1367
|
...OPENAI_CATALOG,
|
|
1289
1368
|
...GOOGLE_CATALOG,
|
|
1290
|
-
...ELEVENLABS_CATALOG
|
|
1369
|
+
...ELEVENLABS_CATALOG,
|
|
1370
|
+
...TOPAZ_CATALOG
|
|
1291
1371
|
];
|
|
1292
1372
|
var catalog = BUILT_IN;
|
|
1293
1373
|
var catalogMap = byId(BUILT_IN);
|
|
@@ -1357,21 +1437,79 @@ function resolveProvider(modelId, options) {
|
|
|
1357
1437
|
`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
1438
|
);
|
|
1359
1439
|
}
|
|
1440
|
+
var BROTU_SUPPORTED_CATEGORIES = ["image", "video"];
|
|
1441
|
+
function describeModels(options) {
|
|
1442
|
+
const vendors = vendorProviders(options);
|
|
1443
|
+
const platformKey = brotuApiKey(options);
|
|
1444
|
+
return catalog.map((model) => {
|
|
1445
|
+
const provider = model.provider;
|
|
1446
|
+
if (!provider) {
|
|
1447
|
+
return {
|
|
1448
|
+
model,
|
|
1449
|
+
runsOn: "(none)",
|
|
1450
|
+
runnable: false,
|
|
1451
|
+
reason: "The model declares no provider."
|
|
1452
|
+
};
|
|
1453
|
+
}
|
|
1454
|
+
if (provider in vendors) {
|
|
1455
|
+
return { model, runsOn: provider, runnable: true };
|
|
1456
|
+
}
|
|
1457
|
+
if (platformKey) {
|
|
1458
|
+
if (BROTU_SUPPORTED_CATEGORIES.includes(model.category)) {
|
|
1459
|
+
return { model, runsOn: "brotu", runnable: true };
|
|
1460
|
+
}
|
|
1461
|
+
return {
|
|
1462
|
+
model,
|
|
1463
|
+
runsOn: provider,
|
|
1464
|
+
runnable: false,
|
|
1465
|
+
reason: `Brotu generates ${BROTU_SUPPORTED_CATEGORIES.join(" and ")} only. Pass providers.${provider} to run this one.`
|
|
1466
|
+
};
|
|
1467
|
+
}
|
|
1468
|
+
return {
|
|
1469
|
+
model,
|
|
1470
|
+
runsOn: provider,
|
|
1471
|
+
runnable: false,
|
|
1472
|
+
reason: `No key for "${provider}". Pass a Brotu key or providers.${provider}.`
|
|
1473
|
+
};
|
|
1474
|
+
});
|
|
1475
|
+
}
|
|
1360
1476
|
function getAvailableModels(options) {
|
|
1361
|
-
|
|
1362
|
-
|
|
1477
|
+
return describeModels(options).filter((entry) => entry.runnable).map((entry) => entry.model);
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
// src/lib/jobs.ts
|
|
1481
|
+
var import_node_async_hooks = require("async_hooks");
|
|
1482
|
+
var PendingJob = class extends Error {
|
|
1483
|
+
taskId;
|
|
1484
|
+
pollEndpoint;
|
|
1485
|
+
constructor(taskId, pollEndpoint) {
|
|
1486
|
+
super(`Job ${taskId} was submitted and is still running.`);
|
|
1487
|
+
this.name = "PendingJob";
|
|
1488
|
+
this.taskId = taskId;
|
|
1489
|
+
this.pollEndpoint = pollEndpoint;
|
|
1363
1490
|
}
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1491
|
+
};
|
|
1492
|
+
function isPendingJob(error) {
|
|
1493
|
+
return error instanceof PendingJob;
|
|
1494
|
+
}
|
|
1495
|
+
var submitMode = new import_node_async_hooks.AsyncLocalStorage();
|
|
1496
|
+
function runInSubmitMode(fn) {
|
|
1497
|
+
return submitMode.run(true, fn);
|
|
1498
|
+
}
|
|
1499
|
+
function isSubmitMode() {
|
|
1500
|
+
return submitMode.getStore() === true;
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
// src/ports/content-generator.port.ts
|
|
1504
|
+
function expiresInHours(hours) {
|
|
1505
|
+
return new Date(Date.now() + hours * 36e5).toISOString();
|
|
1368
1506
|
}
|
|
1369
1507
|
|
|
1370
1508
|
// src/adapters/estimate.ts
|
|
1371
1509
|
function estimateFor(provider, type, params, defaults) {
|
|
1372
1510
|
const modelId = params.model ?? "";
|
|
1373
1511
|
const model = getModel(modelId);
|
|
1374
|
-
const
|
|
1512
|
+
const video2 = params;
|
|
1375
1513
|
const unit = model?.pricing?.unit ?? (type === "video" ? "second" : type === "audio" ? "character" : type === "text" ? "token" : "image");
|
|
1376
1514
|
let units = 1;
|
|
1377
1515
|
if (unit === "character") {
|
|
@@ -1379,7 +1517,7 @@ function estimateFor(provider, type, params, defaults) {
|
|
|
1379
1517
|
} else if (unit === "token") {
|
|
1380
1518
|
units = 0;
|
|
1381
1519
|
} else if (unit === "second") {
|
|
1382
|
-
units =
|
|
1520
|
+
units = video2.duration ?? defaults?.durationSeconds ?? model?.minDuration ?? 5;
|
|
1383
1521
|
}
|
|
1384
1522
|
const resolution = params.resolution;
|
|
1385
1523
|
const rate = (resolution ? model?.pricing?.byResolution?.[resolution] : void 0) ?? model?.pricing?.usdPerUnit;
|
|
@@ -1412,7 +1550,7 @@ var BrotuAdapter = class {
|
|
|
1412
1550
|
}
|
|
1413
1551
|
opts;
|
|
1414
1552
|
providerName = "brotu";
|
|
1415
|
-
supportedTypes =
|
|
1553
|
+
supportedTypes = BROTU_SUPPORTED_CATEGORIES;
|
|
1416
1554
|
workspaceIdCache;
|
|
1417
1555
|
get origin() {
|
|
1418
1556
|
return (this.opts.apiUrl ?? DEFAULT_API_URL).replace(/\/$/, "");
|
|
@@ -1635,23 +1773,23 @@ var BrotuAdapter = class {
|
|
|
1635
1773
|
}
|
|
1636
1774
|
try {
|
|
1637
1775
|
const workspaceId = await this.workspaceId();
|
|
1638
|
-
const
|
|
1639
|
-
const
|
|
1776
|
+
const video2 = params;
|
|
1777
|
+
const image2 = params;
|
|
1640
1778
|
const payload = await this.request(
|
|
1641
1779
|
this.studioPath("/api/v1/studio/estimate", workspaceId),
|
|
1642
1780
|
{
|
|
1643
1781
|
method: "POST",
|
|
1644
1782
|
body: JSON.stringify({
|
|
1645
1783
|
model: this.platformModel(params.model ?? ""),
|
|
1646
|
-
duration:
|
|
1647
|
-
resolution:
|
|
1648
|
-
aspectRatio:
|
|
1649
|
-
withAudio:
|
|
1784
|
+
duration: video2.duration,
|
|
1785
|
+
resolution: video2.resolution ?? image2.resolution,
|
|
1786
|
+
aspectRatio: video2.aspectRatio ?? image2.aspectRatio,
|
|
1787
|
+
withAudio: video2.withAudio,
|
|
1650
1788
|
hasReferenceImage: Boolean(
|
|
1651
|
-
|
|
1789
|
+
video2.imageUrl || video2.imageUrls?.length || video2.referenceImages?.length || image2.referenceImages?.length
|
|
1652
1790
|
),
|
|
1653
|
-
hasReferenceVideo: Boolean(
|
|
1654
|
-
quality:
|
|
1791
|
+
hasReferenceVideo: Boolean(video2.videoUrl || video2.videoUrls?.length),
|
|
1792
|
+
quality: image2.quality
|
|
1655
1793
|
})
|
|
1656
1794
|
}
|
|
1657
1795
|
);
|
|
@@ -2254,12 +2392,12 @@ var GoogleAdapter = class {
|
|
|
2254
2392
|
const input = [
|
|
2255
2393
|
{ type: "text", text: params.prompt }
|
|
2256
2394
|
];
|
|
2257
|
-
for (const
|
|
2258
|
-
const [meta, data] =
|
|
2395
|
+
for (const image2 of params.referenceImages ?? []) {
|
|
2396
|
+
const [meta, data] = image2.split(",");
|
|
2259
2397
|
input.push({
|
|
2260
2398
|
type: "image",
|
|
2261
2399
|
mime_type: meta?.match(/data:([^;]+)/)?.[1] ?? "image/png",
|
|
2262
|
-
data: data ??
|
|
2400
|
+
data: data ?? image2
|
|
2263
2401
|
});
|
|
2264
2402
|
}
|
|
2265
2403
|
const payload = await this.request(
|
|
@@ -2351,10 +2489,10 @@ var GoogleAdapter = class {
|
|
|
2351
2489
|
};
|
|
2352
2490
|
const first = params.imageUrl ?? params.referenceImages?.[0];
|
|
2353
2491
|
if (first) attach("image", first);
|
|
2354
|
-
for (const
|
|
2355
|
-
const
|
|
2356
|
-
if (
|
|
2357
|
-
const task =
|
|
2492
|
+
for (const image2 of params.imageUrls ?? []) attach("image", image2);
|
|
2493
|
+
const video2 = params.videoUrl ?? params.videoUrls?.[0];
|
|
2494
|
+
if (video2) attach("video", video2);
|
|
2495
|
+
const task = video2 ? "edit" : params.imageUrls?.length ? "reference_to_video" : first ? "image_to_video" : "text_to_video";
|
|
2358
2496
|
return {
|
|
2359
2497
|
model: modelId,
|
|
2360
2498
|
input,
|
|
@@ -2523,12 +2661,12 @@ var GoogleAdapter = class {
|
|
|
2523
2661
|
const input = [
|
|
2524
2662
|
{ type: "text", text: params.prompt }
|
|
2525
2663
|
];
|
|
2526
|
-
for (const
|
|
2527
|
-
const [meta, data] =
|
|
2664
|
+
for (const image2 of params.referenceImages ?? []) {
|
|
2665
|
+
const [meta, data] = image2.split(",");
|
|
2528
2666
|
input.push({
|
|
2529
2667
|
type: "image",
|
|
2530
2668
|
mime_type: meta?.match(/data:([^;]+)/)?.[1] ?? "image/png",
|
|
2531
|
-
data: data ??
|
|
2669
|
+
data: data ?? image2
|
|
2532
2670
|
});
|
|
2533
2671
|
}
|
|
2534
2672
|
return {
|
|
@@ -3338,11 +3476,11 @@ var QwenAdapter = class {
|
|
|
3338
3476
|
case "media": {
|
|
3339
3477
|
const media = [];
|
|
3340
3478
|
if (binding.mode === "video-edit") {
|
|
3341
|
-
const
|
|
3342
|
-
if (!
|
|
3479
|
+
const video2 = params.videoUrl ?? params.videoUrls?.[0];
|
|
3480
|
+
if (!video2) {
|
|
3343
3481
|
throw new Error(`Qwen "${modelId}" needs a video \u2014 pass videoUrl.`);
|
|
3344
3482
|
}
|
|
3345
|
-
media.push({ type: "video", url:
|
|
3483
|
+
media.push({ type: "video", url: video2 });
|
|
3346
3484
|
} else if (binding.mode === "r2v") {
|
|
3347
3485
|
for (const url of references) {
|
|
3348
3486
|
media.push({ type: "reference_image", url });
|
|
@@ -3382,7 +3520,7 @@ var QwenAdapter = class {
|
|
|
3382
3520
|
...output.video_url ? [output.video_url] : [],
|
|
3383
3521
|
...(output.results ?? []).map((result) => result.url).filter((url) => Boolean(url)),
|
|
3384
3522
|
...(output.choices ?? []).flatMap(
|
|
3385
|
-
(choice) => (choice.message?.content ?? []).map((part) => part.image).filter((
|
|
3523
|
+
(choice) => (choice.message?.content ?? []).map((part) => part.image).filter((image2) => Boolean(image2))
|
|
3386
3524
|
)
|
|
3387
3525
|
];
|
|
3388
3526
|
return urls.map((url) => ({
|
|
@@ -3530,8 +3668,8 @@ var QwenAdapter = class {
|
|
|
3530
3668
|
}
|
|
3531
3669
|
try {
|
|
3532
3670
|
const content = [];
|
|
3533
|
-
for (const
|
|
3534
|
-
content.push({ image });
|
|
3671
|
+
for (const image2 of params.referenceImages ?? []) {
|
|
3672
|
+
content.push({ image: image2 });
|
|
3535
3673
|
}
|
|
3536
3674
|
content.push({ text: params.prompt });
|
|
3537
3675
|
const parameters = {
|
|
@@ -3724,6 +3862,475 @@ var QwenAdapter = class {
|
|
|
3724
3862
|
}
|
|
3725
3863
|
};
|
|
3726
3864
|
|
|
3865
|
+
// src/adapters/topaz.adapter.ts
|
|
3866
|
+
var DEFAULT_BASE_URL7 = "https://api.topazlabs.com";
|
|
3867
|
+
var POLL_INTERVAL_MS6 = 5e3;
|
|
3868
|
+
var DEFAULT_MAX_POLL_ATTEMPTS6 = 240;
|
|
3869
|
+
var DEFAULT_FPS = 30;
|
|
3870
|
+
var PIXELS2 = {
|
|
3871
|
+
"720p": { width: 1280, height: 720 },
|
|
3872
|
+
"720P": { width: 1280, height: 720 },
|
|
3873
|
+
"1080p": { width: 1920, height: 1080 },
|
|
3874
|
+
"1080P": { width: 1920, height: 1080 },
|
|
3875
|
+
"4k": { width: 3840, height: 2160 },
|
|
3876
|
+
"4K": { width: 3840, height: 2160 },
|
|
3877
|
+
"1K": { width: 1024, height: 1024 },
|
|
3878
|
+
"2K": { width: 2048, height: 2048 }
|
|
3879
|
+
};
|
|
3880
|
+
var TopazAdapter = class {
|
|
3881
|
+
providerName = "topaz";
|
|
3882
|
+
supportedTypes = ["video", "image"];
|
|
3883
|
+
opts;
|
|
3884
|
+
constructor(opts) {
|
|
3885
|
+
this.opts = opts;
|
|
3886
|
+
}
|
|
3887
|
+
get baseUrl() {
|
|
3888
|
+
return (this.opts.baseUrl ?? DEFAULT_BASE_URL7).replace(/\/$/, "");
|
|
3889
|
+
}
|
|
3890
|
+
get headers() {
|
|
3891
|
+
return {
|
|
3892
|
+
"X-API-Key": this.opts.apiKey,
|
|
3893
|
+
accept: "application/json",
|
|
3894
|
+
"Content-Type": "application/json"
|
|
3895
|
+
};
|
|
3896
|
+
}
|
|
3897
|
+
binding(modelId) {
|
|
3898
|
+
const id = modelId ?? "";
|
|
3899
|
+
const found = TOPAZ_MODELS[id];
|
|
3900
|
+
if (!found) {
|
|
3901
|
+
throw new Error(
|
|
3902
|
+
`"${id}" is not a Topaz model. Known: ${TOPAZ_CATALOG.map((model) => model.id).join(", ")}.`
|
|
3903
|
+
);
|
|
3904
|
+
}
|
|
3905
|
+
return [id, found];
|
|
3906
|
+
}
|
|
3907
|
+
/** 720p / 1080p / 4k, or an explicit "1920x1080" / "1920*1080" pair. */
|
|
3908
|
+
resolutionOf(resolution) {
|
|
3909
|
+
if (!resolution) return PIXELS2["1080p"];
|
|
3910
|
+
const named = PIXELS2[resolution];
|
|
3911
|
+
if (named) return named;
|
|
3912
|
+
const match = /^(\d{2,5})[x*](\d{2,5})$/i.exec(resolution.trim());
|
|
3913
|
+
if (match?.[1] && match[2]) {
|
|
3914
|
+
return { width: Number(match[1]), height: Number(match[2]) };
|
|
3915
|
+
}
|
|
3916
|
+
throw new Error(
|
|
3917
|
+
`Topaz wants 720p, 1080p, 4k or WxH, not "${resolution}".`
|
|
3918
|
+
);
|
|
3919
|
+
}
|
|
3920
|
+
containerOf(url) {
|
|
3921
|
+
const path = url.split("?")[0]?.toLowerCase() ?? "";
|
|
3922
|
+
if (path.endsWith(".mov")) return "mov";
|
|
3923
|
+
if (path.endsWith(".mkv")) return "mkv";
|
|
3924
|
+
return "mp4";
|
|
3925
|
+
}
|
|
3926
|
+
filterOf(binding, params) {
|
|
3927
|
+
const extras = params.providerOptions?.topaz ?? {};
|
|
3928
|
+
if (binding.kind === "upscale") {
|
|
3929
|
+
return { auto: "Auto", ...extras, model: binding.vendorModel };
|
|
3930
|
+
}
|
|
3931
|
+
const slowmo = numberish(extras.slowmo) ?? 1;
|
|
3932
|
+
const fps = numberish(extras.fps) ?? DEFAULT_FPS;
|
|
3933
|
+
if (slowmo < 1 || slowmo > 16) {
|
|
3934
|
+
throw new Error(`Topaz interpolation slowmo is 1\u201316, not ${slowmo}.`);
|
|
3935
|
+
}
|
|
3936
|
+
if (fps < 15 || fps > 240) {
|
|
3937
|
+
throw new Error(`Topaz interpolation fps is 15\u2013240, not ${fps}.`);
|
|
3938
|
+
}
|
|
3939
|
+
const filter = {
|
|
3940
|
+
model: binding.vendorModel,
|
|
3941
|
+
slowmo,
|
|
3942
|
+
fps
|
|
3943
|
+
};
|
|
3944
|
+
if (typeof extras.duplicate === "boolean") {
|
|
3945
|
+
filter.duplicate = extras.duplicate;
|
|
3946
|
+
}
|
|
3947
|
+
if (extras.duplicateThreshold !== void 0) {
|
|
3948
|
+
filter.duplicateThreshold = extras.duplicateThreshold;
|
|
3949
|
+
}
|
|
3950
|
+
return filter;
|
|
3951
|
+
}
|
|
3952
|
+
requestBody(binding, params, sourceUrl) {
|
|
3953
|
+
const extras = params.providerOptions?.topaz ?? {};
|
|
3954
|
+
const fps = numberish(extras.fps) ?? DEFAULT_FPS;
|
|
3955
|
+
return {
|
|
3956
|
+
source: { container: this.containerOf(sourceUrl) },
|
|
3957
|
+
filters: [this.filterOf(binding, params)],
|
|
3958
|
+
output: {
|
|
3959
|
+
resolution: this.resolutionOf(params.resolution),
|
|
3960
|
+
frameRate: fps,
|
|
3961
|
+
audioCodec: "AAC",
|
|
3962
|
+
audioTransfer: "Copy",
|
|
3963
|
+
dynamicCompressionLevel: "High",
|
|
3964
|
+
container: "mp4"
|
|
3965
|
+
}
|
|
3966
|
+
};
|
|
3967
|
+
}
|
|
3968
|
+
async request(path, init) {
|
|
3969
|
+
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
3970
|
+
method: init?.method ?? "GET",
|
|
3971
|
+
headers: this.headers,
|
|
3972
|
+
body: init?.body !== void 0 ? JSON.stringify(init.body) : void 0
|
|
3973
|
+
});
|
|
3974
|
+
const payload = await response.json();
|
|
3975
|
+
if (!response.ok) {
|
|
3976
|
+
throw new Error(
|
|
3977
|
+
payload.message ?? `Topaz returned ${response.status} for ${path}.`
|
|
3978
|
+
);
|
|
3979
|
+
}
|
|
3980
|
+
return payload;
|
|
3981
|
+
}
|
|
3982
|
+
async downloadSource(url) {
|
|
3983
|
+
const response = await fetch(url);
|
|
3984
|
+
if (!response.ok) {
|
|
3985
|
+
throw new Error(`Could not download the source video (${response.status}).`);
|
|
3986
|
+
}
|
|
3987
|
+
return {
|
|
3988
|
+
bytes: await response.arrayBuffer(),
|
|
3989
|
+
contentType: response.headers.get("content-type") ?? "video/mp4"
|
|
3990
|
+
};
|
|
3991
|
+
}
|
|
3992
|
+
async submitTask(params) {
|
|
3993
|
+
const [, binding] = this.binding(params.model);
|
|
3994
|
+
if (binding.surface !== "video") {
|
|
3995
|
+
throw new Error(
|
|
3996
|
+
`Topaz "${params.model}" is an image model. Use ai.image.`
|
|
3997
|
+
);
|
|
3998
|
+
}
|
|
3999
|
+
const sourceUrl = params.videoUrl ?? params.videoUrls?.[0];
|
|
4000
|
+
if (!sourceUrl) {
|
|
4001
|
+
throw new Error(
|
|
4002
|
+
`Topaz "${params.model}" needs a source video \u2014 pass videoUrl.`
|
|
4003
|
+
);
|
|
4004
|
+
}
|
|
4005
|
+
const source = await this.downloadSource(sourceUrl);
|
|
4006
|
+
const created = await this.request("/video/express", {
|
|
4007
|
+
method: "POST",
|
|
4008
|
+
body: this.requestBody(binding, params, sourceUrl)
|
|
4009
|
+
});
|
|
4010
|
+
const taskId = created.requestId?.trim();
|
|
4011
|
+
const uploadUrl = created.uploadUrls?.[0];
|
|
4012
|
+
if (!taskId || !uploadUrl) {
|
|
4013
|
+
throw new Error(
|
|
4014
|
+
"Topaz accepted the request but returned no request id or upload URL."
|
|
4015
|
+
);
|
|
4016
|
+
}
|
|
4017
|
+
const uploaded = await fetch(uploadUrl, {
|
|
4018
|
+
method: "PUT",
|
|
4019
|
+
headers: { "Content-Type": source.contentType },
|
|
4020
|
+
body: source.bytes
|
|
4021
|
+
});
|
|
4022
|
+
if (!uploaded.ok) {
|
|
4023
|
+
throw new Error(`Topaz upload failed (${uploaded.status}).`);
|
|
4024
|
+
}
|
|
4025
|
+
return { taskId, pollEndpoint: `/video/${taskId}/status` };
|
|
4026
|
+
}
|
|
4027
|
+
outputsFrom(payload, job) {
|
|
4028
|
+
const url = payload.download?.url;
|
|
4029
|
+
if (!url) return [];
|
|
4030
|
+
const expiresAtMs = payload.download?.expiresAt;
|
|
4031
|
+
return [
|
|
4032
|
+
{
|
|
4033
|
+
url,
|
|
4034
|
+
mimeType: "video/mp4",
|
|
4035
|
+
taskId: job.id,
|
|
4036
|
+
expiresAt: expiresAtMs ? new Date(expiresAtMs).toISOString() : expiresInHours(24),
|
|
4037
|
+
raw: {
|
|
4038
|
+
status: payload.status,
|
|
4039
|
+
progress: payload.progress,
|
|
4040
|
+
estimates: payload.estimates,
|
|
4041
|
+
outputSize: payload.outputSize
|
|
4042
|
+
}
|
|
4043
|
+
}
|
|
4044
|
+
];
|
|
4045
|
+
}
|
|
4046
|
+
snapshot(payload, job) {
|
|
4047
|
+
const raw = (payload.status ?? "").toLowerCase();
|
|
4048
|
+
if (raw === "failed" || raw === "canceled") {
|
|
4049
|
+
return {
|
|
4050
|
+
status: "failed",
|
|
4051
|
+
error: payload.message ?? `Topaz request ${raw}.`
|
|
4052
|
+
};
|
|
4053
|
+
}
|
|
4054
|
+
if (raw !== "complete") return { status: "pending" };
|
|
4055
|
+
const outputs = this.outputsFrom(payload, job);
|
|
4056
|
+
if (outputs.length === 0) return { status: "pending" };
|
|
4057
|
+
return {
|
|
4058
|
+
status: "succeeded",
|
|
4059
|
+
result: {
|
|
4060
|
+
success: true,
|
|
4061
|
+
outputs,
|
|
4062
|
+
creditsUsed: 0,
|
|
4063
|
+
provider: this.providerName,
|
|
4064
|
+
model: job.model,
|
|
4065
|
+
processingTimeMs: 0
|
|
4066
|
+
}
|
|
4067
|
+
};
|
|
4068
|
+
}
|
|
4069
|
+
async run(params) {
|
|
4070
|
+
const startedAt = Date.now();
|
|
4071
|
+
let modelId = params.model ?? "(none)";
|
|
4072
|
+
const failure = (error) => ({
|
|
4073
|
+
success: false,
|
|
4074
|
+
outputs: [],
|
|
4075
|
+
creditsUsed: 0,
|
|
4076
|
+
provider: this.providerName,
|
|
4077
|
+
model: modelId,
|
|
4078
|
+
processingTimeMs: Date.now() - startedAt,
|
|
4079
|
+
error
|
|
4080
|
+
});
|
|
4081
|
+
let submitted;
|
|
4082
|
+
try {
|
|
4083
|
+
modelId = this.binding(params.model)[0];
|
|
4084
|
+
submitted = await this.submitTask(params);
|
|
4085
|
+
} catch (error) {
|
|
4086
|
+
if (isPendingJob(error)) throw error;
|
|
4087
|
+
return failure(error instanceof Error ? error.message : String(error));
|
|
4088
|
+
}
|
|
4089
|
+
if (isSubmitMode()) {
|
|
4090
|
+
throw new PendingJob(submitted.taskId, submitted.pollEndpoint);
|
|
4091
|
+
}
|
|
4092
|
+
const job = {
|
|
4093
|
+
id: submitted.taskId,
|
|
4094
|
+
provider: this.providerName,
|
|
4095
|
+
model: modelId,
|
|
4096
|
+
kind: "video",
|
|
4097
|
+
pollEndpoint: submitted.pollEndpoint,
|
|
4098
|
+
params,
|
|
4099
|
+
submittedAt: new Date(startedAt).toISOString()
|
|
4100
|
+
};
|
|
4101
|
+
const maxAttempts = this.opts.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS6;
|
|
4102
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
4103
|
+
const snapshot = await this.completeJob(job);
|
|
4104
|
+
if (snapshot.status === "failed") {
|
|
4105
|
+
return failure(snapshot.error ?? "Topaz request failed.");
|
|
4106
|
+
}
|
|
4107
|
+
if (snapshot.status === "succeeded" && snapshot.result) {
|
|
4108
|
+
return { ...snapshot.result, processingTimeMs: Date.now() - startedAt };
|
|
4109
|
+
}
|
|
4110
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS6));
|
|
4111
|
+
}
|
|
4112
|
+
return failure(
|
|
4113
|
+
`Topaz request ${submitted.taskId} did not finish after ${maxAttempts} checks.`
|
|
4114
|
+
);
|
|
4115
|
+
}
|
|
4116
|
+
async completeJob(job) {
|
|
4117
|
+
try {
|
|
4118
|
+
if (job.pollEndpoint?.startsWith("/image/")) {
|
|
4119
|
+
return this.imageSnapshot(job);
|
|
4120
|
+
}
|
|
4121
|
+
const payload = await this.request(
|
|
4122
|
+
job.pollEndpoint ?? `/video/${job.id}/status`
|
|
4123
|
+
);
|
|
4124
|
+
return this.snapshot(payload, job);
|
|
4125
|
+
} catch (error) {
|
|
4126
|
+
return {
|
|
4127
|
+
status: "failed",
|
|
4128
|
+
error: error instanceof Error ? error.message : String(error)
|
|
4129
|
+
};
|
|
4130
|
+
}
|
|
4131
|
+
}
|
|
4132
|
+
generateVideo(params) {
|
|
4133
|
+
return this.run(params);
|
|
4134
|
+
}
|
|
4135
|
+
imageBody(binding, params, sourceUrl) {
|
|
4136
|
+
const extras = params.providerOptions?.topaz ?? {};
|
|
4137
|
+
const pixels = params.resolution ? this.resolutionOf(params.resolution) : void 0;
|
|
4138
|
+
const fields = {
|
|
4139
|
+
source_url: sourceUrl,
|
|
4140
|
+
model: binding.vendorModel,
|
|
4141
|
+
output_format: params.outputFormat === "png" ? "png" : params.outputFormat === "jpeg" ? "jpeg" : "jpeg"
|
|
4142
|
+
};
|
|
4143
|
+
if (pixels) {
|
|
4144
|
+
fields.output_width = String(pixels.width);
|
|
4145
|
+
fields.output_height = String(pixels.height);
|
|
4146
|
+
}
|
|
4147
|
+
for (const [key, value] of Object.entries(extras)) {
|
|
4148
|
+
if (value === void 0) continue;
|
|
4149
|
+
fields[key] = typeof value === "string" ? value : String(value);
|
|
4150
|
+
}
|
|
4151
|
+
return fields;
|
|
4152
|
+
}
|
|
4153
|
+
async submitImage(params) {
|
|
4154
|
+
const [, binding] = this.binding(params.model);
|
|
4155
|
+
if (binding.surface !== "image" || !binding.imagePath) {
|
|
4156
|
+
throw new Error(
|
|
4157
|
+
`Topaz "${params.model}" is a video model. Use ai.video with videoUrl.`
|
|
4158
|
+
);
|
|
4159
|
+
}
|
|
4160
|
+
const sourceUrl = params.referenceImages?.[0];
|
|
4161
|
+
if (!sourceUrl) {
|
|
4162
|
+
throw new Error(
|
|
4163
|
+
`Topaz "${params.model}" needs a source image \u2014 pass referenceImages.`
|
|
4164
|
+
);
|
|
4165
|
+
}
|
|
4166
|
+
const form = new FormData();
|
|
4167
|
+
for (const [key, value] of Object.entries(
|
|
4168
|
+
this.imageBody(binding, params, sourceUrl)
|
|
4169
|
+
)) {
|
|
4170
|
+
form.set(key, value);
|
|
4171
|
+
}
|
|
4172
|
+
const response = await fetch(`${this.baseUrl}${binding.imagePath}`, {
|
|
4173
|
+
method: "POST",
|
|
4174
|
+
headers: {
|
|
4175
|
+
"X-API-Key": this.opts.apiKey,
|
|
4176
|
+
accept: "application/json"
|
|
4177
|
+
},
|
|
4178
|
+
body: form
|
|
4179
|
+
});
|
|
4180
|
+
const payload = await response.json();
|
|
4181
|
+
if (!response.ok) {
|
|
4182
|
+
throw new Error(
|
|
4183
|
+
payload.message ?? `Topaz returned ${response.status} enhancing the image.`
|
|
4184
|
+
);
|
|
4185
|
+
}
|
|
4186
|
+
const taskId = payload.process_id?.trim();
|
|
4187
|
+
if (!taskId) {
|
|
4188
|
+
throw new Error(
|
|
4189
|
+
"Topaz accepted the image but returned no process id."
|
|
4190
|
+
);
|
|
4191
|
+
}
|
|
4192
|
+
return { taskId, pollEndpoint: `/image/v1/status/${taskId}` };
|
|
4193
|
+
}
|
|
4194
|
+
async imageSnapshot(job) {
|
|
4195
|
+
const payload = await this.request(
|
|
4196
|
+
job.pollEndpoint ?? `/image/v1/status/${job.id}`
|
|
4197
|
+
);
|
|
4198
|
+
const raw = (payload.status ?? "").toLowerCase();
|
|
4199
|
+
if (raw === "failed" || raw === "cancelled") {
|
|
4200
|
+
return {
|
|
4201
|
+
status: "failed",
|
|
4202
|
+
error: `Topaz image ${raw}.`
|
|
4203
|
+
};
|
|
4204
|
+
}
|
|
4205
|
+
if (raw !== "completed") return { status: "pending" };
|
|
4206
|
+
let url = payload.download_url;
|
|
4207
|
+
let expiresAt = payload.eta ? new Date(payload.eta > 1e10 ? payload.eta : payload.eta * 1e3).toISOString() : void 0;
|
|
4208
|
+
if (!url) {
|
|
4209
|
+
const download = await this.request(
|
|
4210
|
+
`/image/v1/download/${job.id}`
|
|
4211
|
+
);
|
|
4212
|
+
url = download.download_url;
|
|
4213
|
+
if (download.expiry) {
|
|
4214
|
+
expiresAt = new Date(
|
|
4215
|
+
download.expiry > 1e10 ? download.expiry : download.expiry * 1e3
|
|
4216
|
+
).toISOString();
|
|
4217
|
+
}
|
|
4218
|
+
}
|
|
4219
|
+
if (!url) return { status: "pending" };
|
|
4220
|
+
return {
|
|
4221
|
+
status: "succeeded",
|
|
4222
|
+
result: {
|
|
4223
|
+
success: true,
|
|
4224
|
+
outputs: [
|
|
4225
|
+
{
|
|
4226
|
+
url,
|
|
4227
|
+
mimeType: "image/jpeg",
|
|
4228
|
+
taskId: job.id,
|
|
4229
|
+
expiresAt: expiresAt ?? expiresInHours(1),
|
|
4230
|
+
raw: {
|
|
4231
|
+
status: payload.status,
|
|
4232
|
+
progress: payload.progress,
|
|
4233
|
+
model: payload.model
|
|
4234
|
+
}
|
|
4235
|
+
}
|
|
4236
|
+
],
|
|
4237
|
+
creditsUsed: payload.credits ?? 0,
|
|
4238
|
+
provider: this.providerName,
|
|
4239
|
+
model: job.model,
|
|
4240
|
+
processingTimeMs: 0
|
|
4241
|
+
}
|
|
4242
|
+
};
|
|
4243
|
+
}
|
|
4244
|
+
async runImage(params) {
|
|
4245
|
+
const startedAt = Date.now();
|
|
4246
|
+
let modelId = params.model ?? "(none)";
|
|
4247
|
+
const failure = (error) => ({
|
|
4248
|
+
success: false,
|
|
4249
|
+
outputs: [],
|
|
4250
|
+
creditsUsed: 0,
|
|
4251
|
+
provider: this.providerName,
|
|
4252
|
+
model: modelId,
|
|
4253
|
+
processingTimeMs: Date.now() - startedAt,
|
|
4254
|
+
error
|
|
4255
|
+
});
|
|
4256
|
+
let submitted;
|
|
4257
|
+
try {
|
|
4258
|
+
modelId = this.binding(params.model)[0];
|
|
4259
|
+
submitted = await this.submitImage(params);
|
|
4260
|
+
} catch (error) {
|
|
4261
|
+
if (isPendingJob(error)) throw error;
|
|
4262
|
+
return failure(error instanceof Error ? error.message : String(error));
|
|
4263
|
+
}
|
|
4264
|
+
if (isSubmitMode()) {
|
|
4265
|
+
throw new PendingJob(submitted.taskId, submitted.pollEndpoint);
|
|
4266
|
+
}
|
|
4267
|
+
const job = {
|
|
4268
|
+
id: submitted.taskId,
|
|
4269
|
+
provider: this.providerName,
|
|
4270
|
+
model: modelId,
|
|
4271
|
+
kind: "image",
|
|
4272
|
+
pollEndpoint: submitted.pollEndpoint,
|
|
4273
|
+
params,
|
|
4274
|
+
submittedAt: new Date(startedAt).toISOString()
|
|
4275
|
+
};
|
|
4276
|
+
const maxAttempts = this.opts.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS6;
|
|
4277
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
4278
|
+
const snapshot = await this.completeJob(job);
|
|
4279
|
+
if (snapshot.status === "failed") {
|
|
4280
|
+
return failure(snapshot.error ?? "Topaz image failed.");
|
|
4281
|
+
}
|
|
4282
|
+
if (snapshot.status === "succeeded" && snapshot.result) {
|
|
4283
|
+
return { ...snapshot.result, processingTimeMs: Date.now() - startedAt };
|
|
4284
|
+
}
|
|
4285
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS6));
|
|
4286
|
+
}
|
|
4287
|
+
return failure(
|
|
4288
|
+
`Topaz image ${submitted.taskId} did not finish after ${maxAttempts} checks.`
|
|
4289
|
+
);
|
|
4290
|
+
}
|
|
4291
|
+
generateImage(params) {
|
|
4292
|
+
return this.runImage(params);
|
|
4293
|
+
}
|
|
4294
|
+
generateText(params) {
|
|
4295
|
+
return Promise.resolve(unsupported(this.providerName, params.model, "text"));
|
|
4296
|
+
}
|
|
4297
|
+
generateAudio(params) {
|
|
4298
|
+
return Promise.resolve(unsupported(this.providerName, params.model, "audio"));
|
|
4299
|
+
}
|
|
4300
|
+
estimateCost(type, params) {
|
|
4301
|
+
return Promise.resolve(estimateFor(this.providerName, type, params));
|
|
4302
|
+
}
|
|
4303
|
+
supportsModel(model) {
|
|
4304
|
+
return model in TOPAZ_MODELS;
|
|
4305
|
+
}
|
|
4306
|
+
getAvailableModels() {
|
|
4307
|
+
return TOPAZ_CATALOG.map((model) => ({
|
|
4308
|
+
id: model.id,
|
|
4309
|
+
name: model.name,
|
|
4310
|
+
type: model.category
|
|
4311
|
+
}));
|
|
4312
|
+
}
|
|
4313
|
+
};
|
|
4314
|
+
function numberish(value) {
|
|
4315
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
4316
|
+
if (typeof value === "string" && value.length > 0) {
|
|
4317
|
+
const parsed = Number(value);
|
|
4318
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
4319
|
+
}
|
|
4320
|
+
return void 0;
|
|
4321
|
+
}
|
|
4322
|
+
function unsupported(provider, model, kind) {
|
|
4323
|
+
return {
|
|
4324
|
+
success: false,
|
|
4325
|
+
outputs: [],
|
|
4326
|
+
creditsUsed: 0,
|
|
4327
|
+
provider,
|
|
4328
|
+
model: model ?? "",
|
|
4329
|
+
processingTimeMs: 0,
|
|
4330
|
+
error: `Topaz does not generate ${kind} on this adapter.`
|
|
4331
|
+
};
|
|
4332
|
+
}
|
|
4333
|
+
|
|
3727
4334
|
// src/helpers/result.ts
|
|
3728
4335
|
function ok(data) {
|
|
3729
4336
|
return { data, error: null };
|
|
@@ -3740,6 +4347,22 @@ function failFrom(code, cause, context) {
|
|
|
3740
4347
|
});
|
|
3741
4348
|
}
|
|
3742
4349
|
|
|
4350
|
+
// src/lib/hooks.ts
|
|
4351
|
+
function hookName(kind, stage) {
|
|
4352
|
+
return `on${kind[0].toUpperCase()}${kind.slice(1)}${stage}`;
|
|
4353
|
+
}
|
|
4354
|
+
function findHook(hooks, kind, stage) {
|
|
4355
|
+
if (!hooks || !kind) return void 0;
|
|
4356
|
+
return hooks[hookName(kind, stage)];
|
|
4357
|
+
}
|
|
4358
|
+
async function runHook(hook, event) {
|
|
4359
|
+
if (!hook) return;
|
|
4360
|
+
try {
|
|
4361
|
+
await hook(event);
|
|
4362
|
+
} catch {
|
|
4363
|
+
}
|
|
4364
|
+
}
|
|
4365
|
+
|
|
3743
4366
|
// src/lib/storage.ts
|
|
3744
4367
|
async function loadS3(config) {
|
|
3745
4368
|
let s3;
|
|
@@ -3908,9 +4531,10 @@ var NATIVE_PROVIDERS = [
|
|
|
3908
4531
|
"google",
|
|
3909
4532
|
"kling",
|
|
3910
4533
|
"openai",
|
|
3911
|
-
"qwen"
|
|
4534
|
+
"qwen",
|
|
4535
|
+
"topaz"
|
|
3912
4536
|
];
|
|
3913
|
-
function
|
|
4537
|
+
function brotu(options) {
|
|
3914
4538
|
const apiKey = options.apiKey?.trim();
|
|
3915
4539
|
if (!apiKey) {
|
|
3916
4540
|
throw new Error(
|
|
@@ -4007,6 +4631,12 @@ function brotuClient(options) {
|
|
|
4007
4631
|
baseUrl: provider.baseUrl
|
|
4008
4632
|
});
|
|
4009
4633
|
}
|
|
4634
|
+
if (provider.id === "topaz") {
|
|
4635
|
+
return new TopazAdapter({
|
|
4636
|
+
apiKey: provider.apiKey,
|
|
4637
|
+
baseUrl: provider.baseUrl
|
|
4638
|
+
});
|
|
4639
|
+
}
|
|
4010
4640
|
return void 0;
|
|
4011
4641
|
}
|
|
4012
4642
|
function generateWith(adapter, kind, params) {
|
|
@@ -4023,7 +4653,10 @@ function brotuClient(options) {
|
|
|
4023
4653
|
}
|
|
4024
4654
|
async function notifySettled(input) {
|
|
4025
4655
|
const config = webhookFor(input.params);
|
|
4026
|
-
|
|
4656
|
+
const kind = input.kind ?? input.job?.kind;
|
|
4657
|
+
const stage = input.event === "generation.succeeded" ? "Success" : "Error";
|
|
4658
|
+
const hook = findHook(options.hooks, kind, stage);
|
|
4659
|
+
if (!config && !hook) return;
|
|
4027
4660
|
const jobId = input.job?.id;
|
|
4028
4661
|
if (jobId) {
|
|
4029
4662
|
if (notifiedJobIds.has(jobId)) return;
|
|
@@ -4034,14 +4667,38 @@ function brotuClient(options) {
|
|
|
4034
4667
|
jobId,
|
|
4035
4668
|
provider: input.provider ?? input.job?.provider,
|
|
4036
4669
|
model: input.model ?? input.job?.model,
|
|
4037
|
-
kind
|
|
4670
|
+
kind,
|
|
4038
4671
|
outputs: input.outputs,
|
|
4039
4672
|
error: input.error ? { code: input.error.code, message: input.error.message } : void 0,
|
|
4040
4673
|
metadata: input.metadata ?? input.job?.metadata,
|
|
4041
4674
|
processingTimeMs: input.processingTimeMs,
|
|
4042
4675
|
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4043
4676
|
};
|
|
4044
|
-
|
|
4677
|
+
if (hook && kind) {
|
|
4678
|
+
await runHook(hook, {
|
|
4679
|
+
kind,
|
|
4680
|
+
stage,
|
|
4681
|
+
provider: payload.provider ?? "",
|
|
4682
|
+
model: payload.model ?? "",
|
|
4683
|
+
jobId: payload.jobId,
|
|
4684
|
+
outputs: payload.outputs,
|
|
4685
|
+
error: payload.error,
|
|
4686
|
+
metadata: payload.metadata,
|
|
4687
|
+
processingTimeMs: payload.processingTimeMs,
|
|
4688
|
+
at: payload.completedAt
|
|
4689
|
+
});
|
|
4690
|
+
}
|
|
4691
|
+
if (config) await deliverWebhook(config, payload);
|
|
4692
|
+
}
|
|
4693
|
+
function notifyLoading(kind, routed, params) {
|
|
4694
|
+
return runHook(findHook(options.hooks, kind, "Loading"), {
|
|
4695
|
+
kind,
|
|
4696
|
+
stage: "Loading",
|
|
4697
|
+
provider: routed.provider,
|
|
4698
|
+
model: routed.model,
|
|
4699
|
+
metadata: params.metadata,
|
|
4700
|
+
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
4701
|
+
});
|
|
4045
4702
|
}
|
|
4046
4703
|
async function toGeneration(raw, metadata) {
|
|
4047
4704
|
if (!raw.success) {
|
|
@@ -4064,6 +4721,7 @@ function brotuClient(options) {
|
|
|
4064
4721
|
async function generate(kind, params) {
|
|
4065
4722
|
const routed = route(params.model);
|
|
4066
4723
|
if (routed.error) return fail(routed.error);
|
|
4724
|
+
await notifyLoading(kind, routed.data, params);
|
|
4067
4725
|
try {
|
|
4068
4726
|
const result = await toGeneration(
|
|
4069
4727
|
await generateWith(routed.data.adapter, kind, params),
|
|
@@ -4114,6 +4772,7 @@ function brotuClient(options) {
|
|
|
4114
4772
|
async function submit(kind, params) {
|
|
4115
4773
|
const routed = route(params.model);
|
|
4116
4774
|
if (routed.error) return fail(routed.error);
|
|
4775
|
+
await notifyLoading(kind, routed.data, params);
|
|
4117
4776
|
const base = {
|
|
4118
4777
|
provider: routed.data.provider,
|
|
4119
4778
|
model: routed.data.model,
|
|
@@ -4285,24 +4944,88 @@ function brotuClient(options) {
|
|
|
4285
4944
|
}
|
|
4286
4945
|
};
|
|
4287
4946
|
}
|
|
4947
|
+
function listFor(category) {
|
|
4948
|
+
return describeModels(optionsWithKey).filter(
|
|
4949
|
+
(entry) => entry.model.category === category
|
|
4950
|
+
);
|
|
4951
|
+
}
|
|
4288
4952
|
return {
|
|
4289
4953
|
google: googleNamespace(),
|
|
4290
4954
|
kling: klingNamespace(),
|
|
4291
4955
|
image: {
|
|
4292
4956
|
submit: (params) => submit("image", params),
|
|
4293
|
-
generate: (params) => generate("image", params)
|
|
4957
|
+
generate: (params) => generate("image", params),
|
|
4958
|
+
list: () => listFor("image"),
|
|
4959
|
+
upscale: (params) => {
|
|
4960
|
+
if (!params.imageUrl?.trim()) {
|
|
4961
|
+
return Promise.resolve(
|
|
4962
|
+
fail({
|
|
4963
|
+
code: "invalid_request",
|
|
4964
|
+
message: "image.upscale needs imageUrl.",
|
|
4965
|
+
model: params.model
|
|
4966
|
+
})
|
|
4967
|
+
);
|
|
4968
|
+
}
|
|
4969
|
+
const model = getModel(params.model);
|
|
4970
|
+
if (model && !model.nodeTypes.includes("image_upscale")) {
|
|
4971
|
+
return Promise.resolve(
|
|
4972
|
+
fail({
|
|
4973
|
+
code: "invalid_request",
|
|
4974
|
+
message: `"${params.model}" is not an image upscale model.`,
|
|
4975
|
+
model: params.model
|
|
4976
|
+
})
|
|
4977
|
+
);
|
|
4978
|
+
}
|
|
4979
|
+
return generate("image", {
|
|
4980
|
+
...params,
|
|
4981
|
+
prompt: params.prompt ?? "",
|
|
4982
|
+
referenceImages: [
|
|
4983
|
+
params.imageUrl,
|
|
4984
|
+
...params.referenceImages ?? []
|
|
4985
|
+
]
|
|
4986
|
+
});
|
|
4987
|
+
}
|
|
4294
4988
|
},
|
|
4295
4989
|
video: {
|
|
4296
4990
|
submit: (params) => submit("video", params),
|
|
4297
|
-
generate: (params) => generate("video", params)
|
|
4991
|
+
generate: (params) => generate("video", params),
|
|
4992
|
+
list: () => listFor("video"),
|
|
4993
|
+
upscale: (params) => {
|
|
4994
|
+
if (!params.videoUrl?.trim()) {
|
|
4995
|
+
return Promise.resolve(
|
|
4996
|
+
fail({
|
|
4997
|
+
code: "invalid_request",
|
|
4998
|
+
message: "video.upscale needs videoUrl.",
|
|
4999
|
+
model: params.model
|
|
5000
|
+
})
|
|
5001
|
+
);
|
|
5002
|
+
}
|
|
5003
|
+
const model = getModel(params.model);
|
|
5004
|
+
if (model && !model.nodeTypes.includes("video_upscale")) {
|
|
5005
|
+
return Promise.resolve(
|
|
5006
|
+
fail({
|
|
5007
|
+
code: "invalid_request",
|
|
5008
|
+
message: `"${params.model}" is not a video upscale model. Interpolation models use ai.video.submit.`,
|
|
5009
|
+
model: params.model
|
|
5010
|
+
})
|
|
5011
|
+
);
|
|
5012
|
+
}
|
|
5013
|
+
return submit("video", {
|
|
5014
|
+
...params,
|
|
5015
|
+
prompt: params.prompt ?? "",
|
|
5016
|
+
videoUrl: params.videoUrl
|
|
5017
|
+
});
|
|
5018
|
+
}
|
|
4298
5019
|
},
|
|
4299
5020
|
text: {
|
|
4300
5021
|
submit: (params) => submit("text", params),
|
|
4301
|
-
generate: (params) => generate("text", params)
|
|
5022
|
+
generate: (params) => generate("text", params),
|
|
5023
|
+
list: () => listFor("text")
|
|
4302
5024
|
},
|
|
4303
5025
|
audio: {
|
|
4304
5026
|
submit: (params) => submit("audio", params),
|
|
4305
|
-
generate: (params) => generate("audio", params)
|
|
5027
|
+
generate: (params) => generate("audio", params),
|
|
5028
|
+
list: () => listFor("audio")
|
|
4306
5029
|
},
|
|
4307
5030
|
webhook: {
|
|
4308
5031
|
set(value) {
|
|
@@ -4369,6 +5092,7 @@ function brotuClient(options) {
|
|
|
4369
5092
|
}
|
|
4370
5093
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4371
5094
|
0 && (module.exports = {
|
|
5095
|
+
BROTU_SUPPORTED_CATEGORIES,
|
|
4372
5096
|
BYTEPLUS_CATALOG,
|
|
4373
5097
|
BYTEPLUS_MODELS,
|
|
4374
5098
|
BrotuAdapter,
|
|
@@ -4400,9 +5124,15 @@ function brotuClient(options) {
|
|
|
4400
5124
|
QWEN_TEXT_MODELS,
|
|
4401
5125
|
QWEN_VIDEO_MODELS,
|
|
4402
5126
|
QwenAdapter,
|
|
4403
|
-
|
|
5127
|
+
TOPAZ_CATALOG,
|
|
5128
|
+
TOPAZ_IMAGE_MODELS,
|
|
5129
|
+
TOPAZ_MODELS,
|
|
5130
|
+
TOPAZ_VIDEO_MODELS,
|
|
5131
|
+
TopazAdapter,
|
|
5132
|
+
brotu,
|
|
4404
5133
|
createS3Storage,
|
|
4405
5134
|
deliverWebhook,
|
|
5135
|
+
describeModels,
|
|
4406
5136
|
fail,
|
|
4407
5137
|
getAvailableModels,
|
|
4408
5138
|
getModel,
|