@brotu/ai 0.5.0 → 0.7.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 +23 -6
- package/dist/adapters/brotu.adapter.d.ts.map +1 -1
- package/dist/adapters/estimate.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 +102 -1
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +1 -1
- package/dist/{chunk-3MC3RDAJ.js → chunk-QQIHMDJC.js} +107 -2
- package/dist/client.d.ts +12 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/helpers/result.d.ts +6 -0
- package/dist/helpers/result.d.ts.map +1 -1
- package/dist/index.cjs +696 -35
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +566 -7
- package/dist/lib/hooks.d.ts +2 -0
- package/dist/lib/hooks.d.ts.map +1 -1
- package/dist/lib/webhook.d.ts +2 -0
- package/dist/lib/webhook.d.ts.map +1 -1
- package/dist/ports/content-generator.port.d.ts +26 -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/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -62,6 +62,11 @@ __export(index_exports, {
|
|
|
62
62
|
QWEN_TEXT_MODELS: () => QWEN_TEXT_MODELS,
|
|
63
63
|
QWEN_VIDEO_MODELS: () => QWEN_VIDEO_MODELS,
|
|
64
64
|
QwenAdapter: () => QwenAdapter,
|
|
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,
|
|
65
70
|
brotu: () => brotu,
|
|
66
71
|
createS3Storage: () => createS3Storage,
|
|
67
72
|
deliverWebhook: () => deliverWebhook,
|
|
@@ -834,7 +839,7 @@ var imageOmni = {
|
|
|
834
839
|
prompt: input.prompt
|
|
835
840
|
};
|
|
836
841
|
if (images.length > 0) {
|
|
837
|
-
body.image_list = images.map((
|
|
842
|
+
body.image_list = images.map((image2) => ({ image: image2 }));
|
|
838
843
|
}
|
|
839
844
|
if (elements.length > 0) {
|
|
840
845
|
body.element_list = elements.map((id) => ({ element_id: id }));
|
|
@@ -1236,6 +1241,105 @@ var QWEN_CATALOG = [
|
|
|
1236
1241
|
)
|
|
1237
1242
|
];
|
|
1238
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
|
+
|
|
1239
1343
|
// src/catalog.ts
|
|
1240
1344
|
var PROVIDER_BASE_URLS = {
|
|
1241
1345
|
byteplus: "https://ark.ap-southeast.bytepluses.com",
|
|
@@ -1244,6 +1348,7 @@ var PROVIDER_BASE_URLS = {
|
|
|
1244
1348
|
google: "https://generativelanguage.googleapis.com",
|
|
1245
1349
|
openai: "https://api.openai.com",
|
|
1246
1350
|
qwen: "https://dashscope-intl.aliyuncs.com",
|
|
1351
|
+
topaz: "https://api.topazlabs.com",
|
|
1247
1352
|
brotu: "https://api.brotu.app"
|
|
1248
1353
|
};
|
|
1249
1354
|
var DEFAULT_BROTU_API_URL = PROVIDER_BASE_URLS.brotu;
|
|
@@ -1261,7 +1366,8 @@ var BUILT_IN = [
|
|
|
1261
1366
|
...QWEN_CATALOG,
|
|
1262
1367
|
...OPENAI_CATALOG,
|
|
1263
1368
|
...GOOGLE_CATALOG,
|
|
1264
|
-
...ELEVENLABS_CATALOG
|
|
1369
|
+
...ELEVENLABS_CATALOG,
|
|
1370
|
+
...TOPAZ_CATALOG
|
|
1265
1371
|
];
|
|
1266
1372
|
var catalog = BUILT_IN;
|
|
1267
1373
|
var catalogMap = byId(BUILT_IN);
|
|
@@ -1403,7 +1509,7 @@ function expiresInHours(hours) {
|
|
|
1403
1509
|
function estimateFor(provider, type, params, defaults) {
|
|
1404
1510
|
const modelId = params.model ?? "";
|
|
1405
1511
|
const model = getModel(modelId);
|
|
1406
|
-
const
|
|
1512
|
+
const video2 = params;
|
|
1407
1513
|
const unit = model?.pricing?.unit ?? (type === "video" ? "second" : type === "audio" ? "character" : type === "text" ? "token" : "image");
|
|
1408
1514
|
let units = 1;
|
|
1409
1515
|
if (unit === "character") {
|
|
@@ -1411,13 +1517,18 @@ function estimateFor(provider, type, params, defaults) {
|
|
|
1411
1517
|
} else if (unit === "token") {
|
|
1412
1518
|
units = 0;
|
|
1413
1519
|
} else if (unit === "second") {
|
|
1414
|
-
units =
|
|
1520
|
+
units = video2.duration ?? defaults?.durationSeconds ?? model?.minDuration ?? 5;
|
|
1415
1521
|
}
|
|
1416
1522
|
const resolution = params.resolution;
|
|
1417
1523
|
const rate = (resolution ? model?.pricing?.byResolution?.[resolution] : void 0) ?? model?.pricing?.usdPerUnit;
|
|
1524
|
+
const tier = model?.runtimePricingTiers?.find(
|
|
1525
|
+
(candidate) => (candidate.resolution === void 0 || candidate.resolution === resolution) && (candidate.durationSeconds === void 0 || candidate.durationSeconds === units)
|
|
1526
|
+
);
|
|
1527
|
+
const creditsPerUnit = tier?.creditsPerUnit ?? model?.creditsPerUnit;
|
|
1418
1528
|
return {
|
|
1419
1529
|
unit,
|
|
1420
1530
|
units,
|
|
1531
|
+
credits: creditsPerUnit ? creditsPerUnit * units : null,
|
|
1421
1532
|
usd: rate === void 0 || unit === "token" ? null : Number((rate * units).toFixed(4)),
|
|
1422
1533
|
note: unit === "token" && rate !== void 0 ? `Billed per token at $${(rate * 1e6).toFixed(2)} per million output tokens. The total depends on how much the model writes, so it is only known after generating.` : rate === void 0 ? `No verified rate for "${modelId}". It bills to your own ${provider} account; ${units} ${unit}${units === 1 ? "" : "s"} will be charged.` : void 0,
|
|
1423
1534
|
provider,
|
|
@@ -1667,23 +1778,23 @@ var BrotuAdapter = class {
|
|
|
1667
1778
|
}
|
|
1668
1779
|
try {
|
|
1669
1780
|
const workspaceId = await this.workspaceId();
|
|
1670
|
-
const
|
|
1671
|
-
const
|
|
1781
|
+
const video2 = params;
|
|
1782
|
+
const image2 = params;
|
|
1672
1783
|
const payload = await this.request(
|
|
1673
1784
|
this.studioPath("/api/v1/studio/estimate", workspaceId),
|
|
1674
1785
|
{
|
|
1675
1786
|
method: "POST",
|
|
1676
1787
|
body: JSON.stringify({
|
|
1677
1788
|
model: this.platformModel(params.model ?? ""),
|
|
1678
|
-
duration:
|
|
1679
|
-
resolution:
|
|
1680
|
-
aspectRatio:
|
|
1681
|
-
withAudio:
|
|
1789
|
+
duration: video2.duration,
|
|
1790
|
+
resolution: video2.resolution ?? image2.resolution,
|
|
1791
|
+
aspectRatio: video2.aspectRatio ?? image2.aspectRatio,
|
|
1792
|
+
withAudio: video2.withAudio,
|
|
1682
1793
|
hasReferenceImage: Boolean(
|
|
1683
|
-
|
|
1794
|
+
video2.imageUrl || video2.imageUrls?.length || video2.referenceImages?.length || image2.referenceImages?.length
|
|
1684
1795
|
),
|
|
1685
|
-
hasReferenceVideo: Boolean(
|
|
1686
|
-
quality:
|
|
1796
|
+
hasReferenceVideo: Boolean(video2.videoUrl || video2.videoUrls?.length),
|
|
1797
|
+
quality: image2.quality
|
|
1687
1798
|
})
|
|
1688
1799
|
}
|
|
1689
1800
|
);
|
|
@@ -1691,6 +1802,7 @@ var BrotuAdapter = class {
|
|
|
1691
1802
|
return {
|
|
1692
1803
|
unit: type === "video" ? "second" : "image",
|
|
1693
1804
|
units: credits,
|
|
1805
|
+
credits,
|
|
1694
1806
|
usd: null,
|
|
1695
1807
|
note: `${credits} Brotu credit${credits === 1 ? "" : "s"}. A vendor key generates on that provider.`,
|
|
1696
1808
|
provider: this.providerName,
|
|
@@ -2286,12 +2398,12 @@ var GoogleAdapter = class {
|
|
|
2286
2398
|
const input = [
|
|
2287
2399
|
{ type: "text", text: params.prompt }
|
|
2288
2400
|
];
|
|
2289
|
-
for (const
|
|
2290
|
-
const [meta, data] =
|
|
2401
|
+
for (const image2 of params.referenceImages ?? []) {
|
|
2402
|
+
const [meta, data] = image2.split(",");
|
|
2291
2403
|
input.push({
|
|
2292
2404
|
type: "image",
|
|
2293
2405
|
mime_type: meta?.match(/data:([^;]+)/)?.[1] ?? "image/png",
|
|
2294
|
-
data: data ??
|
|
2406
|
+
data: data ?? image2
|
|
2295
2407
|
});
|
|
2296
2408
|
}
|
|
2297
2409
|
const payload = await this.request(
|
|
@@ -2383,10 +2495,10 @@ var GoogleAdapter = class {
|
|
|
2383
2495
|
};
|
|
2384
2496
|
const first = params.imageUrl ?? params.referenceImages?.[0];
|
|
2385
2497
|
if (first) attach("image", first);
|
|
2386
|
-
for (const
|
|
2387
|
-
const
|
|
2388
|
-
if (
|
|
2389
|
-
const task =
|
|
2498
|
+
for (const image2 of params.imageUrls ?? []) attach("image", image2);
|
|
2499
|
+
const video2 = params.videoUrl ?? params.videoUrls?.[0];
|
|
2500
|
+
if (video2) attach("video", video2);
|
|
2501
|
+
const task = video2 ? "edit" : params.imageUrls?.length ? "reference_to_video" : first ? "image_to_video" : "text_to_video";
|
|
2390
2502
|
return {
|
|
2391
2503
|
model: modelId,
|
|
2392
2504
|
input,
|
|
@@ -2555,12 +2667,12 @@ var GoogleAdapter = class {
|
|
|
2555
2667
|
const input = [
|
|
2556
2668
|
{ type: "text", text: params.prompt }
|
|
2557
2669
|
];
|
|
2558
|
-
for (const
|
|
2559
|
-
const [meta, data] =
|
|
2670
|
+
for (const image2 of params.referenceImages ?? []) {
|
|
2671
|
+
const [meta, data] = image2.split(",");
|
|
2560
2672
|
input.push({
|
|
2561
2673
|
type: "image",
|
|
2562
2674
|
mime_type: meta?.match(/data:([^;]+)/)?.[1] ?? "image/png",
|
|
2563
|
-
data: data ??
|
|
2675
|
+
data: data ?? image2
|
|
2564
2676
|
});
|
|
2565
2677
|
}
|
|
2566
2678
|
return {
|
|
@@ -3370,11 +3482,11 @@ var QwenAdapter = class {
|
|
|
3370
3482
|
case "media": {
|
|
3371
3483
|
const media = [];
|
|
3372
3484
|
if (binding.mode === "video-edit") {
|
|
3373
|
-
const
|
|
3374
|
-
if (!
|
|
3485
|
+
const video2 = params.videoUrl ?? params.videoUrls?.[0];
|
|
3486
|
+
if (!video2) {
|
|
3375
3487
|
throw new Error(`Qwen "${modelId}" needs a video \u2014 pass videoUrl.`);
|
|
3376
3488
|
}
|
|
3377
|
-
media.push({ type: "video", url:
|
|
3489
|
+
media.push({ type: "video", url: video2 });
|
|
3378
3490
|
} else if (binding.mode === "r2v") {
|
|
3379
3491
|
for (const url of references) {
|
|
3380
3492
|
media.push({ type: "reference_image", url });
|
|
@@ -3414,7 +3526,7 @@ var QwenAdapter = class {
|
|
|
3414
3526
|
...output.video_url ? [output.video_url] : [],
|
|
3415
3527
|
...(output.results ?? []).map((result) => result.url).filter((url) => Boolean(url)),
|
|
3416
3528
|
...(output.choices ?? []).flatMap(
|
|
3417
|
-
(choice) => (choice.message?.content ?? []).map((part) => part.image).filter((
|
|
3529
|
+
(choice) => (choice.message?.content ?? []).map((part) => part.image).filter((image2) => Boolean(image2))
|
|
3418
3530
|
)
|
|
3419
3531
|
];
|
|
3420
3532
|
return urls.map((url) => ({
|
|
@@ -3562,8 +3674,8 @@ var QwenAdapter = class {
|
|
|
3562
3674
|
}
|
|
3563
3675
|
try {
|
|
3564
3676
|
const content = [];
|
|
3565
|
-
for (const
|
|
3566
|
-
content.push({ image });
|
|
3677
|
+
for (const image2 of params.referenceImages ?? []) {
|
|
3678
|
+
content.push({ image: image2 });
|
|
3567
3679
|
}
|
|
3568
3680
|
content.push({ text: params.prompt });
|
|
3569
3681
|
const parameters = {
|
|
@@ -3756,6 +3868,475 @@ var QwenAdapter = class {
|
|
|
3756
3868
|
}
|
|
3757
3869
|
};
|
|
3758
3870
|
|
|
3871
|
+
// src/adapters/topaz.adapter.ts
|
|
3872
|
+
var DEFAULT_BASE_URL7 = "https://api.topazlabs.com";
|
|
3873
|
+
var POLL_INTERVAL_MS6 = 5e3;
|
|
3874
|
+
var DEFAULT_MAX_POLL_ATTEMPTS6 = 240;
|
|
3875
|
+
var DEFAULT_FPS = 30;
|
|
3876
|
+
var PIXELS2 = {
|
|
3877
|
+
"720p": { width: 1280, height: 720 },
|
|
3878
|
+
"720P": { width: 1280, height: 720 },
|
|
3879
|
+
"1080p": { width: 1920, height: 1080 },
|
|
3880
|
+
"1080P": { width: 1920, height: 1080 },
|
|
3881
|
+
"4k": { width: 3840, height: 2160 },
|
|
3882
|
+
"4K": { width: 3840, height: 2160 },
|
|
3883
|
+
"1K": { width: 1024, height: 1024 },
|
|
3884
|
+
"2K": { width: 2048, height: 2048 }
|
|
3885
|
+
};
|
|
3886
|
+
var TopazAdapter = class {
|
|
3887
|
+
providerName = "topaz";
|
|
3888
|
+
supportedTypes = ["video", "image"];
|
|
3889
|
+
opts;
|
|
3890
|
+
constructor(opts) {
|
|
3891
|
+
this.opts = opts;
|
|
3892
|
+
}
|
|
3893
|
+
get baseUrl() {
|
|
3894
|
+
return (this.opts.baseUrl ?? DEFAULT_BASE_URL7).replace(/\/$/, "");
|
|
3895
|
+
}
|
|
3896
|
+
get headers() {
|
|
3897
|
+
return {
|
|
3898
|
+
"X-API-Key": this.opts.apiKey,
|
|
3899
|
+
accept: "application/json",
|
|
3900
|
+
"Content-Type": "application/json"
|
|
3901
|
+
};
|
|
3902
|
+
}
|
|
3903
|
+
binding(modelId) {
|
|
3904
|
+
const id = modelId ?? "";
|
|
3905
|
+
const found = TOPAZ_MODELS[id];
|
|
3906
|
+
if (!found) {
|
|
3907
|
+
throw new Error(
|
|
3908
|
+
`"${id}" is not a Topaz model. Known: ${TOPAZ_CATALOG.map((model) => model.id).join(", ")}.`
|
|
3909
|
+
);
|
|
3910
|
+
}
|
|
3911
|
+
return [id, found];
|
|
3912
|
+
}
|
|
3913
|
+
/** 720p / 1080p / 4k, or an explicit "1920x1080" / "1920*1080" pair. */
|
|
3914
|
+
resolutionOf(resolution) {
|
|
3915
|
+
if (!resolution) return PIXELS2["1080p"];
|
|
3916
|
+
const named = PIXELS2[resolution];
|
|
3917
|
+
if (named) return named;
|
|
3918
|
+
const match = /^(\d{2,5})[x*](\d{2,5})$/i.exec(resolution.trim());
|
|
3919
|
+
if (match?.[1] && match[2]) {
|
|
3920
|
+
return { width: Number(match[1]), height: Number(match[2]) };
|
|
3921
|
+
}
|
|
3922
|
+
throw new Error(
|
|
3923
|
+
`Topaz wants 720p, 1080p, 4k or WxH, not "${resolution}".`
|
|
3924
|
+
);
|
|
3925
|
+
}
|
|
3926
|
+
containerOf(url) {
|
|
3927
|
+
const path = url.split("?")[0]?.toLowerCase() ?? "";
|
|
3928
|
+
if (path.endsWith(".mov")) return "mov";
|
|
3929
|
+
if (path.endsWith(".mkv")) return "mkv";
|
|
3930
|
+
return "mp4";
|
|
3931
|
+
}
|
|
3932
|
+
filterOf(binding, params) {
|
|
3933
|
+
const extras = params.providerOptions?.topaz ?? {};
|
|
3934
|
+
if (binding.kind === "upscale") {
|
|
3935
|
+
return { auto: "Auto", ...extras, model: binding.vendorModel };
|
|
3936
|
+
}
|
|
3937
|
+
const slowmo = numberish(extras.slowmo) ?? 1;
|
|
3938
|
+
const fps = numberish(extras.fps) ?? DEFAULT_FPS;
|
|
3939
|
+
if (slowmo < 1 || slowmo > 16) {
|
|
3940
|
+
throw new Error(`Topaz interpolation slowmo is 1\u201316, not ${slowmo}.`);
|
|
3941
|
+
}
|
|
3942
|
+
if (fps < 15 || fps > 240) {
|
|
3943
|
+
throw new Error(`Topaz interpolation fps is 15\u2013240, not ${fps}.`);
|
|
3944
|
+
}
|
|
3945
|
+
const filter = {
|
|
3946
|
+
model: binding.vendorModel,
|
|
3947
|
+
slowmo,
|
|
3948
|
+
fps
|
|
3949
|
+
};
|
|
3950
|
+
if (typeof extras.duplicate === "boolean") {
|
|
3951
|
+
filter.duplicate = extras.duplicate;
|
|
3952
|
+
}
|
|
3953
|
+
if (extras.duplicateThreshold !== void 0) {
|
|
3954
|
+
filter.duplicateThreshold = extras.duplicateThreshold;
|
|
3955
|
+
}
|
|
3956
|
+
return filter;
|
|
3957
|
+
}
|
|
3958
|
+
requestBody(binding, params, sourceUrl) {
|
|
3959
|
+
const extras = params.providerOptions?.topaz ?? {};
|
|
3960
|
+
const fps = numberish(extras.fps) ?? DEFAULT_FPS;
|
|
3961
|
+
return {
|
|
3962
|
+
source: { container: this.containerOf(sourceUrl) },
|
|
3963
|
+
filters: [this.filterOf(binding, params)],
|
|
3964
|
+
output: {
|
|
3965
|
+
resolution: this.resolutionOf(params.resolution),
|
|
3966
|
+
frameRate: fps,
|
|
3967
|
+
audioCodec: "AAC",
|
|
3968
|
+
audioTransfer: "Copy",
|
|
3969
|
+
dynamicCompressionLevel: "High",
|
|
3970
|
+
container: "mp4"
|
|
3971
|
+
}
|
|
3972
|
+
};
|
|
3973
|
+
}
|
|
3974
|
+
async request(path, init) {
|
|
3975
|
+
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
3976
|
+
method: init?.method ?? "GET",
|
|
3977
|
+
headers: this.headers,
|
|
3978
|
+
body: init?.body !== void 0 ? JSON.stringify(init.body) : void 0
|
|
3979
|
+
});
|
|
3980
|
+
const payload = await response.json();
|
|
3981
|
+
if (!response.ok) {
|
|
3982
|
+
throw new Error(
|
|
3983
|
+
payload.message ?? `Topaz returned ${response.status} for ${path}.`
|
|
3984
|
+
);
|
|
3985
|
+
}
|
|
3986
|
+
return payload;
|
|
3987
|
+
}
|
|
3988
|
+
async downloadSource(url) {
|
|
3989
|
+
const response = await fetch(url);
|
|
3990
|
+
if (!response.ok) {
|
|
3991
|
+
throw new Error(`Could not download the source video (${response.status}).`);
|
|
3992
|
+
}
|
|
3993
|
+
return {
|
|
3994
|
+
bytes: await response.arrayBuffer(),
|
|
3995
|
+
contentType: response.headers.get("content-type") ?? "video/mp4"
|
|
3996
|
+
};
|
|
3997
|
+
}
|
|
3998
|
+
async submitTask(params) {
|
|
3999
|
+
const [, binding] = this.binding(params.model);
|
|
4000
|
+
if (binding.surface !== "video") {
|
|
4001
|
+
throw new Error(
|
|
4002
|
+
`Topaz "${params.model}" is an image model. Use ai.image.`
|
|
4003
|
+
);
|
|
4004
|
+
}
|
|
4005
|
+
const sourceUrl = params.videoUrl ?? params.videoUrls?.[0];
|
|
4006
|
+
if (!sourceUrl) {
|
|
4007
|
+
throw new Error(
|
|
4008
|
+
`Topaz "${params.model}" needs a source video \u2014 pass videoUrl.`
|
|
4009
|
+
);
|
|
4010
|
+
}
|
|
4011
|
+
const source = await this.downloadSource(sourceUrl);
|
|
4012
|
+
const created = await this.request("/video/express", {
|
|
4013
|
+
method: "POST",
|
|
4014
|
+
body: this.requestBody(binding, params, sourceUrl)
|
|
4015
|
+
});
|
|
4016
|
+
const taskId = created.requestId?.trim();
|
|
4017
|
+
const uploadUrl = created.uploadUrls?.[0];
|
|
4018
|
+
if (!taskId || !uploadUrl) {
|
|
4019
|
+
throw new Error(
|
|
4020
|
+
"Topaz accepted the request but returned no request id or upload URL."
|
|
4021
|
+
);
|
|
4022
|
+
}
|
|
4023
|
+
const uploaded = await fetch(uploadUrl, {
|
|
4024
|
+
method: "PUT",
|
|
4025
|
+
headers: { "Content-Type": source.contentType },
|
|
4026
|
+
body: source.bytes
|
|
4027
|
+
});
|
|
4028
|
+
if (!uploaded.ok) {
|
|
4029
|
+
throw new Error(`Topaz upload failed (${uploaded.status}).`);
|
|
4030
|
+
}
|
|
4031
|
+
return { taskId, pollEndpoint: `/video/${taskId}/status` };
|
|
4032
|
+
}
|
|
4033
|
+
outputsFrom(payload, job) {
|
|
4034
|
+
const url = payload.download?.url;
|
|
4035
|
+
if (!url) return [];
|
|
4036
|
+
const expiresAtMs = payload.download?.expiresAt;
|
|
4037
|
+
return [
|
|
4038
|
+
{
|
|
4039
|
+
url,
|
|
4040
|
+
mimeType: "video/mp4",
|
|
4041
|
+
taskId: job.id,
|
|
4042
|
+
expiresAt: expiresAtMs ? new Date(expiresAtMs).toISOString() : expiresInHours(24),
|
|
4043
|
+
raw: {
|
|
4044
|
+
status: payload.status,
|
|
4045
|
+
progress: payload.progress,
|
|
4046
|
+
estimates: payload.estimates,
|
|
4047
|
+
outputSize: payload.outputSize
|
|
4048
|
+
}
|
|
4049
|
+
}
|
|
4050
|
+
];
|
|
4051
|
+
}
|
|
4052
|
+
snapshot(payload, job) {
|
|
4053
|
+
const raw = (payload.status ?? "").toLowerCase();
|
|
4054
|
+
if (raw === "failed" || raw === "canceled") {
|
|
4055
|
+
return {
|
|
4056
|
+
status: "failed",
|
|
4057
|
+
error: payload.message ?? `Topaz request ${raw}.`
|
|
4058
|
+
};
|
|
4059
|
+
}
|
|
4060
|
+
if (raw !== "complete") return { status: "pending" };
|
|
4061
|
+
const outputs = this.outputsFrom(payload, job);
|
|
4062
|
+
if (outputs.length === 0) return { status: "pending" };
|
|
4063
|
+
return {
|
|
4064
|
+
status: "succeeded",
|
|
4065
|
+
result: {
|
|
4066
|
+
success: true,
|
|
4067
|
+
outputs,
|
|
4068
|
+
creditsUsed: 0,
|
|
4069
|
+
provider: this.providerName,
|
|
4070
|
+
model: job.model,
|
|
4071
|
+
processingTimeMs: 0
|
|
4072
|
+
}
|
|
4073
|
+
};
|
|
4074
|
+
}
|
|
4075
|
+
async run(params) {
|
|
4076
|
+
const startedAt = Date.now();
|
|
4077
|
+
let modelId = params.model ?? "(none)";
|
|
4078
|
+
const failure = (error) => ({
|
|
4079
|
+
success: false,
|
|
4080
|
+
outputs: [],
|
|
4081
|
+
creditsUsed: 0,
|
|
4082
|
+
provider: this.providerName,
|
|
4083
|
+
model: modelId,
|
|
4084
|
+
processingTimeMs: Date.now() - startedAt,
|
|
4085
|
+
error
|
|
4086
|
+
});
|
|
4087
|
+
let submitted;
|
|
4088
|
+
try {
|
|
4089
|
+
modelId = this.binding(params.model)[0];
|
|
4090
|
+
submitted = await this.submitTask(params);
|
|
4091
|
+
} catch (error) {
|
|
4092
|
+
if (isPendingJob(error)) throw error;
|
|
4093
|
+
return failure(error instanceof Error ? error.message : String(error));
|
|
4094
|
+
}
|
|
4095
|
+
if (isSubmitMode()) {
|
|
4096
|
+
throw new PendingJob(submitted.taskId, submitted.pollEndpoint);
|
|
4097
|
+
}
|
|
4098
|
+
const job = {
|
|
4099
|
+
id: submitted.taskId,
|
|
4100
|
+
provider: this.providerName,
|
|
4101
|
+
model: modelId,
|
|
4102
|
+
kind: "video",
|
|
4103
|
+
pollEndpoint: submitted.pollEndpoint,
|
|
4104
|
+
params,
|
|
4105
|
+
submittedAt: new Date(startedAt).toISOString()
|
|
4106
|
+
};
|
|
4107
|
+
const maxAttempts = this.opts.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS6;
|
|
4108
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
4109
|
+
const snapshot = await this.completeJob(job);
|
|
4110
|
+
if (snapshot.status === "failed") {
|
|
4111
|
+
return failure(snapshot.error ?? "Topaz request failed.");
|
|
4112
|
+
}
|
|
4113
|
+
if (snapshot.status === "succeeded" && snapshot.result) {
|
|
4114
|
+
return { ...snapshot.result, processingTimeMs: Date.now() - startedAt };
|
|
4115
|
+
}
|
|
4116
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS6));
|
|
4117
|
+
}
|
|
4118
|
+
return failure(
|
|
4119
|
+
`Topaz request ${submitted.taskId} did not finish after ${maxAttempts} checks.`
|
|
4120
|
+
);
|
|
4121
|
+
}
|
|
4122
|
+
async completeJob(job) {
|
|
4123
|
+
try {
|
|
4124
|
+
if (job.pollEndpoint?.startsWith("/image/")) {
|
|
4125
|
+
return this.imageSnapshot(job);
|
|
4126
|
+
}
|
|
4127
|
+
const payload = await this.request(
|
|
4128
|
+
job.pollEndpoint ?? `/video/${job.id}/status`
|
|
4129
|
+
);
|
|
4130
|
+
return this.snapshot(payload, job);
|
|
4131
|
+
} catch (error) {
|
|
4132
|
+
return {
|
|
4133
|
+
status: "failed",
|
|
4134
|
+
error: error instanceof Error ? error.message : String(error)
|
|
4135
|
+
};
|
|
4136
|
+
}
|
|
4137
|
+
}
|
|
4138
|
+
generateVideo(params) {
|
|
4139
|
+
return this.run(params);
|
|
4140
|
+
}
|
|
4141
|
+
imageBody(binding, params, sourceUrl) {
|
|
4142
|
+
const extras = params.providerOptions?.topaz ?? {};
|
|
4143
|
+
const pixels = params.resolution ? this.resolutionOf(params.resolution) : void 0;
|
|
4144
|
+
const fields = {
|
|
4145
|
+
source_url: sourceUrl,
|
|
4146
|
+
model: binding.vendorModel,
|
|
4147
|
+
output_format: params.outputFormat === "png" ? "png" : params.outputFormat === "jpeg" ? "jpeg" : "jpeg"
|
|
4148
|
+
};
|
|
4149
|
+
if (pixels) {
|
|
4150
|
+
fields.output_width = String(pixels.width);
|
|
4151
|
+
fields.output_height = String(pixels.height);
|
|
4152
|
+
}
|
|
4153
|
+
for (const [key, value] of Object.entries(extras)) {
|
|
4154
|
+
if (value === void 0) continue;
|
|
4155
|
+
fields[key] = typeof value === "string" ? value : String(value);
|
|
4156
|
+
}
|
|
4157
|
+
return fields;
|
|
4158
|
+
}
|
|
4159
|
+
async submitImage(params) {
|
|
4160
|
+
const [, binding] = this.binding(params.model);
|
|
4161
|
+
if (binding.surface !== "image" || !binding.imagePath) {
|
|
4162
|
+
throw new Error(
|
|
4163
|
+
`Topaz "${params.model}" is a video model. Use ai.video with videoUrl.`
|
|
4164
|
+
);
|
|
4165
|
+
}
|
|
4166
|
+
const sourceUrl = params.referenceImages?.[0];
|
|
4167
|
+
if (!sourceUrl) {
|
|
4168
|
+
throw new Error(
|
|
4169
|
+
`Topaz "${params.model}" needs a source image \u2014 pass referenceImages.`
|
|
4170
|
+
);
|
|
4171
|
+
}
|
|
4172
|
+
const form = new FormData();
|
|
4173
|
+
for (const [key, value] of Object.entries(
|
|
4174
|
+
this.imageBody(binding, params, sourceUrl)
|
|
4175
|
+
)) {
|
|
4176
|
+
form.set(key, value);
|
|
4177
|
+
}
|
|
4178
|
+
const response = await fetch(`${this.baseUrl}${binding.imagePath}`, {
|
|
4179
|
+
method: "POST",
|
|
4180
|
+
headers: {
|
|
4181
|
+
"X-API-Key": this.opts.apiKey,
|
|
4182
|
+
accept: "application/json"
|
|
4183
|
+
},
|
|
4184
|
+
body: form
|
|
4185
|
+
});
|
|
4186
|
+
const payload = await response.json();
|
|
4187
|
+
if (!response.ok) {
|
|
4188
|
+
throw new Error(
|
|
4189
|
+
payload.message ?? `Topaz returned ${response.status} enhancing the image.`
|
|
4190
|
+
);
|
|
4191
|
+
}
|
|
4192
|
+
const taskId = payload.process_id?.trim();
|
|
4193
|
+
if (!taskId) {
|
|
4194
|
+
throw new Error(
|
|
4195
|
+
"Topaz accepted the image but returned no process id."
|
|
4196
|
+
);
|
|
4197
|
+
}
|
|
4198
|
+
return { taskId, pollEndpoint: `/image/v1/status/${taskId}` };
|
|
4199
|
+
}
|
|
4200
|
+
async imageSnapshot(job) {
|
|
4201
|
+
const payload = await this.request(
|
|
4202
|
+
job.pollEndpoint ?? `/image/v1/status/${job.id}`
|
|
4203
|
+
);
|
|
4204
|
+
const raw = (payload.status ?? "").toLowerCase();
|
|
4205
|
+
if (raw === "failed" || raw === "cancelled") {
|
|
4206
|
+
return {
|
|
4207
|
+
status: "failed",
|
|
4208
|
+
error: `Topaz image ${raw}.`
|
|
4209
|
+
};
|
|
4210
|
+
}
|
|
4211
|
+
if (raw !== "completed") return { status: "pending" };
|
|
4212
|
+
let url = payload.download_url;
|
|
4213
|
+
let expiresAt = payload.eta ? new Date(payload.eta > 1e10 ? payload.eta : payload.eta * 1e3).toISOString() : void 0;
|
|
4214
|
+
if (!url) {
|
|
4215
|
+
const download = await this.request(
|
|
4216
|
+
`/image/v1/download/${job.id}`
|
|
4217
|
+
);
|
|
4218
|
+
url = download.download_url;
|
|
4219
|
+
if (download.expiry) {
|
|
4220
|
+
expiresAt = new Date(
|
|
4221
|
+
download.expiry > 1e10 ? download.expiry : download.expiry * 1e3
|
|
4222
|
+
).toISOString();
|
|
4223
|
+
}
|
|
4224
|
+
}
|
|
4225
|
+
if (!url) return { status: "pending" };
|
|
4226
|
+
return {
|
|
4227
|
+
status: "succeeded",
|
|
4228
|
+
result: {
|
|
4229
|
+
success: true,
|
|
4230
|
+
outputs: [
|
|
4231
|
+
{
|
|
4232
|
+
url,
|
|
4233
|
+
mimeType: "image/jpeg",
|
|
4234
|
+
taskId: job.id,
|
|
4235
|
+
expiresAt: expiresAt ?? expiresInHours(1),
|
|
4236
|
+
raw: {
|
|
4237
|
+
status: payload.status,
|
|
4238
|
+
progress: payload.progress,
|
|
4239
|
+
model: payload.model
|
|
4240
|
+
}
|
|
4241
|
+
}
|
|
4242
|
+
],
|
|
4243
|
+
creditsUsed: payload.credits ?? 0,
|
|
4244
|
+
provider: this.providerName,
|
|
4245
|
+
model: job.model,
|
|
4246
|
+
processingTimeMs: 0
|
|
4247
|
+
}
|
|
4248
|
+
};
|
|
4249
|
+
}
|
|
4250
|
+
async runImage(params) {
|
|
4251
|
+
const startedAt = Date.now();
|
|
4252
|
+
let modelId = params.model ?? "(none)";
|
|
4253
|
+
const failure = (error) => ({
|
|
4254
|
+
success: false,
|
|
4255
|
+
outputs: [],
|
|
4256
|
+
creditsUsed: 0,
|
|
4257
|
+
provider: this.providerName,
|
|
4258
|
+
model: modelId,
|
|
4259
|
+
processingTimeMs: Date.now() - startedAt,
|
|
4260
|
+
error
|
|
4261
|
+
});
|
|
4262
|
+
let submitted;
|
|
4263
|
+
try {
|
|
4264
|
+
modelId = this.binding(params.model)[0];
|
|
4265
|
+
submitted = await this.submitImage(params);
|
|
4266
|
+
} catch (error) {
|
|
4267
|
+
if (isPendingJob(error)) throw error;
|
|
4268
|
+
return failure(error instanceof Error ? error.message : String(error));
|
|
4269
|
+
}
|
|
4270
|
+
if (isSubmitMode()) {
|
|
4271
|
+
throw new PendingJob(submitted.taskId, submitted.pollEndpoint);
|
|
4272
|
+
}
|
|
4273
|
+
const job = {
|
|
4274
|
+
id: submitted.taskId,
|
|
4275
|
+
provider: this.providerName,
|
|
4276
|
+
model: modelId,
|
|
4277
|
+
kind: "image",
|
|
4278
|
+
pollEndpoint: submitted.pollEndpoint,
|
|
4279
|
+
params,
|
|
4280
|
+
submittedAt: new Date(startedAt).toISOString()
|
|
4281
|
+
};
|
|
4282
|
+
const maxAttempts = this.opts.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS6;
|
|
4283
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
4284
|
+
const snapshot = await this.completeJob(job);
|
|
4285
|
+
if (snapshot.status === "failed") {
|
|
4286
|
+
return failure(snapshot.error ?? "Topaz image failed.");
|
|
4287
|
+
}
|
|
4288
|
+
if (snapshot.status === "succeeded" && snapshot.result) {
|
|
4289
|
+
return { ...snapshot.result, processingTimeMs: Date.now() - startedAt };
|
|
4290
|
+
}
|
|
4291
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS6));
|
|
4292
|
+
}
|
|
4293
|
+
return failure(
|
|
4294
|
+
`Topaz image ${submitted.taskId} did not finish after ${maxAttempts} checks.`
|
|
4295
|
+
);
|
|
4296
|
+
}
|
|
4297
|
+
generateImage(params) {
|
|
4298
|
+
return this.runImage(params);
|
|
4299
|
+
}
|
|
4300
|
+
generateText(params) {
|
|
4301
|
+
return Promise.resolve(unsupported(this.providerName, params.model, "text"));
|
|
4302
|
+
}
|
|
4303
|
+
generateAudio(params) {
|
|
4304
|
+
return Promise.resolve(unsupported(this.providerName, params.model, "audio"));
|
|
4305
|
+
}
|
|
4306
|
+
estimateCost(type, params) {
|
|
4307
|
+
return Promise.resolve(estimateFor(this.providerName, type, params));
|
|
4308
|
+
}
|
|
4309
|
+
supportsModel(model) {
|
|
4310
|
+
return model in TOPAZ_MODELS;
|
|
4311
|
+
}
|
|
4312
|
+
getAvailableModels() {
|
|
4313
|
+
return TOPAZ_CATALOG.map((model) => ({
|
|
4314
|
+
id: model.id,
|
|
4315
|
+
name: model.name,
|
|
4316
|
+
type: model.category
|
|
4317
|
+
}));
|
|
4318
|
+
}
|
|
4319
|
+
};
|
|
4320
|
+
function numberish(value) {
|
|
4321
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
4322
|
+
if (typeof value === "string" && value.length > 0) {
|
|
4323
|
+
const parsed = Number(value);
|
|
4324
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
4325
|
+
}
|
|
4326
|
+
return void 0;
|
|
4327
|
+
}
|
|
4328
|
+
function unsupported(provider, model, kind) {
|
|
4329
|
+
return {
|
|
4330
|
+
success: false,
|
|
4331
|
+
outputs: [],
|
|
4332
|
+
creditsUsed: 0,
|
|
4333
|
+
provider,
|
|
4334
|
+
model: model ?? "",
|
|
4335
|
+
processingTimeMs: 0,
|
|
4336
|
+
error: `Topaz does not generate ${kind} on this adapter.`
|
|
4337
|
+
};
|
|
4338
|
+
}
|
|
4339
|
+
|
|
3759
4340
|
// src/helpers/result.ts
|
|
3760
4341
|
function ok(data) {
|
|
3761
4342
|
return { data, error: null };
|
|
@@ -3956,7 +4537,8 @@ var NATIVE_PROVIDERS = [
|
|
|
3956
4537
|
"google",
|
|
3957
4538
|
"kling",
|
|
3958
4539
|
"openai",
|
|
3959
|
-
"qwen"
|
|
4540
|
+
"qwen",
|
|
4541
|
+
"topaz"
|
|
3960
4542
|
];
|
|
3961
4543
|
function brotu(options) {
|
|
3962
4544
|
const apiKey = options.apiKey?.trim();
|
|
@@ -4055,6 +4637,12 @@ function brotu(options) {
|
|
|
4055
4637
|
baseUrl: provider.baseUrl
|
|
4056
4638
|
});
|
|
4057
4639
|
}
|
|
4640
|
+
if (provider.id === "topaz") {
|
|
4641
|
+
return new TopazAdapter({
|
|
4642
|
+
apiKey: provider.apiKey,
|
|
4643
|
+
baseUrl: provider.baseUrl
|
|
4644
|
+
});
|
|
4645
|
+
}
|
|
4058
4646
|
return void 0;
|
|
4059
4647
|
}
|
|
4060
4648
|
function generateWith(adapter, kind, params) {
|
|
@@ -4089,6 +4677,7 @@ function brotu(options) {
|
|
|
4089
4677
|
outputs: input.outputs,
|
|
4090
4678
|
error: input.error ? { code: input.error.code, message: input.error.message } : void 0,
|
|
4091
4679
|
metadata: input.metadata ?? input.job?.metadata,
|
|
4680
|
+
creditsUsed: input.params?.credits ?? input.creditsUsed,
|
|
4092
4681
|
processingTimeMs: input.processingTimeMs,
|
|
4093
4682
|
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4094
4683
|
};
|
|
@@ -4102,6 +4691,7 @@ function brotu(options) {
|
|
|
4102
4691
|
outputs: payload.outputs,
|
|
4103
4692
|
error: payload.error,
|
|
4104
4693
|
metadata: payload.metadata,
|
|
4694
|
+
creditsUsed: payload.creditsUsed,
|
|
4105
4695
|
processingTimeMs: payload.processingTimeMs,
|
|
4106
4696
|
at: payload.completedAt
|
|
4107
4697
|
});
|
|
@@ -4118,7 +4708,7 @@ function brotu(options) {
|
|
|
4118
4708
|
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
4119
4709
|
});
|
|
4120
4710
|
}
|
|
4121
|
-
async function toGeneration(raw, metadata) {
|
|
4711
|
+
async function toGeneration(raw, metadata, credits) {
|
|
4122
4712
|
if (!raw.success) {
|
|
4123
4713
|
return fail({
|
|
4124
4714
|
code: "provider_error",
|
|
@@ -4133,6 +4723,9 @@ function brotu(options) {
|
|
|
4133
4723
|
provider: raw.provider,
|
|
4134
4724
|
model: raw.model,
|
|
4135
4725
|
processingTimeMs: raw.processingTimeMs,
|
|
4726
|
+
// What you said to charge wins over what the platform reported: the
|
|
4727
|
+
// caller's number is the one their ledger has to match.
|
|
4728
|
+
creditsUsed: credits ?? raw.creditsUsed,
|
|
4136
4729
|
metadata
|
|
4137
4730
|
});
|
|
4138
4731
|
}
|
|
@@ -4143,7 +4736,8 @@ function brotu(options) {
|
|
|
4143
4736
|
try {
|
|
4144
4737
|
const result = await toGeneration(
|
|
4145
4738
|
await generateWith(routed.data.adapter, kind, params),
|
|
4146
|
-
params.metadata
|
|
4739
|
+
params.metadata,
|
|
4740
|
+
params.credits
|
|
4147
4741
|
);
|
|
4148
4742
|
if (result.error) {
|
|
4149
4743
|
await notifySettled({
|
|
@@ -4165,6 +4759,7 @@ function brotu(options) {
|
|
|
4165
4759
|
model: result.data.model,
|
|
4166
4760
|
outputs: result.data.outputs,
|
|
4167
4761
|
metadata: result.data.metadata,
|
|
4762
|
+
creditsUsed: result.data.creditsUsed,
|
|
4168
4763
|
processingTimeMs: result.data.processingTimeMs
|
|
4169
4764
|
});
|
|
4170
4765
|
return result;
|
|
@@ -4261,6 +4856,7 @@ function brotu(options) {
|
|
|
4261
4856
|
outputs: snapshot.result.outputs,
|
|
4262
4857
|
provider: snapshot.result.provider,
|
|
4263
4858
|
model: snapshot.result.model,
|
|
4859
|
+
creditsUsed: snapshot.result.creditsUsed,
|
|
4264
4860
|
processingTimeMs: snapshot.result.processingTimeMs,
|
|
4265
4861
|
metadata: job.metadata
|
|
4266
4862
|
});
|
|
@@ -4283,7 +4879,11 @@ function brotu(options) {
|
|
|
4283
4879
|
}
|
|
4284
4880
|
async function finalizeSnapshot(job, snapshot) {
|
|
4285
4881
|
if (snapshot.status === "succeeded" && snapshot.result) {
|
|
4286
|
-
const persisted = await toGeneration(
|
|
4882
|
+
const persisted = await toGeneration(
|
|
4883
|
+
snapshot.result,
|
|
4884
|
+
job.metadata,
|
|
4885
|
+
job.params.credits
|
|
4886
|
+
);
|
|
4287
4887
|
if (!persisted.error) {
|
|
4288
4888
|
snapshot = {
|
|
4289
4889
|
status: "succeeded",
|
|
@@ -4373,12 +4973,67 @@ function brotu(options) {
|
|
|
4373
4973
|
image: {
|
|
4374
4974
|
submit: (params) => submit("image", params),
|
|
4375
4975
|
generate: (params) => generate("image", params),
|
|
4376
|
-
list: () => listFor("image")
|
|
4976
|
+
list: () => listFor("image"),
|
|
4977
|
+
upscale: (params) => {
|
|
4978
|
+
if (!params.imageUrl?.trim()) {
|
|
4979
|
+
return Promise.resolve(
|
|
4980
|
+
fail({
|
|
4981
|
+
code: "invalid_request",
|
|
4982
|
+
message: "image.upscale needs imageUrl.",
|
|
4983
|
+
model: params.model
|
|
4984
|
+
})
|
|
4985
|
+
);
|
|
4986
|
+
}
|
|
4987
|
+
const model = getModel(params.model);
|
|
4988
|
+
if (model && !model.nodeTypes.includes("image_upscale")) {
|
|
4989
|
+
return Promise.resolve(
|
|
4990
|
+
fail({
|
|
4991
|
+
code: "invalid_request",
|
|
4992
|
+
message: `"${params.model}" is not an image upscale model.`,
|
|
4993
|
+
model: params.model
|
|
4994
|
+
})
|
|
4995
|
+
);
|
|
4996
|
+
}
|
|
4997
|
+
return generate("image", {
|
|
4998
|
+
...params,
|
|
4999
|
+
prompt: params.prompt ?? "",
|
|
5000
|
+
referenceImages: [
|
|
5001
|
+
params.imageUrl,
|
|
5002
|
+
...params.referenceImages ?? []
|
|
5003
|
+
]
|
|
5004
|
+
});
|
|
5005
|
+
}
|
|
4377
5006
|
},
|
|
4378
5007
|
video: {
|
|
4379
5008
|
submit: (params) => submit("video", params),
|
|
4380
5009
|
generate: (params) => generate("video", params),
|
|
4381
|
-
list: () => listFor("video")
|
|
5010
|
+
list: () => listFor("video"),
|
|
5011
|
+
upscale: (params) => {
|
|
5012
|
+
if (!params.videoUrl?.trim()) {
|
|
5013
|
+
return Promise.resolve(
|
|
5014
|
+
fail({
|
|
5015
|
+
code: "invalid_request",
|
|
5016
|
+
message: "video.upscale needs videoUrl.",
|
|
5017
|
+
model: params.model
|
|
5018
|
+
})
|
|
5019
|
+
);
|
|
5020
|
+
}
|
|
5021
|
+
const model = getModel(params.model);
|
|
5022
|
+
if (model && !model.nodeTypes.includes("video_upscale")) {
|
|
5023
|
+
return Promise.resolve(
|
|
5024
|
+
fail({
|
|
5025
|
+
code: "invalid_request",
|
|
5026
|
+
message: `"${params.model}" is not a video upscale model. Interpolation models use ai.video.submit.`,
|
|
5027
|
+
model: params.model
|
|
5028
|
+
})
|
|
5029
|
+
);
|
|
5030
|
+
}
|
|
5031
|
+
return submit("video", {
|
|
5032
|
+
...params,
|
|
5033
|
+
prompt: params.prompt ?? "",
|
|
5034
|
+
videoUrl: params.videoUrl
|
|
5035
|
+
});
|
|
5036
|
+
}
|
|
4382
5037
|
},
|
|
4383
5038
|
text: {
|
|
4384
5039
|
submit: (params) => submit("text", params),
|
|
@@ -4415,6 +5070,7 @@ function brotu(options) {
|
|
|
4415
5070
|
provider: snapshot.result.provider,
|
|
4416
5071
|
model: snapshot.result.model,
|
|
4417
5072
|
processingTimeMs: snapshot.result.processingTimeMs,
|
|
5073
|
+
creditsUsed: job.params.credits ?? snapshot.result.creditsUsed,
|
|
4418
5074
|
metadata: job.metadata
|
|
4419
5075
|
});
|
|
4420
5076
|
}
|
|
@@ -4487,6 +5143,11 @@ function brotu(options) {
|
|
|
4487
5143
|
QWEN_TEXT_MODELS,
|
|
4488
5144
|
QWEN_VIDEO_MODELS,
|
|
4489
5145
|
QwenAdapter,
|
|
5146
|
+
TOPAZ_CATALOG,
|
|
5147
|
+
TOPAZ_IMAGE_MODELS,
|
|
5148
|
+
TOPAZ_MODELS,
|
|
5149
|
+
TOPAZ_VIDEO_MODELS,
|
|
5150
|
+
TopazAdapter,
|
|
4490
5151
|
brotu,
|
|
4491
5152
|
createS3Storage,
|
|
4492
5153
|
deliverWebhook,
|