@picsart/ai-sdk 5.39.0 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +87 -19
- package/_vendor/workflows-client/index.d.ts +246 -6
- package/chunk-4VNS5WPM.js +37 -0
- package/esm-debug-3SQICTIF.js +8341 -0
- package/index.d.ts +278 -194
- package/index.js +2536 -1939
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -1,31 +1,6 @@
|
|
|
1
|
+
import { __commonJS, __toESM } from './chunk-4VNS5WPM.js';
|
|
1
2
|
import { deflateSync, inflateSync } from 'fflate';
|
|
2
3
|
|
|
3
|
-
var __create = Object.create;
|
|
4
|
-
var __defProp = Object.defineProperty;
|
|
5
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
-
var __commonJS = (cb, mod) => function __require() {
|
|
10
|
-
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
11
|
-
};
|
|
12
|
-
var __copyProps = (to, from, except, desc) => {
|
|
13
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
-
for (let key of __getOwnPropNames(from))
|
|
15
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
-
}
|
|
18
|
-
return to;
|
|
19
|
-
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
__defProp(target, "default", { value: mod, enumerable: true }) ,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
|
-
|
|
29
4
|
// ../../node_modules/@picsart/pa-model-pricing-sdk/build/lib/errors/ModelPricingClientError.js
|
|
30
5
|
var require_ModelPricingClientError = __commonJS({
|
|
31
6
|
"../../node_modules/@picsart/pa-model-pricing-sdk/build/lib/errors/ModelPricingClientError.js"(exports) {
|
|
@@ -441,192 +416,6 @@ var require_build = __commonJS({
|
|
|
441
416
|
}
|
|
442
417
|
});
|
|
443
418
|
|
|
444
|
-
// src/core/errors.ts
|
|
445
|
-
var ApiError = class extends Error {
|
|
446
|
-
/** HTTP status, or the synthesized equivalent for non-HTTP failures. */
|
|
447
|
-
status;
|
|
448
|
-
/** Platform `reason`, or an SDK-synthesized code. Always equal to {@link reason}. */
|
|
449
|
-
code;
|
|
450
|
-
/** Alias of {@link code}, named after the platform's own error field. */
|
|
451
|
-
reason;
|
|
452
|
-
constructor(message, init) {
|
|
453
|
-
super(message);
|
|
454
|
-
this.name = "ApiError";
|
|
455
|
-
this.status = init.status;
|
|
456
|
-
this.code = init.code;
|
|
457
|
-
this.reason = init.code;
|
|
458
|
-
}
|
|
459
|
-
};
|
|
460
|
-
var CODE_BY_STATUS = {
|
|
461
|
-
400: "bad_request",
|
|
462
|
-
401: "unauthorized",
|
|
463
|
-
402: "payment_required",
|
|
464
|
-
403: "forbidden",
|
|
465
|
-
404: "not_found",
|
|
466
|
-
408: "timeout",
|
|
467
|
-
409: "conflict",
|
|
468
|
-
413: "payload_too_large",
|
|
469
|
-
422: "unprocessable_entity",
|
|
470
|
-
429: "rate_limited",
|
|
471
|
-
500: "server_error",
|
|
472
|
-
502: "bad_gateway",
|
|
473
|
-
503: "service_unavailable",
|
|
474
|
-
504: "gateway_timeout"
|
|
475
|
-
};
|
|
476
|
-
function codeForStatus(status) {
|
|
477
|
-
return CODE_BY_STATUS[status] ?? (status >= 500 ? "server_error" : `http_${status}`);
|
|
478
|
-
}
|
|
479
|
-
async function readErrorBody(res) {
|
|
480
|
-
let text = "";
|
|
481
|
-
try {
|
|
482
|
-
text = await res.text();
|
|
483
|
-
} catch {
|
|
484
|
-
return { text: "" };
|
|
485
|
-
}
|
|
486
|
-
try {
|
|
487
|
-
const parsed = JSON.parse(text);
|
|
488
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
489
|
-
return { text, json: parsed };
|
|
490
|
-
}
|
|
491
|
-
} catch {
|
|
492
|
-
}
|
|
493
|
-
return { text };
|
|
494
|
-
}
|
|
495
|
-
function reasonFrom(json, status) {
|
|
496
|
-
const raw = json?.reason ?? json?.code;
|
|
497
|
-
return typeof raw === "string" && raw.length > 0 ? raw : codeForStatus(status);
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
// src/core/workflow.ts
|
|
501
|
-
var DEFAULT_POLL_INTERVAL_MS = 2e3;
|
|
502
|
-
var DEFAULT_MAX_ATTEMPTS = 300;
|
|
503
|
-
var sleepDefault = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
504
|
-
function getNested(raw, path) {
|
|
505
|
-
let current = raw;
|
|
506
|
-
for (const key of path) {
|
|
507
|
-
if (!current || typeof current !== "object") return void 0;
|
|
508
|
-
current = current[key];
|
|
509
|
-
}
|
|
510
|
-
return current;
|
|
511
|
-
}
|
|
512
|
-
function pickFirst(raw, paths) {
|
|
513
|
-
for (const path of paths) {
|
|
514
|
-
const value = getNested(raw, path);
|
|
515
|
-
if (value !== void 0) return value;
|
|
516
|
-
}
|
|
517
|
-
return void 0;
|
|
518
|
-
}
|
|
519
|
-
function normalizeStatus(status) {
|
|
520
|
-
if (typeof status !== "string") return "UNKNOWN";
|
|
521
|
-
const s = status.toUpperCase();
|
|
522
|
-
if (s === "ACCEPTED") return "ACCEPTED";
|
|
523
|
-
if (s === "IN_PROGRESS" || s === "PENDING" || s === "RUNNING") return "IN_PROGRESS";
|
|
524
|
-
if (s === "COMPLETED" || s === "SUCCESS") return "COMPLETED";
|
|
525
|
-
if (s === "FAILED" || s === "ERROR") return "FAILED";
|
|
526
|
-
if (s === "CANCELED" || s === "CANCELLED") return "CANCELED";
|
|
527
|
-
return "UNKNOWN";
|
|
528
|
-
}
|
|
529
|
-
function parseWorkflowStatus(handle, raw) {
|
|
530
|
-
const statusRaw = pickFirst(raw, [["response", "status"], ["status"]]);
|
|
531
|
-
const status = normalizeStatus(statusRaw);
|
|
532
|
-
const result = pickFirst(raw, [["response", "result"], ["result"]]);
|
|
533
|
-
const usageRaw = pickFirst(raw, [["response", "usage"], ["usage"]]);
|
|
534
|
-
const usage = usageRaw && typeof usageRaw === "object" && (typeof usageRaw.credits === "number" || Array.isArray(usageRaw.details)) ? usageRaw : void 0;
|
|
535
|
-
const errorRaw = pickFirst(raw, [["response", "error"], ["response", "message"], ["error"], ["message"], ["reason"]]);
|
|
536
|
-
const reasonRaw = pickFirst(raw, [["response", "reason"], ["reason"]]);
|
|
537
|
-
const statusCodeRaw = pickFirst(raw, [["response", "statusCode"], ["statusCode"]]);
|
|
538
|
-
const progressRaw = pickFirst(raw, [["response", "progress"], ["progress"]]);
|
|
539
|
-
const progress = progressRaw && typeof progressRaw === "object" ? {
|
|
540
|
-
percent: typeof progressRaw.percent === "number" ? progressRaw.percent : void 0,
|
|
541
|
-
estimatedSecondsLeft: typeof progressRaw.estimatedSecondsLeft === "number" ? progressRaw.estimatedSecondsLeft : void 0
|
|
542
|
-
} : void 0;
|
|
543
|
-
return {
|
|
544
|
-
handle,
|
|
545
|
-
status,
|
|
546
|
-
result,
|
|
547
|
-
error: typeof errorRaw === "string" ? errorRaw : void 0,
|
|
548
|
-
reason: typeof reasonRaw === "string" ? reasonRaw : void 0,
|
|
549
|
-
statusCode: typeof statusCodeRaw === "number" ? statusCodeRaw : void 0,
|
|
550
|
-
progress,
|
|
551
|
-
usage,
|
|
552
|
-
raw
|
|
553
|
-
};
|
|
554
|
-
}
|
|
555
|
-
function isTerminal(status) {
|
|
556
|
-
return status === "COMPLETED" || status === "FAILED" || status === "CANCELED";
|
|
557
|
-
}
|
|
558
|
-
function createWorkflowClient(transport, options = {}) {
|
|
559
|
-
const parseStatus = options.parseStatus ?? parseWorkflowStatus;
|
|
560
|
-
const sleep2 = options.sleep ?? sleepDefault;
|
|
561
|
-
const defaultPollIntervalMs = options.pollingIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
562
|
-
const defaultMaxAttempts = options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
|
|
563
|
-
const submit = async (request) => {
|
|
564
|
-
if (!transport.submit) {
|
|
565
|
-
throw new ApiError("Transport does not support submit (execute-only transport)", {
|
|
566
|
-
status: 400,
|
|
567
|
-
code: "unsupported_transport"
|
|
568
|
-
});
|
|
569
|
-
}
|
|
570
|
-
return transport.submit(request);
|
|
571
|
-
};
|
|
572
|
-
const status = async (handle, signal) => {
|
|
573
|
-
if (!transport.status) {
|
|
574
|
-
throw new ApiError("Transport does not support status (execute-only transport)", {
|
|
575
|
-
status: 400,
|
|
576
|
-
code: "unsupported_transport"
|
|
577
|
-
});
|
|
578
|
-
}
|
|
579
|
-
const raw = await transport.status(handle, signal);
|
|
580
|
-
return parseStatus(handle, raw);
|
|
581
|
-
};
|
|
582
|
-
const result = async (handle, pollOptions = {}) => {
|
|
583
|
-
const intervalMs = pollOptions.intervalMs ?? defaultPollIntervalMs;
|
|
584
|
-
const maxAttempts = pollOptions.maxAttempts ?? defaultMaxAttempts;
|
|
585
|
-
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
586
|
-
if (pollOptions.signal?.aborted) {
|
|
587
|
-
throw new ApiError("Operation aborted", { status: 499, code: "aborted" });
|
|
588
|
-
}
|
|
589
|
-
const next = await status(handle, pollOptions.signal);
|
|
590
|
-
if (isTerminal(next.status)) return next;
|
|
591
|
-
await sleep2(intervalMs);
|
|
592
|
-
}
|
|
593
|
-
throw new ApiError(
|
|
594
|
-
`Timed out waiting for workflow ${handle.workflow}:${handle.id}`,
|
|
595
|
-
{ status: 408, code: "timeout" }
|
|
596
|
-
);
|
|
597
|
-
};
|
|
598
|
-
const run = async (request, runOptions = {}) => {
|
|
599
|
-
const runMode = runOptions.mode;
|
|
600
|
-
const useExecute = runMode === "sync" || runMode === void 0 && !transport.submit;
|
|
601
|
-
if (useExecute) {
|
|
602
|
-
const raw = await transport.execute(request);
|
|
603
|
-
const syntheticHandle = { workflow: request.workflow, id: "sync" };
|
|
604
|
-
const parsed = parseStatus(syntheticHandle, raw);
|
|
605
|
-
return parsed.status === "UNKNOWN" ? { ...parsed, status: "COMPLETED" } : parsed;
|
|
606
|
-
}
|
|
607
|
-
const handle = await submit(request);
|
|
608
|
-
return result(handle, runOptions);
|
|
609
|
-
};
|
|
610
|
-
const subscribe = async function* (handle, subscribeOptions = {}) {
|
|
611
|
-
const intervalMs = subscribeOptions.intervalMs ?? defaultPollIntervalMs;
|
|
612
|
-
const maxAttempts = subscribeOptions.maxAttempts ?? defaultMaxAttempts;
|
|
613
|
-
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
614
|
-
if (subscribeOptions.signal?.aborted) {
|
|
615
|
-
throw new ApiError("Operation aborted", { status: 499, code: "aborted" });
|
|
616
|
-
}
|
|
617
|
-
const next = await status(handle, subscribeOptions.signal);
|
|
618
|
-
yield next;
|
|
619
|
-
if (isTerminal(next.status)) return next;
|
|
620
|
-
await sleep2(intervalMs);
|
|
621
|
-
}
|
|
622
|
-
throw new ApiError(
|
|
623
|
-
`Timed out waiting for workflow ${handle.workflow}:${handle.id}`,
|
|
624
|
-
{ status: 408, code: "timeout" }
|
|
625
|
-
);
|
|
626
|
-
};
|
|
627
|
-
return { submit, status, result, run, subscribe };
|
|
628
|
-
}
|
|
629
|
-
|
|
630
419
|
// src/core/descriptors/utils.ts
|
|
631
420
|
function extractDefaults(params2) {
|
|
632
421
|
const defaults = {};
|
|
@@ -851,11 +640,47 @@ function transferValues(newParams, prev) {
|
|
|
851
640
|
return ctx;
|
|
852
641
|
}
|
|
853
642
|
|
|
643
|
+
// src/core/errors.ts
|
|
644
|
+
var ApiError = class extends Error {
|
|
645
|
+
/** HTTP status, or the synthesized equivalent for non-HTTP failures. */
|
|
646
|
+
status;
|
|
647
|
+
/** Platform `reason`, or an SDK-synthesized code. Always equal to {@link reason}. */
|
|
648
|
+
code;
|
|
649
|
+
/** Alias of {@link code}, named after the platform's own error field. */
|
|
650
|
+
reason;
|
|
651
|
+
constructor(message, init) {
|
|
652
|
+
super(message);
|
|
653
|
+
this.name = "ApiError";
|
|
654
|
+
this.status = init.status;
|
|
655
|
+
this.code = init.code;
|
|
656
|
+
this.reason = init.code;
|
|
657
|
+
}
|
|
658
|
+
};
|
|
659
|
+
var CODE_BY_STATUS = {
|
|
660
|
+
400: "bad_request",
|
|
661
|
+
401: "unauthorized",
|
|
662
|
+
402: "payment_required",
|
|
663
|
+
403: "forbidden",
|
|
664
|
+
404: "not_found",
|
|
665
|
+
408: "timeout",
|
|
666
|
+
409: "conflict",
|
|
667
|
+
413: "payload_too_large",
|
|
668
|
+
422: "unprocessable_entity",
|
|
669
|
+
429: "rate_limited",
|
|
670
|
+
500: "server_error",
|
|
671
|
+
502: "bad_gateway",
|
|
672
|
+
503: "service_unavailable",
|
|
673
|
+
504: "gateway_timeout"
|
|
674
|
+
};
|
|
675
|
+
function codeForStatus(status) {
|
|
676
|
+
return CODE_BY_STATUS[status] ?? (status >= 500 ? "server_error" : `http_${status}`);
|
|
677
|
+
}
|
|
678
|
+
|
|
854
679
|
// src/core/visibility.ts
|
|
855
680
|
var DEFAULT_VISIBLE_RELEASES = ["production", "general-availability"];
|
|
856
681
|
var releaseOf = (m) => m.release ?? "production";
|
|
857
682
|
function isVisibleForReleases(m, releases = DEFAULT_VISIBLE_RELEASES) {
|
|
858
|
-
if (m.
|
|
683
|
+
if (m.deprecated) return false;
|
|
859
684
|
return releases.includes(releaseOf(m));
|
|
860
685
|
}
|
|
861
686
|
|
|
@@ -1290,7 +1115,6 @@ function defineModels(provider, configs) {
|
|
|
1290
1115
|
if (c.pollOptions !== void 0) model.pollOptions = c.pollOptions;
|
|
1291
1116
|
if (c.badge !== void 0) model.badge = c.badge;
|
|
1292
1117
|
if (c.addedAt !== void 0) model.addedAt = c.addedAt;
|
|
1293
|
-
if (c.disabled !== void 0) model.disabled = c.disabled;
|
|
1294
1118
|
if (c.deprecated !== void 0) model.deprecated = c.deprecated;
|
|
1295
1119
|
if (c.release !== void 0) model.release = c.release;
|
|
1296
1120
|
if (c.modelId !== void 0) model.modelId = c.modelId;
|
|
@@ -1523,23 +1347,6 @@ var klingOmniAdvancedParams = {
|
|
|
1523
1347
|
};
|
|
1524
1348
|
|
|
1525
1349
|
// src/vendors/catalog/kling/index.ts
|
|
1526
|
-
var KLING_DUAL_IMAGE_EFFECTS = /* @__PURE__ */ new Set([
|
|
1527
|
-
"pet_skateboard",
|
|
1528
|
-
"daily_ootd",
|
|
1529
|
-
"toss_run",
|
|
1530
|
-
"switch_to_silk",
|
|
1531
|
-
"studio_look",
|
|
1532
|
-
"french_elegance",
|
|
1533
|
-
"finger_swipe",
|
|
1534
|
-
"smooth_transition",
|
|
1535
|
-
"kiss_pro",
|
|
1536
|
-
"snow_night_kiss",
|
|
1537
|
-
"eternal_kiss",
|
|
1538
|
-
"cheers_2026",
|
|
1539
|
-
"fight_pro",
|
|
1540
|
-
"hug_pro",
|
|
1541
|
-
"heart_gesture_pro"
|
|
1542
|
-
]);
|
|
1543
1350
|
var V3_DURATIONS = [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
|
|
1544
1351
|
var V26_DURATIONS = [5, 10];
|
|
1545
1352
|
var KLING_IMAGE_AR = ["16:9", "9:16", "1:1", "21:9", "4:3", "3:2", "2:3", "3:4"];
|
|
@@ -1893,7 +1700,7 @@ var { MODELS } = defineModels("kling", [
|
|
|
1893
1700
|
id: "kling-elements",
|
|
1894
1701
|
name: "Kling Elements",
|
|
1895
1702
|
addedAt: "2026-05-11",
|
|
1896
|
-
|
|
1703
|
+
release: "preview",
|
|
1897
1704
|
// pending backend toolId + pricing confirmation
|
|
1898
1705
|
workflow: "kling-elements",
|
|
1899
1706
|
estimatedTime: 30,
|
|
@@ -2232,10 +2039,27 @@ var buildKlingElementsPayload = (input) => {
|
|
|
2232
2039
|
...input.elementVoiceId ? { element_voice_id: input.elementVoiceId } : {}
|
|
2233
2040
|
};
|
|
2234
2041
|
};
|
|
2042
|
+
var DUAL_IMAGE_EFFECTS = /* @__PURE__ */ new Set([
|
|
2043
|
+
"pet_skateboard",
|
|
2044
|
+
"daily_ootd",
|
|
2045
|
+
"toss_run",
|
|
2046
|
+
"switch_to_silk",
|
|
2047
|
+
"studio_look",
|
|
2048
|
+
"french_elegance",
|
|
2049
|
+
"finger_swipe",
|
|
2050
|
+
"smooth_transition",
|
|
2051
|
+
"kiss_pro",
|
|
2052
|
+
"snow_night_kiss",
|
|
2053
|
+
"eternal_kiss",
|
|
2054
|
+
"cheers_2026",
|
|
2055
|
+
"fight_pro",
|
|
2056
|
+
"hug_pro",
|
|
2057
|
+
"heart_gesture_pro"
|
|
2058
|
+
]);
|
|
2235
2059
|
var buildKlingVideoEffectsPayload = (input) => {
|
|
2236
|
-
const scene = input.templateId
|
|
2060
|
+
const scene = input.templateId;
|
|
2237
2061
|
const catalogItem = getHydratedCatalog({ workflow: "kling/v1/catalog/templates" })?.items.find((item) => item.id === scene);
|
|
2238
|
-
const slots = typeof catalogItem?.meta?.imageSlots === "number" ? catalogItem.meta.imageSlots : scene &&
|
|
2062
|
+
const slots = typeof catalogItem?.meta?.imageSlots === "number" ? catalogItem.meta.imageSlots : scene && DUAL_IMAGE_EFFECTS.has(scene) ? 2 : 1;
|
|
2239
2063
|
const uploaded = input.imageUrls?.length ?? 0;
|
|
2240
2064
|
if (uploaded < slots) {
|
|
2241
2065
|
throw new ApiError(`Kling Video Effects: the "${scene}" effect requires ${slots} image${slots > 1 ? "s" : ""} (got ${uploaded}).`, { status: 400, code: "validation_error" });
|
|
@@ -4734,8 +4558,8 @@ var GEMINI_DEFAULT_VOICE_ID = "Kore";
|
|
|
4734
4558
|
var DEFAULT_GROK_VOICE_ID = "eve";
|
|
4735
4559
|
var ASYNC_DEFAULT_VOICE_ID = "cca0e076-b350-4966-b570-4c2fca50b525";
|
|
4736
4560
|
var SEEDAUDIO_DEFAULT_VOICE_ID = "en_male_tim_uranus_bigtts";
|
|
4737
|
-
function getVoiceById(id
|
|
4738
|
-
return
|
|
4561
|
+
function getVoiceById(id) {
|
|
4562
|
+
return getHydratedVoices().find((v) => v.id === id);
|
|
4739
4563
|
}
|
|
4740
4564
|
|
|
4741
4565
|
// src/vendors/catalog/seedaudio.ts
|
|
@@ -6566,7 +6390,7 @@ var { MODELS: MODELS23 } = defineModels("elevenlabs", [
|
|
|
6566
6390
|
estimatedTime: 15,
|
|
6567
6391
|
mode: "audio",
|
|
6568
6392
|
inputType: "tts",
|
|
6569
|
-
|
|
6393
|
+
release: "preview",
|
|
6570
6394
|
description: "Remix voice characteristics by describing the desired vocal style.",
|
|
6571
6395
|
features: [feat("Voice Design", "characteristic"), feat("Remix", "characteristic")],
|
|
6572
6396
|
paramConfig: {
|
|
@@ -6735,9 +6559,6 @@ var { MODELS: MODELS24 } = defineModels("heygen", [
|
|
|
6735
6559
|
]);
|
|
6736
6560
|
|
|
6737
6561
|
// src/vendors/catalog/minimax.ts
|
|
6738
|
-
var buildMinimaxTTSPayload = (ctx) => ({
|
|
6739
|
-
text: ctx.prompt
|
|
6740
|
-
});
|
|
6741
6562
|
var buildMinimaxMusicPayload = (ctx) => ({
|
|
6742
6563
|
prompt: ctx.prompt,
|
|
6743
6564
|
...ctx.lyricsPrompt ? { lyrics: ctx.lyricsPrompt } : {},
|
|
@@ -6788,25 +6609,9 @@ var h3MaxConstraints = [
|
|
|
6788
6609
|
} }
|
|
6789
6610
|
];
|
|
6790
6611
|
var { MODELS: MODELS25 } = defineModels("minimax", [
|
|
6791
|
-
|
|
6792
|
-
|
|
6793
|
-
|
|
6794
|
-
modelId: "minimax-02-hd",
|
|
6795
|
-
addedAt: "2026-02-06",
|
|
6796
|
-
workflow: "minimax-tts",
|
|
6797
|
-
buildPayload: buildMinimaxTTSPayload,
|
|
6798
|
-
estimatedTime: 15,
|
|
6799
|
-
mode: "audio",
|
|
6800
|
-
inputType: "tts",
|
|
6801
|
-
disabled: true,
|
|
6802
|
-
// Backend workflow not deployed
|
|
6803
|
-
description: "HD voice synthesis with rich tonal depth and consistent delivery.",
|
|
6804
|
-
features: [feat("Consistent", "characteristic"), feat("Cinematic", "characteristic")],
|
|
6805
|
-
paramConfig: {
|
|
6806
|
-
...params.language(true),
|
|
6807
|
-
...params.prompt({ maxLength: 150 })
|
|
6808
|
-
}
|
|
6809
|
-
},
|
|
6612
|
+
// minimax-02-hd (minimax-tts) was removed in 6.0: its backend workflow was
|
|
6613
|
+
// never deployed anywhere, so no generation ever existed to resolve — nothing
|
|
6614
|
+
// to deprecate. Re-add as a fresh entry if MiniMax TTS ever ships.
|
|
6810
6615
|
{
|
|
6811
6616
|
id: "minimax-music-v2",
|
|
6812
6617
|
name: "MiniMax Music v2",
|
|
@@ -7115,6 +6920,13 @@ var buildIdeogramV4GeneratePayload = (ctx) => ({
|
|
|
7115
6920
|
...ctx.renderingSpeed ? { rendering_speed: ctx.renderingSpeed } : {},
|
|
7116
6921
|
...ctx.enableCopyrightDetection ? { enable_copyright_detection: true } : {}
|
|
7117
6922
|
});
|
|
6923
|
+
var buildIdeogramV4RemixPayload = (ctx) => ({
|
|
6924
|
+
text_prompt: ctx.prompt,
|
|
6925
|
+
image: ctx.startFrame ?? ctx.imageUrls?.[0],
|
|
6926
|
+
...ctx.imageWeight != null ? { image_weight: ctx.imageWeight } : {},
|
|
6927
|
+
...ctx.renderingSpeed ? { rendering_speed: ctx.renderingSpeed } : {},
|
|
6928
|
+
...ctx.enableCopyrightDetection ? { enable_copyright_detection: true } : {}
|
|
6929
|
+
});
|
|
7118
6930
|
var buildIdeogramPImagePayload = (ctx) => ({
|
|
7119
6931
|
prompt: ctx.prompt,
|
|
7120
6932
|
resolution: ctx.resolution ?? "1024x1024",
|
|
@@ -7126,15 +6938,18 @@ var { MODELS: MODELS26 } = defineModels("ideogram", [
|
|
|
7126
6938
|
name: "Ideogram 4.0",
|
|
7127
6939
|
addedAt: "2026-06-03",
|
|
7128
6940
|
workflow: "ideogram/v4/generate",
|
|
6941
|
+
editWorkflow: "ideogram/v4/remix",
|
|
7129
6942
|
buildPayload: buildIdeogramV4GeneratePayload,
|
|
6943
|
+
buildEditPayload: buildIdeogramV4RemixPayload,
|
|
7130
6944
|
estimatedTime: 20,
|
|
7131
6945
|
mode: "image",
|
|
7132
6946
|
inputType: "t2i",
|
|
7133
6947
|
description: "Ideogram's latest model \u2014 class-leading text rendering at up to ~3K resolution.",
|
|
7134
|
-
features: [feat("Text Rendering", "style"), feat("Up to 3K", "resolution")],
|
|
6948
|
+
features: [feat("Text Rendering", "style"), feat("Up to 3K", "resolution"), feat("Image Remix", "input")],
|
|
7135
6949
|
paramConfig: {
|
|
7136
6950
|
...params.prompt(),
|
|
7137
6951
|
...params.resolution([
|
|
6952
|
+
// 2K bucket (~3–4 MP)
|
|
7138
6953
|
"2048x2048",
|
|
7139
6954
|
"1440x2880",
|
|
7140
6955
|
"2880x1440",
|
|
@@ -7155,14 +6970,36 @@ var { MODELS: MODELS26 } = defineModels("ideogram", [
|
|
|
7155
6970
|
"1248x3328",
|
|
7156
6971
|
"3328x1248",
|
|
7157
6972
|
"1280x3072",
|
|
7158
|
-
"3072x1280"
|
|
6973
|
+
"3072x1280",
|
|
6974
|
+
"1024x3072",
|
|
6975
|
+
"3072x1024",
|
|
6976
|
+
// 1K bucket (~1 MP)
|
|
6977
|
+
"1024x1024",
|
|
6978
|
+
"896x1120",
|
|
6979
|
+
"1120x896",
|
|
6980
|
+
"864x1152",
|
|
6981
|
+
"1152x864",
|
|
6982
|
+
"832x1248",
|
|
6983
|
+
"1248x832",
|
|
6984
|
+
"800x1280",
|
|
6985
|
+
"1280x800",
|
|
6986
|
+
"720x1280",
|
|
6987
|
+
"1280x720",
|
|
6988
|
+
"720x1440",
|
|
6989
|
+
"1440x720",
|
|
6990
|
+
"512x1536",
|
|
6991
|
+
"1536x512"
|
|
7159
6992
|
], "2048x2048"),
|
|
7160
6993
|
...params.renderingSpeed([
|
|
7161
6994
|
{ id: "TURBO", label: "Turbo" },
|
|
7162
6995
|
{ id: "DEFAULT", label: "Balanced" },
|
|
7163
6996
|
{ id: "QUALITY", label: "Quality" }
|
|
7164
6997
|
], "DEFAULT"),
|
|
7165
|
-
...p.boolean("enableCopyrightDetection", false, "Copyright Detection")
|
|
6998
|
+
...p.boolean("enableCopyrightDetection", false, "Copyright Detection"),
|
|
6999
|
+
// Remix (editWorkflow) inputs — an image switches the request to
|
|
7000
|
+
// ideogram/v4/remix. Weight minimum is 1; the API rejects 0.
|
|
7001
|
+
...params.imageInput(1, "Source Image"),
|
|
7002
|
+
...params.imageWeight(1, 100, 50, 5)
|
|
7166
7003
|
}
|
|
7167
7004
|
},
|
|
7168
7005
|
{
|
|
@@ -9339,7 +9176,7 @@ var ALL_MODELS = [
|
|
|
9339
9176
|
...MODELS36,
|
|
9340
9177
|
...MODELS37
|
|
9341
9178
|
];
|
|
9342
|
-
var getModelsByMode = (mode,
|
|
9179
|
+
var getModelsByMode = (mode, includeHidden = false) => ALL_MODELS.filter((m) => m.mode === mode && (includeHidden || isVisibleForReleases(m)));
|
|
9343
9180
|
|
|
9344
9181
|
// src/core/contracts.ts
|
|
9345
9182
|
function requireObject(value, message) {
|
|
@@ -9384,9 +9221,6 @@ function createModelContract(model) {
|
|
|
9384
9221
|
output: buildOutputSchema(model)
|
|
9385
9222
|
};
|
|
9386
9223
|
}
|
|
9387
|
-
function validateModelInput(model, input) {
|
|
9388
|
-
return createModelContract(model).input.parse(input);
|
|
9389
|
-
}
|
|
9390
9224
|
var _contracts = null;
|
|
9391
9225
|
function ensureContracts() {
|
|
9392
9226
|
if (!_contracts) {
|
|
@@ -9425,10 +9259,7 @@ function throwIfErrorResult(result, modelName) {
|
|
|
9425
9259
|
function extractSyncResult(raw) {
|
|
9426
9260
|
if (!raw || typeof raw !== "object") return raw;
|
|
9427
9261
|
const data = raw;
|
|
9428
|
-
|
|
9429
|
-
const sr = syncResult;
|
|
9430
|
-
const imgs = sr && Array.isArray(sr.images) ? sr.images : null;
|
|
9431
|
-
return imgs?.length ? imgs[0] : syncResult;
|
|
9262
|
+
return data.response?.result ?? data.result;
|
|
9432
9263
|
}
|
|
9433
9264
|
var extractUrl = (result) => {
|
|
9434
9265
|
if (Array.isArray(result)) return extractUrl(result[0]);
|
|
@@ -9554,27 +9385,55 @@ var extractText = (result) => {
|
|
|
9554
9385
|
}
|
|
9555
9386
|
return void 0;
|
|
9556
9387
|
};
|
|
9388
|
+
var RESULT_ARRAY_KEYS = ["items", "images", "imageUrls", "urls", "data", "previews"];
|
|
9557
9389
|
var extractAllResults = (result) => {
|
|
9558
9390
|
if (!result || typeof result !== "object") return void 0;
|
|
9559
9391
|
const obj = result;
|
|
9560
|
-
|
|
9561
|
-
|
|
9562
|
-
|
|
9563
|
-
|
|
9564
|
-
|
|
9565
|
-
|
|
9566
|
-
if (url) {
|
|
9567
|
-
items.push({
|
|
9568
|
-
url,
|
|
9569
|
-
exploreImageId: typeof it.image_id === "string" ? it.image_id : void 0
|
|
9570
|
-
});
|
|
9571
|
-
}
|
|
9572
|
-
}
|
|
9392
|
+
let arr;
|
|
9393
|
+
for (const key of RESULT_ARRAY_KEYS) {
|
|
9394
|
+
const candidate = obj[key];
|
|
9395
|
+
if (Array.isArray(candidate) && candidate.length > 1) {
|
|
9396
|
+
arr = candidate;
|
|
9397
|
+
break;
|
|
9573
9398
|
}
|
|
9574
|
-
if (items.length > 0) return items;
|
|
9575
9399
|
}
|
|
9576
|
-
return void 0;
|
|
9400
|
+
if (!arr) return void 0;
|
|
9401
|
+
const items = [];
|
|
9402
|
+
for (const entry of arr) {
|
|
9403
|
+
if (typeof entry === "string") {
|
|
9404
|
+
items.push({ url: entry });
|
|
9405
|
+
continue;
|
|
9406
|
+
}
|
|
9407
|
+
if (entry && typeof entry === "object") {
|
|
9408
|
+
const it = entry;
|
|
9409
|
+
if (typeof it.url === "string") items.push({ url: it.url, source: it });
|
|
9410
|
+
}
|
|
9411
|
+
}
|
|
9412
|
+
return items.length > 0 ? items : void 0;
|
|
9577
9413
|
};
|
|
9414
|
+
function buildItemMetadata(parsed, item, index, provider) {
|
|
9415
|
+
const meta = {};
|
|
9416
|
+
const top = parsed && typeof parsed === "object" ? parsed : void 0;
|
|
9417
|
+
const it = item && typeof item === "object" && !Array.isArray(item) ? item : void 0;
|
|
9418
|
+
if (provider === "recraft" && typeof it?.image_id === "string") meta.exploreImageId = it.image_id;
|
|
9419
|
+
if (provider === "elevenlabs" && typeof it?.generated_voice_id === "string") meta.generatedVoiceId = it.generated_voice_id;
|
|
9420
|
+
if (typeof top?.seed === "number") meta.seed = top.seed;
|
|
9421
|
+
if (Array.isArray(top?.has_nsfw_concepts) && typeof top.has_nsfw_concepts[index] === "boolean") {
|
|
9422
|
+
meta.nsfw = top.has_nsfw_concepts[index];
|
|
9423
|
+
}
|
|
9424
|
+
if (typeof it?.width === "number") meta.width = it.width;
|
|
9425
|
+
if (typeof it?.height === "number") meta.height = it.height;
|
|
9426
|
+
if (typeof it?.content_type === "string") meta.contentType = it.content_type;
|
|
9427
|
+
const video = top?.video && typeof top.video === "object" ? top.video : void 0;
|
|
9428
|
+
if (video) {
|
|
9429
|
+
if (typeof video.duration === "number") meta.duration = video.duration;
|
|
9430
|
+
if (typeof video.fps === "number") meta.fps = video.fps;
|
|
9431
|
+
if (typeof video.file_size === "number") meta.fileSize = video.file_size;
|
|
9432
|
+
if (meta.width === void 0 && typeof video.width === "number") meta.width = video.width;
|
|
9433
|
+
if (meta.height === void 0 && typeof video.height === "number") meta.height = video.height;
|
|
9434
|
+
}
|
|
9435
|
+
return Object.keys(meta).length > 0 ? meta : void 0;
|
|
9436
|
+
}
|
|
9578
9437
|
function toCompletedStatus(handle, result, raw, usage) {
|
|
9579
9438
|
return {
|
|
9580
9439
|
handle,
|
|
@@ -9627,870 +9486,839 @@ function resolveModel(id) {
|
|
|
9627
9486
|
return found;
|
|
9628
9487
|
}
|
|
9629
9488
|
|
|
9630
|
-
//
|
|
9631
|
-
var
|
|
9632
|
-
|
|
9633
|
-
|
|
9489
|
+
// ../../node_modules/@picsart/workflows-client/dist/index.mjs
|
|
9490
|
+
var logger_default = {
|
|
9491
|
+
error: (...args) => {
|
|
9492
|
+
console.error(...args);
|
|
9493
|
+
},
|
|
9494
|
+
warn: (...args) => {
|
|
9495
|
+
console.debug(...args);
|
|
9496
|
+
},
|
|
9497
|
+
info: (...args) => {
|
|
9498
|
+
console.info(...args);
|
|
9499
|
+
},
|
|
9500
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9501
|
+
debug: (...args) => {
|
|
9502
|
+
console.debug(...args);
|
|
9503
|
+
}
|
|
9634
9504
|
};
|
|
9635
|
-
|
|
9636
|
-
|
|
9637
|
-
|
|
9638
|
-
|
|
9639
|
-
|
|
9640
|
-
|
|
9641
|
-
|
|
9642
|
-
|
|
9643
|
-
|
|
9644
|
-
|
|
9645
|
-
|
|
9646
|
-
|
|
9505
|
+
var ExecutionMode = /* @__PURE__ */ ((ExecutionMode2) => {
|
|
9506
|
+
ExecutionMode2["ASYNC"] = "ASYNC";
|
|
9507
|
+
ExecutionMode2["SYNC"] = "SYNC";
|
|
9508
|
+
ExecutionMode2["STREAM"] = "STREAM";
|
|
9509
|
+
ExecutionMode2["SOCKET"] = "SOCKET";
|
|
9510
|
+
return ExecutionMode2;
|
|
9511
|
+
})(ExecutionMode || {});
|
|
9512
|
+
var STREAM_EVENT_NAME = "task.stream";
|
|
9513
|
+
var normalizeWorkflowName = (workflow) => (workflow || "").replace(/-/g, "_").toLowerCase();
|
|
9514
|
+
var taskChannel = (workflow, taskId) => `workflows:${normalizeWorkflowName(workflow)}:${taskId}`;
|
|
9515
|
+
var workflowChannel = (workflow) => `workflows:${normalizeWorkflowName(workflow)}:all`;
|
|
9516
|
+
var DEFAULT_REASON = "unknown_error";
|
|
9517
|
+
var DEFAULT_MESSAGE = "Unknown error";
|
|
9518
|
+
var NON_JSON_BODY_MESSAGE = "Non json response was returned from server";
|
|
9519
|
+
var asRecord = (value) => value && typeof value === "object" ? value : void 0;
|
|
9520
|
+
var asText = (value) => typeof value === "string" && value.length > 0 ? value : void 0;
|
|
9521
|
+
var asStatusCode = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
9522
|
+
var WorkflowsError = class _WorkflowsError extends Error {
|
|
9523
|
+
constructor(init) {
|
|
9524
|
+
super(init.message);
|
|
9525
|
+
this.name = this.constructor.name;
|
|
9526
|
+
this.reason = init.reason;
|
|
9527
|
+
this.httpStatusCode = init.httpStatusCode;
|
|
9647
9528
|
}
|
|
9648
|
-
|
|
9649
|
-
|
|
9650
|
-
|
|
9651
|
-
|
|
9652
|
-
|
|
9653
|
-
|
|
9654
|
-
|
|
9655
|
-
|
|
9656
|
-
|
|
9657
|
-
|
|
9658
|
-
|
|
9659
|
-
|
|
9660
|
-
async submit(request) {
|
|
9661
|
-
const res = await jsonPost(
|
|
9662
|
-
`${apiUrl}/workflows/${request.workflow}/submit`,
|
|
9663
|
-
{ params: request.payload },
|
|
9664
|
-
request.signal
|
|
9665
|
-
);
|
|
9666
|
-
const { text, json } = await readErrorBody(res);
|
|
9667
|
-
if (!res.ok) {
|
|
9668
|
-
const detail = json ? json.message ?? JSON.stringify(json) : text;
|
|
9669
|
-
throw new ApiError(`Submit failed (${res.status}): ${detail}`, {
|
|
9670
|
-
status: res.status,
|
|
9671
|
-
code: reasonFrom(json, res.status)
|
|
9672
|
-
});
|
|
9673
|
-
}
|
|
9674
|
-
const response = json?.response;
|
|
9675
|
-
const id = response?.id ?? json?.id;
|
|
9676
|
-
if (!id) {
|
|
9677
|
-
throw new ApiError(`No task id in response: ${json ? JSON.stringify(json) : text}`, {
|
|
9678
|
-
status: 502,
|
|
9679
|
-
code: "invalid_response"
|
|
9680
|
-
});
|
|
9681
|
-
}
|
|
9682
|
-
return { workflow: request.workflow, id: String(id) };
|
|
9683
|
-
},
|
|
9684
|
-
async status(handle, signal) {
|
|
9685
|
-
const res = await f(`${apiUrl}/workflows/${handle.workflow}/${handle.id}/result`, { signal });
|
|
9686
|
-
if (!res.ok) {
|
|
9687
|
-
const { text, json } = await readErrorBody(res);
|
|
9688
|
-
const detail = json ? json.message ?? text : text;
|
|
9689
|
-
throw new ApiError(`Status check failed (${res.status}): ${detail}`, {
|
|
9690
|
-
status: res.status,
|
|
9691
|
-
code: reasonFrom(json, res.status)
|
|
9692
|
-
});
|
|
9693
|
-
}
|
|
9694
|
-
return res.json();
|
|
9695
|
-
},
|
|
9696
|
-
async execute(request) {
|
|
9697
|
-
const res = await jsonPost(
|
|
9698
|
-
`${apiUrl}/workflows/${request.workflow}/execute`,
|
|
9699
|
-
{ params: request.payload },
|
|
9700
|
-
request.signal
|
|
9701
|
-
);
|
|
9702
|
-
if (!res.ok) {
|
|
9703
|
-
const { text, json } = await readErrorBody(res);
|
|
9704
|
-
const detail = json ? json.message ?? text : text;
|
|
9705
|
-
throw new ApiError(`Execute failed (${res.status}): ${detail}`, {
|
|
9706
|
-
status: res.status,
|
|
9707
|
-
code: reasonFrom(json, res.status)
|
|
9708
|
-
});
|
|
9709
|
-
}
|
|
9710
|
-
return res.json();
|
|
9711
|
-
},
|
|
9712
|
-
async options(workflow, payload) {
|
|
9713
|
-
try {
|
|
9714
|
-
const res = await jsonPost(`${apiUrl}/workflows/${workflow}/options`, { params: payload });
|
|
9715
|
-
if (!res.ok) return null;
|
|
9716
|
-
const data = await res.json();
|
|
9717
|
-
const response = data.response;
|
|
9718
|
-
const credits = response?.credits;
|
|
9719
|
-
return typeof credits === "number" ? credits : null;
|
|
9720
|
-
} catch {
|
|
9721
|
-
return null;
|
|
9722
|
-
}
|
|
9723
|
-
}
|
|
9724
|
-
};
|
|
9725
|
-
}
|
|
9726
|
-
function isClientConfig(input) {
|
|
9727
|
-
return "fetch" in input && typeof input.fetch === "function" || "apiKey" in input && typeof input.apiKey === "string";
|
|
9728
|
-
}
|
|
9729
|
-
|
|
9730
|
-
// src/client/prepare.ts
|
|
9731
|
-
function resolvePayloadBuild(model, ctx) {
|
|
9732
|
-
const hasImages = Array.isArray(ctx.imageUrls) && ctx.imageUrls.length > 0 || !!ctx.startFrame || !!ctx.endFrame;
|
|
9733
|
-
return {
|
|
9734
|
-
hasImages,
|
|
9735
|
-
workflow: hasImages && model.editWorkflow ? model.editWorkflow : model.workflow,
|
|
9736
|
-
buildPayload: hasImages && model.buildEditPayload ? model.buildEditPayload : model.buildPayload ?? ((ctx2) => ({ prompt: ctx2.prompt }))
|
|
9737
|
-
};
|
|
9738
|
-
}
|
|
9739
|
-
function prepareRequest(model, params2) {
|
|
9740
|
-
const ctx = { ...params2 };
|
|
9741
|
-
const contract = getModelContract(model.id);
|
|
9742
|
-
const validatedCtx = contract ? contract.input.parse(ctx) : ctx;
|
|
9743
|
-
const resolved = resolvePayloadBuild(model, validatedCtx);
|
|
9744
|
-
const payload = resolved.buildPayload(validatedCtx);
|
|
9745
|
-
return { ctx, workflow: resolved.workflow, payload, contract };
|
|
9746
|
-
}
|
|
9747
|
-
function throwIfTerminalFailure(completed, model) {
|
|
9748
|
-
if (completed.status === "FAILED") {
|
|
9749
|
-
throw new ApiError(`${model.name} failed: ${completed.error ?? "unknown error"}`, {
|
|
9750
|
-
status: completed.statusCode ?? 502,
|
|
9751
|
-
code: completed.reason ?? "generation_failed"
|
|
9529
|
+
/**
|
|
9530
|
+
* Builds an error from an already-parsed error payload — a `FailedResult` off a stream/socket
|
|
9531
|
+
* event, or any body with `reason` / `message` / `statusCode`. Each field falls back to
|
|
9532
|
+
* `fallback` individually, so a payload that carries only a message still keeps the caller's
|
|
9533
|
+
* reason and status.
|
|
9534
|
+
*/
|
|
9535
|
+
static fromBody(body, fallback) {
|
|
9536
|
+
const parsed = asRecord(body);
|
|
9537
|
+
return new _WorkflowsError({
|
|
9538
|
+
reason: asText(parsed?.reason) || fallback.reason,
|
|
9539
|
+
message: asText(parsed?.message) || fallback.message,
|
|
9540
|
+
httpStatusCode: asStatusCode(parsed?.statusCode) ?? fallback.httpStatusCode
|
|
9752
9541
|
});
|
|
9753
9542
|
}
|
|
9754
|
-
|
|
9755
|
-
|
|
9756
|
-
|
|
9757
|
-
|
|
9758
|
-
|
|
9759
|
-
|
|
9760
|
-
|
|
9761
|
-
|
|
9762
|
-
|
|
9763
|
-
|
|
9764
|
-
|
|
9765
|
-
|
|
9766
|
-
|
|
9767
|
-
|
|
9768
|
-
|
|
9769
|
-
|
|
9770
|
-
|
|
9771
|
-
if (!url) {
|
|
9772
|
-
throw new ApiError(`${model.name}: unexpected response \u2014 no result URL`, {
|
|
9773
|
-
status: 502,
|
|
9774
|
-
code: "invalid_response"
|
|
9543
|
+
/**
|
|
9544
|
+
* Builds an error from a non-ok `Response`. The status always comes from the response itself;
|
|
9545
|
+
* `reason` and `message` come from the JSON body when it has them, otherwise from `fallback`
|
|
9546
|
+
* (a body that isn't JSON at all is reported as such rather than throwing a parse error).
|
|
9547
|
+
*/
|
|
9548
|
+
static async fromResponse(response, fallback) {
|
|
9549
|
+
let body;
|
|
9550
|
+
try {
|
|
9551
|
+
body = await response.json();
|
|
9552
|
+
} catch {
|
|
9553
|
+
body = void 0;
|
|
9554
|
+
}
|
|
9555
|
+
const parsed = asRecord(body);
|
|
9556
|
+
return new _WorkflowsError({
|
|
9557
|
+
reason: asText(parsed?.reason) || fallback?.reason || DEFAULT_REASON,
|
|
9558
|
+
message: asText(parsed?.message) || fallback?.message || (parsed ? DEFAULT_MESSAGE : NON_JSON_BODY_MESSAGE),
|
|
9559
|
+
httpStatusCode: response.status
|
|
9775
9560
|
});
|
|
9776
9561
|
}
|
|
9777
|
-
|
|
9778
|
-
|
|
9779
|
-
|
|
9780
|
-
|
|
9781
|
-
|
|
9782
|
-
throwIfErrorResult(completed.raw, model.name);
|
|
9783
|
-
const text = extractText(completed.result) ?? extractText(completed.raw);
|
|
9784
|
-
if (text == null) {
|
|
9785
|
-
throw new ApiError(`${model.name}: unexpected response \u2014 no text`, {
|
|
9786
|
-
status: 502,
|
|
9787
|
-
code: "invalid_response"
|
|
9562
|
+
/** Wraps an unexpected local throw (anything that isn't already a WorkflowsError). */
|
|
9563
|
+
static fromUnknown(error) {
|
|
9564
|
+
return new _WorkflowsError({
|
|
9565
|
+
reason: DEFAULT_REASON,
|
|
9566
|
+
message: (error instanceof Error ? error.message : asText(error)) || DEFAULT_MESSAGE
|
|
9788
9567
|
});
|
|
9789
9568
|
}
|
|
9790
|
-
|
|
9791
|
-
|
|
9792
|
-
|
|
9793
|
-
|
|
9794
|
-
|
|
9795
|
-
|
|
9796
|
-
|
|
9797
|
-
|
|
9798
|
-
|
|
9799
|
-
|
|
9800
|
-
|
|
9801
|
-
|
|
9802
|
-
|
|
9803
|
-
|
|
9804
|
-
|
|
9805
|
-
|
|
9806
|
-
|
|
9807
|
-
|
|
9808
|
-
return `${slug}-${shortId}.${ext}`;
|
|
9809
|
-
}
|
|
9810
|
-
function inferMediaType(file) {
|
|
9811
|
-
const name = String(file.name || "");
|
|
9812
|
-
if (/\.(mp3|wav|ogg|aac|flac|m4a)$/i.test(name)) return "audio";
|
|
9813
|
-
if (/\.(mp4|webm|mov|avi|mkv|m4v|wmv)$/i.test(name)) return "video";
|
|
9814
|
-
const contentType = file.contentType ?? file.content;
|
|
9815
|
-
const resourceType = String(contentType?.resourceType || "").toUpperCase();
|
|
9816
|
-
if (resourceType === "VIDEO") return "video";
|
|
9817
|
-
if (resourceType === "AUDIO") return "audio";
|
|
9818
|
-
return "image";
|
|
9819
|
-
}
|
|
9820
|
-
function contentResourceTypes(type) {
|
|
9821
|
-
if (type === "image") return "PHOTO";
|
|
9822
|
-
if (type === "video") return "VIDEO";
|
|
9823
|
-
if (type === "audio") return "AUDIO";
|
|
9824
|
-
return "PHOTO,VIDEO,AUDIO";
|
|
9825
|
-
}
|
|
9826
|
-
function normalizeUrl(raw) {
|
|
9827
|
-
if (typeof raw !== "string") return void 0;
|
|
9828
|
-
return raw.trim() || void 0;
|
|
9829
|
-
}
|
|
9830
|
-
function parseAttributes(raw) {
|
|
9831
|
-
const map = {};
|
|
9832
|
-
if (!raw || typeof raw !== "object") return map;
|
|
9833
|
-
if (Array.isArray(raw)) {
|
|
9834
|
-
for (const a of raw) {
|
|
9835
|
-
map[a.property] = String(a.value);
|
|
9836
|
-
}
|
|
9837
|
-
} else {
|
|
9838
|
-
for (const [k, v] of Object.entries(raw)) {
|
|
9839
|
-
map[k] = String(v);
|
|
9569
|
+
};
|
|
9570
|
+
var WorkflowResultUpdateAware = class {
|
|
9571
|
+
constructor(onProgressFn, onPartialResultFn, onEventFn) {
|
|
9572
|
+
this.onProgressFn = onProgressFn;
|
|
9573
|
+
this.onPartialResultFn = onPartialResultFn;
|
|
9574
|
+
this.onEventFn = onEventFn;
|
|
9575
|
+
}
|
|
9576
|
+
async onUpdate(response) {
|
|
9577
|
+
if (!response.updated) return;
|
|
9578
|
+
await this.deliverEvents(response);
|
|
9579
|
+
if (response.status !== "IN_PROGRESS") return;
|
|
9580
|
+
const newUpdated = new Date(response.updated);
|
|
9581
|
+
if (newUpdated?.getTime() !== this.updated?.getTime()) {
|
|
9582
|
+
this.updated = newUpdated;
|
|
9583
|
+
await this.onPartialResultFn?.(response);
|
|
9584
|
+
if (response.progress) {
|
|
9585
|
+
await this.onProgressFn?.(response.progress);
|
|
9586
|
+
}
|
|
9840
9587
|
}
|
|
9841
9588
|
}
|
|
9842
|
-
|
|
9843
|
-
|
|
9844
|
-
|
|
9845
|
-
|
|
9846
|
-
|
|
9847
|
-
|
|
9848
|
-
|
|
9849
|
-
|
|
9850
|
-
|
|
9851
|
-
|
|
9852
|
-
|
|
9853
|
-
|
|
9589
|
+
async deliverEvents(response) {
|
|
9590
|
+
if (!response.events?.length || !this.onEventFn) return;
|
|
9591
|
+
let startIdx = 0;
|
|
9592
|
+
if (this._lastEventId) {
|
|
9593
|
+
const lastSeenIdx = response.events.findIndex((e) => e.id === this._lastEventId);
|
|
9594
|
+
if (lastSeenIdx !== -1) {
|
|
9595
|
+
startIdx = lastSeenIdx + 1;
|
|
9596
|
+
}
|
|
9597
|
+
}
|
|
9598
|
+
const newEvents = response.events.slice(startIdx);
|
|
9599
|
+
for (const event of newEvents) {
|
|
9600
|
+
await this.onEventFn(event);
|
|
9601
|
+
}
|
|
9602
|
+
if (newEvents.length > 0) {
|
|
9603
|
+
this._lastEventId = newEvents[newEvents.length - 1].id;
|
|
9604
|
+
}
|
|
9854
9605
|
}
|
|
9855
|
-
}
|
|
9856
|
-
var
|
|
9857
|
-
var
|
|
9858
|
-
|
|
9859
|
-
|
|
9860
|
-
|
|
9861
|
-
|
|
9862
|
-
|
|
9863
|
-
|
|
9606
|
+
};
|
|
9607
|
+
var bearer = (token) => token.startsWith("Bearer ") ? token : `Bearer ${token}`;
|
|
9608
|
+
var WorkflowsSocket = class {
|
|
9609
|
+
constructor(config) {
|
|
9610
|
+
this.config = config;
|
|
9611
|
+
this.ownsSocket = false;
|
|
9612
|
+
this.channelRefs = /* @__PURE__ */ new WeakMap();
|
|
9613
|
+
}
|
|
9614
|
+
// Watch already-submitted work live, as an async iterable of the raw StreamSocketMessage the gateway
|
|
9615
|
+
// pushes. Iterate with `for await` and switch on `msg.type` (EventTypes.* or an `event.<custom>`
|
|
9616
|
+
// string), reading `msg.payload`. With a taskId, watch that one task and end after its COMPLETED /
|
|
9617
|
+
// FAILED; without, watch every task of the workflow until stopped. A lost socket session (or the
|
|
9618
|
+
// passed AbortSignal) is THROWN out of the loop; `break` also stops watching — the `finally` tears
|
|
9619
|
+
// everything down.
|
|
9620
|
+
async *subscribe(options) {
|
|
9621
|
+
if (!this.config.socket && !this.config.socketConnection) {
|
|
9622
|
+
throw new WorkflowsError({
|
|
9623
|
+
httpStatusCode: 400,
|
|
9624
|
+
reason: "invalid_state",
|
|
9625
|
+
message: "subscribe() requires either `socket` or `socketConnection` on the client."
|
|
9626
|
+
});
|
|
9627
|
+
}
|
|
9628
|
+
if (options.signal?.aborted) throw new DOMException("Aborted", "AbortError");
|
|
9629
|
+
const channel = options.taskId ? taskChannel(options.name, options.taskId) : workflowChannel(options.name);
|
|
9630
|
+
this.assertValidChannel(channel);
|
|
9631
|
+
const socket = await this.resolveSocket();
|
|
9632
|
+
if (!socket) return;
|
|
9633
|
+
if (options.signal?.aborted) throw new DOMException("Aborted", "AbortError");
|
|
9634
|
+
const workflow = normalizeWorkflowName(options.name);
|
|
9635
|
+
const queue = [];
|
|
9636
|
+
let wake;
|
|
9637
|
+
const push = (item) => {
|
|
9638
|
+
queue.push(item);
|
|
9639
|
+
const w = wake;
|
|
9640
|
+
wake = void 0;
|
|
9641
|
+
w?.();
|
|
9642
|
+
};
|
|
9643
|
+
const handler = (message) => {
|
|
9644
|
+
if (!message) return;
|
|
9645
|
+
const mine = options.taskId ? message.taskId === options.taskId : message.workflow === workflow;
|
|
9646
|
+
if (mine) push({ msg: message });
|
|
9647
|
+
};
|
|
9648
|
+
socket.on(STREAM_EVENT_NAME, handler);
|
|
9649
|
+
const streamEnded = (detail) => push({ error: new WorkflowsError({
|
|
9650
|
+
httpStatusCode: 503,
|
|
9651
|
+
reason: "socket_connection_lost",
|
|
9652
|
+
message: `Lost the socket connection (${detail}); the event stream ended.`
|
|
9653
|
+
}) });
|
|
9654
|
+
const detachReconnect = this.onSessionLost(socket, () => streamEnded("the session could not be recovered on reconnect"));
|
|
9655
|
+
const detachAbandoned = this.onConnectAbandoned(socket, (err) => streamEnded(
|
|
9656
|
+
`socket.io stopped reconnecting after "${err instanceof Error ? err.message : String(err)}"`
|
|
9657
|
+
));
|
|
9658
|
+
const detachDisconnect = this.onDisconnected(socket, (reason) => streamEnded(`"${reason}"`));
|
|
9659
|
+
const onAbort = () => push({ error: new DOMException("Aborted", "AbortError") });
|
|
9660
|
+
options.signal?.addEventListener("abort", onAbort);
|
|
9661
|
+
this.joinChannel(socket, channel);
|
|
9662
|
+
try {
|
|
9663
|
+
while (true) {
|
|
9664
|
+
while (queue.length) {
|
|
9665
|
+
const item = queue.shift();
|
|
9666
|
+
if ("error" in item) throw item.error;
|
|
9667
|
+
yield item.msg;
|
|
9668
|
+
if (options.taskId && (item.msg.type === "task.completed" || item.msg.type === "task.failed")) return;
|
|
9669
|
+
}
|
|
9670
|
+
await new Promise((resolve) => {
|
|
9671
|
+
wake = resolve;
|
|
9672
|
+
});
|
|
9673
|
+
}
|
|
9674
|
+
} finally {
|
|
9675
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
9676
|
+
this.leaveChannel(socket, channel);
|
|
9677
|
+
socket.off(STREAM_EVENT_NAME, handler);
|
|
9678
|
+
detachReconnect();
|
|
9679
|
+
detachAbandoned();
|
|
9680
|
+
detachDisconnect();
|
|
9681
|
+
}
|
|
9682
|
+
}
|
|
9683
|
+
// Opens the socket (create + connect for socketConnection; return an injected one). Idempotent —
|
|
9684
|
+
// resolveSocket memoizes, so repeated calls reuse the same connection.
|
|
9685
|
+
connect() {
|
|
9686
|
+
return this.resolveSocket();
|
|
9687
|
+
}
|
|
9688
|
+
// Disconnects the socket ONLY if this class created it (socketConnection); an injected socket is
|
|
9689
|
+
// left for the caller to manage. Safe to call more than once.
|
|
9690
|
+
async disconnect() {
|
|
9691
|
+
if (!this.ownsSocket || !this.socketPromise) return;
|
|
9692
|
+
const socket = await this.socketPromise.catch(() => void 0);
|
|
9693
|
+
socket?.disconnect();
|
|
9694
|
+
this.socketPromise = void 0;
|
|
9695
|
+
this.ownsSocket = false;
|
|
9696
|
+
}
|
|
9697
|
+
// Backs run({ mode: ExecutionMode.SOCKET }). Joins the workflow channel and starts listening BEFORE
|
|
9698
|
+
// submitting, so no early event is missed — the per-task channel can't be joined until submit mints
|
|
9699
|
+
// the taskId. Events seen before we know our taskId are buffered, then matched once we have it (the
|
|
9700
|
+
// workflow room carries sibling tasks, so the taskId filter is load-bearing).
|
|
9701
|
+
// `markSeen` is fired with the taskId the moment a terminal (COMPLETED/FAILED) event is received — the
|
|
9702
|
+
// socket-mode equivalent of the polling client fetching /result, which is what disabled the task's
|
|
9703
|
+
// pending notification server-side. Fired for both terminal outcomes (matching the old poll, which
|
|
9704
|
+
// disabled on COMPLETED and FAILED alike) and ONLY on a real terminal event — never on a lost session,
|
|
9705
|
+
// abort, or connect error, so an unconsumed result still triggers the async fallback notification.
|
|
9706
|
+
// It's best-effort: runTask fires it without awaiting and swallows any rejection, since a failed disable
|
|
9707
|
+
// only costs a redundant fallback notification and must never fail the run.
|
|
9708
|
+
async runTask(workflowName, submit, executionOptions, markSeen) {
|
|
9709
|
+
const signal = executionOptions?.abortSignal;
|
|
9710
|
+
await this.ensureSocketReady(workflowName, signal);
|
|
9711
|
+
const { onProgress, onPartialResult, onEvent } = executionOptions ?? {};
|
|
9712
|
+
const ac = new AbortController();
|
|
9713
|
+
const forwardAbort = () => ac.abort();
|
|
9714
|
+
signal?.addEventListener("abort", forwardAbort);
|
|
9715
|
+
const iter = this.subscribe({ name: workflowName, signal: ac.signal })[Symbol.asyncIterator]();
|
|
9716
|
+
const firstPull = iter.next();
|
|
9717
|
+
firstPull.catch(() => {
|
|
9718
|
+
});
|
|
9719
|
+
try {
|
|
9720
|
+
const taskId = await submit();
|
|
9721
|
+
for (let pull = firstPull; ; pull = iter.next()) {
|
|
9722
|
+
const { value: message, done } = await pull;
|
|
9723
|
+
if (done) break;
|
|
9724
|
+
if (message.taskId !== taskId) continue;
|
|
9725
|
+
switch (message.type) {
|
|
9726
|
+
case "task.completed":
|
|
9727
|
+
void Promise.resolve(markSeen?.(taskId)).catch(() => void 0);
|
|
9728
|
+
return {
|
|
9729
|
+
result: message.payload?.result,
|
|
9730
|
+
usage: message.payload?.usage,
|
|
9731
|
+
status: "COMPLETED"
|
|
9732
|
+
/* COMPLETED */
|
|
9733
|
+
// a FAILED message throws below instead
|
|
9734
|
+
};
|
|
9735
|
+
case "task.failed":
|
|
9736
|
+
void Promise.resolve(markSeen?.(taskId)).catch(() => void 0);
|
|
9737
|
+
throw this.failureError(message.payload?.result ?? message.payload);
|
|
9738
|
+
case "task.metrics":
|
|
9739
|
+
await onProgress?.(message.payload);
|
|
9740
|
+
break;
|
|
9741
|
+
case "task.partial-result":
|
|
9742
|
+
await onPartialResult?.({ status: "IN_PROGRESS", result: message.payload });
|
|
9743
|
+
break;
|
|
9744
|
+
default:
|
|
9745
|
+
if (message.type.startsWith("event.")) {
|
|
9746
|
+
await onEvent?.({ ...message.payload, type: message.type.replace(/^event\./, "") });
|
|
9747
|
+
}
|
|
9748
|
+
}
|
|
9749
|
+
}
|
|
9750
|
+
throw new WorkflowsError({
|
|
9751
|
+
httpStatusCode: 500,
|
|
9752
|
+
reason: "socket_stream_ended",
|
|
9753
|
+
message: "Socket stream ended before the task completed."
|
|
9754
|
+
});
|
|
9755
|
+
} finally {
|
|
9756
|
+
signal?.removeEventListener("abort", forwardAbort);
|
|
9757
|
+
ac.abort();
|
|
9758
|
+
await iter.return?.();
|
|
9759
|
+
}
|
|
9760
|
+
}
|
|
9761
|
+
// Pre-submit gate for runTask: validate the channel and get the socket connected before any work is
|
|
9762
|
+
// submitted. The socket itself isn't returned — subscribe() re-resolves it — this only proves it's
|
|
9763
|
+
// reachable and honors an abort that fired before (or during) connecting, since addEventListener('abort')
|
|
9764
|
+
// never fires for an already-aborted signal.
|
|
9765
|
+
async ensureSocketReady(workflowName, signal) {
|
|
9766
|
+
if (signal?.aborted) throw new DOMException("Aborted", "AbortError");
|
|
9767
|
+
const channel = workflowChannel(workflowName);
|
|
9768
|
+
this.assertValidChannel(channel);
|
|
9769
|
+
const socket = await this.resolveSocket();
|
|
9770
|
+
if (!socket) {
|
|
9771
|
+
throw new WorkflowsError({
|
|
9772
|
+
httpStatusCode: 400,
|
|
9773
|
+
reason: "invalid_state",
|
|
9774
|
+
message: "ExecutionMode.SOCKET requires either `socket` or `socketConnection` on the client."
|
|
9775
|
+
});
|
|
9776
|
+
}
|
|
9777
|
+
if (signal?.aborted) throw new DOMException("Aborted", "AbortError");
|
|
9864
9778
|
}
|
|
9865
|
-
|
|
9866
|
-
|
|
9867
|
-
|
|
9868
|
-
|
|
9869
|
-
|
|
9870
|
-
|
|
9871
|
-
|
|
9872
|
-
|
|
9873
|
-
|
|
9874
|
-
|
|
9779
|
+
// Fails a run/subscription when a reconnect couldn't be recovered. The gateway has Connection State
|
|
9780
|
+
// Recovery: a reconnect within its window restores our room and replays missed events, so there's
|
|
9781
|
+
// nothing to do; past the window `recovered` is false and the stream is gone — and we don't refetch
|
|
9782
|
+
// from the DB. We only wire this after the socket is connected, so any 'connect' here is a reconnect.
|
|
9783
|
+
// Returns a function to detach the listener on teardown.
|
|
9784
|
+
onSessionLost(socket, onLost) {
|
|
9785
|
+
const listener = () => {
|
|
9786
|
+
if (!socket.recovered) onLost();
|
|
9787
|
+
};
|
|
9788
|
+
socket.on("connect", listener);
|
|
9789
|
+
return () => socket.off("connect", listener);
|
|
9790
|
+
}
|
|
9791
|
+
// The other half of onSessionLost, for the case it can't see: a connect attempt that FAILS rather than
|
|
9792
|
+
// succeeding — typically a reconnect whose handshake the gateway rejects (an expired token). No
|
|
9793
|
+
// 'connect' event ever fires then, so onSessionLost stays silent and the watcher parks forever on a
|
|
9794
|
+
// socket that will never deliver again. `socket.active` says whether socket.io intends to keep trying:
|
|
9795
|
+
// still true while it retries a transient transport failure (a blip Connection State Recovery papers
|
|
9796
|
+
// over — ignore it, or we'd fail runs that were about to resume), false once it has given up. Only
|
|
9797
|
+
// that second case is terminal, and it covers BOTH a server-side rejection (never retried) and
|
|
9798
|
+
// reconnectionAttempts running out, which is why the caller reports the error rather than a cause.
|
|
9799
|
+
// Returns a detach function.
|
|
9800
|
+
onConnectAbandoned(socket, onAbandoned) {
|
|
9801
|
+
const listener = (err) => {
|
|
9802
|
+
if (socket.active === false) onAbandoned(err);
|
|
9803
|
+
};
|
|
9804
|
+
socket.on("connect_error", listener);
|
|
9805
|
+
return () => socket.off("connect_error", listener);
|
|
9806
|
+
}
|
|
9807
|
+
// The third and last way this socket can go quiet for good: it disconnects and nothing is coming back
|
|
9808
|
+
// — `client.disconnect()` called while a watch is live ('io client disconnect'), or the gateway closing
|
|
9809
|
+
// us out ('io server disconnect'). Neither produces a 'connect' or a 'connect_error', so without this
|
|
9810
|
+
// the watcher parks forever on a socket that is simply gone. `active` splits the cases here too, as
|
|
9811
|
+
// socket.io documents: still true for a transport close or ping timeout, which it retries and CSR
|
|
9812
|
+
// papers over; false once the connection was closed for good. Returns a detach function.
|
|
9813
|
+
onDisconnected(socket, onGone) {
|
|
9814
|
+
const listener = (reason) => {
|
|
9815
|
+
if (socket.active === false) onGone(reason);
|
|
9816
|
+
};
|
|
9817
|
+
socket.on("disconnect", listener);
|
|
9818
|
+
return () => socket.off("disconnect", listener);
|
|
9819
|
+
}
|
|
9820
|
+
resolveSocket() {
|
|
9821
|
+
if (this.socketPromise) return this.socketPromise;
|
|
9822
|
+
const { socket, socketConnection } = this.config;
|
|
9823
|
+
if (!socket && !socketConnection) return Promise.resolve(void 0);
|
|
9824
|
+
const promise = (async () => {
|
|
9825
|
+
const resolved = socket ?? await this.createSocket(socketConnection);
|
|
9826
|
+
this.ownsSocket = !socket;
|
|
9827
|
+
try {
|
|
9828
|
+
await this.whenConnected(resolved);
|
|
9829
|
+
} catch (err) {
|
|
9830
|
+
if (!socket) {
|
|
9831
|
+
resolved.disconnect();
|
|
9832
|
+
this.ownsSocket = false;
|
|
9833
|
+
}
|
|
9834
|
+
throw err;
|
|
9835
|
+
}
|
|
9836
|
+
return resolved;
|
|
9837
|
+
})();
|
|
9838
|
+
promise.catch(() => {
|
|
9839
|
+
if (this.socketPromise === promise) this.socketPromise = void 0;
|
|
9840
|
+
});
|
|
9841
|
+
this.socketPromise = promise;
|
|
9842
|
+
return promise;
|
|
9843
|
+
}
|
|
9844
|
+
// Resolves once the socket's transport is up; rejects if the connection fails (so callers surface an
|
|
9845
|
+
// error instead of hanging). connect() is idempotent, so driving it is safe whether the socket is
|
|
9846
|
+
// already connecting (autoConnect) or was created with autoConnect:false.
|
|
9847
|
+
whenConnected(socket) {
|
|
9848
|
+
if (socket.connected) return Promise.resolve();
|
|
9849
|
+
return new Promise((resolve, reject) => {
|
|
9850
|
+
const cleanup = () => {
|
|
9851
|
+
socket.off("connect", onConnect);
|
|
9852
|
+
socket.off("connect_error", onError);
|
|
9853
|
+
};
|
|
9854
|
+
const onConnect = () => {
|
|
9855
|
+
cleanup();
|
|
9856
|
+
resolve();
|
|
9857
|
+
};
|
|
9858
|
+
const onError = (err) => {
|
|
9859
|
+
cleanup();
|
|
9860
|
+
reject(err instanceof Error ? err : new Error(`socket connection failed: ${String(err)}`));
|
|
9861
|
+
};
|
|
9862
|
+
socket.on("connect", onConnect);
|
|
9863
|
+
socket.on("connect_error", onError);
|
|
9864
|
+
socket.connect();
|
|
9865
|
+
});
|
|
9875
9866
|
}
|
|
9876
|
-
|
|
9877
|
-
|
|
9878
|
-
|
|
9879
|
-
|
|
9880
|
-
if (!url || String(file.name || "").startsWith("__")) return null;
|
|
9881
|
-
const preview = file.preview;
|
|
9882
|
-
return {
|
|
9883
|
-
uid: String(file.uid ?? ""),
|
|
9884
|
-
url,
|
|
9885
|
-
name: String(file.name || ""),
|
|
9886
|
-
type: inferMediaType(file),
|
|
9887
|
-
previewUrl: normalizeUrl(preview?.url),
|
|
9888
|
-
timestamp: Number(file.updatedAt ?? file.createdAt ?? 0)
|
|
9889
|
-
};
|
|
9890
|
-
}
|
|
9891
|
-
function toDetailedItem(file) {
|
|
9892
|
-
const base2 = toMediaItem(file);
|
|
9893
|
-
if (!base2) return null;
|
|
9894
|
-
const attrs = parseAttributes(file.attributes);
|
|
9895
|
-
let extras = {};
|
|
9896
|
-
if (attrs.textScript) {
|
|
9867
|
+
// socket.io-client is an optional peer dependency, imported on demand so non-socket consumers
|
|
9868
|
+
// never load it (and SSR never connects).
|
|
9869
|
+
async createSocket(conn) {
|
|
9870
|
+
let io;
|
|
9897
9871
|
try {
|
|
9898
|
-
|
|
9872
|
+
({ io } = await import('./esm-debug-3SQICTIF.js'));
|
|
9899
9873
|
} catch {
|
|
9874
|
+
throw new WorkflowsError({
|
|
9875
|
+
httpStatusCode: 400,
|
|
9876
|
+
reason: "invalid_state",
|
|
9877
|
+
message: 'socketConnection requires the optional peer dependency "socket.io-client" to be installed.'
|
|
9878
|
+
});
|
|
9900
9879
|
}
|
|
9880
|
+
return io(conn.url, {
|
|
9881
|
+
path: conn.path ?? "/socket-gateway",
|
|
9882
|
+
transports: conn.transports ?? ["websocket"],
|
|
9883
|
+
auth: this.buildAuth(conn)
|
|
9884
|
+
});
|
|
9901
9885
|
}
|
|
9902
|
-
|
|
9903
|
-
|
|
9904
|
-
|
|
9905
|
-
|
|
9906
|
-
|
|
9907
|
-
|
|
9908
|
-
|
|
9909
|
-
|
|
9910
|
-
|
|
9911
|
-
|
|
9912
|
-
|
|
9913
|
-
|
|
9914
|
-
|
|
9915
|
-
|
|
9916
|
-
|
|
9917
|
-
|
|
9918
|
-
|
|
9919
|
-
|
|
9920
|
-
|
|
9921
|
-
|
|
9922
|
-
|
|
9923
|
-
|
|
9924
|
-
|
|
9925
|
-
try {
|
|
9926
|
-
extras = JSON.parse(attrs.textScript);
|
|
9927
|
-
} catch {
|
|
9886
|
+
// The handshake auth payload, in socket.io's FUNCTION form — socket.io re-invokes it per (re)connect
|
|
9887
|
+
// attempt (Socket#onopen), which is the whole point: a plain object would be snapshotted once and
|
|
9888
|
+
// replayed on every reconnect, so it would go stale along with the token it captured.
|
|
9889
|
+
buildAuth(conn) {
|
|
9890
|
+
const { getToken } = conn;
|
|
9891
|
+
return (cb) => {
|
|
9892
|
+
void Promise.resolve().then(getToken).then((fresh) => cb({ token: bearer(fresh) })).catch((err) => {
|
|
9893
|
+
logger_default.error("workflows.socket - socketConnection.getToken failed; the handshake will be refused", err);
|
|
9894
|
+
cb({ token: "" });
|
|
9895
|
+
});
|
|
9896
|
+
};
|
|
9897
|
+
}
|
|
9898
|
+
// The gateway only accepts channels shaped `workflows:SEG:SEG` (SEG = letters, digits, _, - or /). We build
|
|
9899
|
+
// the channel from `name`/`taskId`, so validate it here to fail fast on a bad name instead of
|
|
9900
|
+
// silently never joining a room. Task names are slash-delimited paths (e.g. /v1/videos/text-to-video),
|
|
9901
|
+
// so `/` is allowed. (A channel that passes this but the gateway still refuses is left to hang — expected.)
|
|
9902
|
+
assertValidChannel(channel) {
|
|
9903
|
+
if (!/^workflows:[a-zA-Z0-9_/-]+:[a-zA-Z0-9_/-]+$/.test(channel)) {
|
|
9904
|
+
throw new WorkflowsError({
|
|
9905
|
+
httpStatusCode: 400,
|
|
9906
|
+
reason: "invalid_channel",
|
|
9907
|
+
message: `Invalid channel "${channel}" \u2014 name and taskId may only contain letters, digits, "_", "-" or "/".`
|
|
9908
|
+
});
|
|
9928
9909
|
}
|
|
9929
9910
|
}
|
|
9930
|
-
|
|
9931
|
-
|
|
9932
|
-
|
|
9933
|
-
|
|
9934
|
-
|
|
9935
|
-
|
|
9936
|
-
|
|
9937
|
-
|
|
9938
|
-
|
|
9939
|
-
|
|
9940
|
-
|
|
9941
|
-
|
|
9942
|
-
|
|
9943
|
-
|
|
9944
|
-
|
|
9945
|
-
|
|
9946
|
-
|
|
9947
|
-
|
|
9948
|
-
|
|
9949
|
-
|
|
9950
|
-
|
|
9951
|
-
|
|
9952
|
-
if (iterateModel) aiSDKPayload.iterateModel = iterateModel;
|
|
9953
|
-
const exploreImageId = asString(extras.exploreImageId);
|
|
9954
|
-
if (exploreImageId) aiSDKPayload.exploreImageId = exploreImageId;
|
|
9955
|
-
const app = attrs.tool ? LEGACY_TOOL_APP[attrs.tool] : void 0;
|
|
9956
|
-
return {
|
|
9957
|
-
appId: app?.appId,
|
|
9958
|
-
appType: app?.appType,
|
|
9959
|
-
model: attrs.model || void 0,
|
|
9960
|
-
aiSDKPayload,
|
|
9961
|
-
userReaction: parseReaction(attrs[USER_REACTION_ATTR])
|
|
9962
|
-
};
|
|
9963
|
-
}
|
|
9964
|
-
function parseGeneration(file) {
|
|
9965
|
-
const attrs = parseAttributes(file.attributes);
|
|
9966
|
-
if (!attrs.aiSDKPayload) {
|
|
9967
|
-
return adaptLegacyGeneration(attrs);
|
|
9968
|
-
}
|
|
9969
|
-
return {
|
|
9970
|
-
appId: attrs.appId || void 0,
|
|
9971
|
-
appType: attrs.appType === "native" || attrs.appType === "miniapp" ? attrs.appType : void 0,
|
|
9972
|
-
model: attrs.model || void 0,
|
|
9973
|
-
aiSDKPayload: parseJsonAttr(attrs.aiSDKPayload),
|
|
9974
|
-
userReaction: parseReaction(attrs[USER_REACTION_ATTR])
|
|
9975
|
-
};
|
|
9976
|
-
}
|
|
9977
|
-
function createDriveClient(f, apiUrl, rootFolderName) {
|
|
9978
|
-
let cachedRootUid = null;
|
|
9979
|
-
let rootPromise = null;
|
|
9980
|
-
const jsonPost = async (path, body) => f(`${apiUrl}${path}`, {
|
|
9981
|
-
method: "POST",
|
|
9982
|
-
headers: { "Content-Type": "application/json" },
|
|
9983
|
-
body: JSON.stringify(body)
|
|
9984
|
-
});
|
|
9985
|
-
const jsonGet = async (path) => f(`${apiUrl}${path}`);
|
|
9986
|
-
async function findFolderByPath(name) {
|
|
9987
|
-
try {
|
|
9988
|
-
const res = await jsonGet(`/cloud-storage/v1/me/files-by-path?path=${encodeURIComponent(name)}`);
|
|
9989
|
-
if (!res.ok) return null;
|
|
9990
|
-
const data = await res.json();
|
|
9991
|
-
if (data.status !== "success") return null;
|
|
9992
|
-
const response = data.response;
|
|
9993
|
-
const file = Array.isArray(response) ? response[0] : response;
|
|
9994
|
-
return file?.uid ?? null;
|
|
9995
|
-
} catch {
|
|
9996
|
-
return null;
|
|
9911
|
+
// Room membership. Every joiner emits its OWN `subscribe` (idempotent at the gateway); the ref-count
|
|
9912
|
+
// is used solely to emit `unsubscribe` ONCE, when the last watcher leaves, so one watcher's teardown
|
|
9913
|
+
// never drops a room a sibling still needs. Fire-and-forget: we validate the channel locally, so
|
|
9914
|
+
// there's no ack to act on.
|
|
9915
|
+
joinChannel(socket, channel) {
|
|
9916
|
+
let refs = this.channelRefs.get(socket);
|
|
9917
|
+
if (!refs) {
|
|
9918
|
+
refs = /* @__PURE__ */ new Map();
|
|
9919
|
+
this.channelRefs.set(socket, refs);
|
|
9920
|
+
}
|
|
9921
|
+
refs.set(channel, (refs.get(channel) ?? 0) + 1);
|
|
9922
|
+
socket.emit("subscribe", { channels: [channel] });
|
|
9923
|
+
}
|
|
9924
|
+
leaveChannel(socket, channel) {
|
|
9925
|
+
const refs = this.channelRefs.get(socket);
|
|
9926
|
+
if (!refs) return;
|
|
9927
|
+
const count = (refs.get(channel) ?? 0) - 1;
|
|
9928
|
+
if (count <= 0) {
|
|
9929
|
+
refs.delete(channel);
|
|
9930
|
+
socket.emit("unsubscribe", { channels: [channel] });
|
|
9931
|
+
} else {
|
|
9932
|
+
refs.set(channel, count);
|
|
9997
9933
|
}
|
|
9998
9934
|
}
|
|
9999
|
-
|
|
10000
|
-
|
|
10001
|
-
|
|
10002
|
-
|
|
10003
|
-
|
|
10004
|
-
|
|
10005
|
-
|
|
10006
|
-
|
|
10007
|
-
const match = files.find((f2) => String(f2.name || "").toLowerCase() === name.toLowerCase());
|
|
10008
|
-
return match?.uid ?? null;
|
|
10009
|
-
} catch {
|
|
10010
|
-
return null;
|
|
10011
|
-
}
|
|
9935
|
+
// The Error a task failure maps to: the gateway's own reason/message/statusCode when it sent them,
|
|
9936
|
+
// otherwise a generic 500 failure.
|
|
9937
|
+
failureError(failure) {
|
|
9938
|
+
return WorkflowsError.fromBody(failure, {
|
|
9939
|
+
httpStatusCode: 500,
|
|
9940
|
+
reason: "workflow_failed",
|
|
9941
|
+
message: "Workflow failed"
|
|
9942
|
+
});
|
|
10012
9943
|
}
|
|
10013
|
-
|
|
10014
|
-
|
|
10015
|
-
|
|
10016
|
-
|
|
10017
|
-
|
|
10018
|
-
|
|
10019
|
-
|
|
10020
|
-
|
|
10021
|
-
|
|
10022
|
-
|
|
10023
|
-
|
|
9944
|
+
};
|
|
9945
|
+
async function* decodeSSE(stream) {
|
|
9946
|
+
for await (const chunk of readSSE(stream)) {
|
|
9947
|
+
const lines = chunk.split("\n");
|
|
9948
|
+
const sseData = {};
|
|
9949
|
+
for (const line of lines) {
|
|
9950
|
+
if (line.startsWith("data:")) {
|
|
9951
|
+
const data = line.replace(/^data:\s*/, "");
|
|
9952
|
+
if (data === "[DONE]") {
|
|
9953
|
+
return;
|
|
9954
|
+
}
|
|
9955
|
+
try {
|
|
9956
|
+
sseData.data = JSON.parse(data);
|
|
9957
|
+
} catch (err) {
|
|
9958
|
+
logger_default.warn(
|
|
9959
|
+
`Failed to parse data JSON from OpenAI event stream: - ${data}, err=${JSON.stringify(err)}`
|
|
9960
|
+
);
|
|
9961
|
+
}
|
|
9962
|
+
}
|
|
10024
9963
|
}
|
|
9964
|
+
yield sseData;
|
|
10025
9965
|
}
|
|
10026
|
-
|
|
10027
|
-
|
|
10028
|
-
|
|
10029
|
-
|
|
10030
|
-
|
|
10031
|
-
|
|
10032
|
-
|
|
10033
|
-
|
|
9966
|
+
}
|
|
9967
|
+
async function* readSSE(stream) {
|
|
9968
|
+
const reader = stream.getReader();
|
|
9969
|
+
let buffer = new Uint8Array();
|
|
9970
|
+
const decoder = new TextDecoder("utf-8");
|
|
9971
|
+
try {
|
|
9972
|
+
while (true) {
|
|
9973
|
+
const { value, done } = await reader.read();
|
|
9974
|
+
if (done) break;
|
|
9975
|
+
const tmp = new Uint8Array(buffer.length + value.length);
|
|
9976
|
+
tmp.set(buffer);
|
|
9977
|
+
tmp.set(value, buffer.length);
|
|
9978
|
+
buffer = tmp;
|
|
9979
|
+
let index;
|
|
9980
|
+
while ((index = findDoubleNewlineIndex(buffer)) !== -1) {
|
|
9981
|
+
const slice = buffer.subarray(0, index);
|
|
9982
|
+
yield decoder.decode(slice);
|
|
9983
|
+
buffer = buffer.subarray(index);
|
|
9984
|
+
}
|
|
9985
|
+
}
|
|
9986
|
+
if (buffer.length > 0) {
|
|
9987
|
+
yield decoder.decode(buffer);
|
|
9988
|
+
}
|
|
9989
|
+
} finally {
|
|
9990
|
+
reader.releaseLock();
|
|
10034
9991
|
}
|
|
10035
|
-
|
|
10036
|
-
|
|
10037
|
-
|
|
10038
|
-
|
|
10039
|
-
|
|
10040
|
-
|
|
10041
|
-
|
|
10042
|
-
|
|
10043
|
-
|
|
10044
|
-
|
|
10045
|
-
|
|
10046
|
-
|
|
10047
|
-
|
|
9992
|
+
}
|
|
9993
|
+
function findDoubleNewlineIndex(buffer) {
|
|
9994
|
+
const newline = 10;
|
|
9995
|
+
const carriage = 13;
|
|
9996
|
+
for (let i = 0; i < buffer.length - 1; i++) {
|
|
9997
|
+
if (buffer[i] === newline && buffer[i + 1] === newline) {
|
|
9998
|
+
return i + 2;
|
|
9999
|
+
}
|
|
10000
|
+
if (buffer[i] === carriage && buffer[i + 1] === carriage) {
|
|
10001
|
+
return i + 2;
|
|
10002
|
+
}
|
|
10003
|
+
if (buffer[i] === carriage && buffer[i + 1] === newline && i + 3 < buffer.length && buffer[i + 2] === carriage && buffer[i + 3] === newline) {
|
|
10004
|
+
return i + 4;
|
|
10048
10005
|
}
|
|
10049
|
-
return rootPromise;
|
|
10050
10006
|
}
|
|
10051
|
-
|
|
10052
|
-
|
|
10053
|
-
|
|
10054
|
-
|
|
10055
|
-
|
|
10056
|
-
|
|
10057
|
-
|
|
10058
|
-
|
|
10059
|
-
|
|
10060
|
-
|
|
10007
|
+
return -1;
|
|
10008
|
+
}
|
|
10009
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
10010
|
+
var DEFAULT_POLLING_INTERVAL = 300;
|
|
10011
|
+
var DEFAULT_RETRIES_COUNT = 1e3;
|
|
10012
|
+
var NETWORK_RETRIES_COUNT = 10;
|
|
10013
|
+
var MAX_POLLING_BACKOFF = 5e3;
|
|
10014
|
+
var WorkflowsClient = class {
|
|
10015
|
+
constructor(options) {
|
|
10016
|
+
this.defaultHeaders = {
|
|
10017
|
+
Accept: "application/json",
|
|
10018
|
+
"Content-Type": "application/json"
|
|
10019
|
+
};
|
|
10020
|
+
this.terminalStatuses = [
|
|
10021
|
+
"COMPLETED",
|
|
10022
|
+
"FAILED"
|
|
10023
|
+
/* FAILED */
|
|
10024
|
+
];
|
|
10025
|
+
this.clientOptions = options || {};
|
|
10026
|
+
this.clientOptions.baseUrl = this.clientOptions.baseUrl || "https://api.picsart.com/";
|
|
10027
|
+
if (!this.clientOptions.baseUrl.endsWith("/")) this.clientOptions.baseUrl += "/";
|
|
10028
|
+
if (this.clientOptions.apiKey) {
|
|
10029
|
+
this.clientOptions.apiKey = this.clientOptions.apiKey.replace("Bearer ", "");
|
|
10030
|
+
}
|
|
10031
|
+
if (this.clientOptions.identityToken) {
|
|
10032
|
+
this.clientOptions.identityToken = this.clientOptions.identityToken.replace("Bearer ", "");
|
|
10033
|
+
}
|
|
10034
|
+
this.workflowsApiBaseUrl = `${this.clientOptions.baseUrl}workflows`;
|
|
10035
|
+
this.sockets = new WorkflowsSocket({
|
|
10036
|
+
socket: this.clientOptions.socket,
|
|
10037
|
+
socketConnection: this.clientOptions.socketConnection && {
|
|
10038
|
+
...this.clientOptions.socketConnection,
|
|
10039
|
+
url: this.clientOptions.socketConnection.url || this.clientOptions.baseUrl
|
|
10040
|
+
}
|
|
10041
|
+
});
|
|
10042
|
+
if (this.clientOptions.socket || this.clientOptions.socketConnection) {
|
|
10043
|
+
void Promise.resolve().then(() => this.sockets.connect()).catch(() => void 0);
|
|
10061
10044
|
}
|
|
10062
10045
|
}
|
|
10063
|
-
|
|
10046
|
+
/**
|
|
10047
|
+
* Runs a workflow end-to-end and resolves with its result.
|
|
10048
|
+
*
|
|
10049
|
+
* A workflow that has an entry in `WorkflowTypes` (from `@picsart/workflows-types`) is
|
|
10050
|
+
* type-checked against it: `params` must match the workflow's input and the result comes back
|
|
10051
|
+
* typed, with no type argument to pass. Every other workflow is left unconstrained — declare
|
|
10052
|
+
* the result yourself with `run<MyResult>(name, params)`.
|
|
10053
|
+
*
|
|
10054
|
+
* The execution mode is taken from remote settings when available, otherwise from
|
|
10055
|
+
* `executionOptions.mode`, defaulting to async (submit + polling). Supported modes:
|
|
10056
|
+
* sync (single HTTP call), stream (SSE, requires `onEvent`), socket (result pushed
|
|
10057
|
+
* over the socket), and async (submit + polling).
|
|
10058
|
+
*
|
|
10059
|
+
* @typeParam R - Shape of the result, for a workflow that has no `WorkflowTypes` entry.
|
|
10060
|
+
* Passing it explicitly also opts a mapped workflow out of its types.
|
|
10061
|
+
* @param name - Workflow name.
|
|
10062
|
+
* @param params - Workflow input, typed per the workflow definition when there is one.
|
|
10063
|
+
* @param executionOptions - Mode, callbacks (`onAccepted`, `onProgress`, `onPartialResult`,
|
|
10064
|
+
* `onEvent`), polling tuning, headers, and abort signal.
|
|
10065
|
+
* @returns The workflow result and usage info.
|
|
10066
|
+
* @throws {WorkflowsError} On a failed request (`httpStatusCode` carries the HTTP status),
|
|
10067
|
+
* invalid arguments, or an unexpected failure.
|
|
10068
|
+
*/
|
|
10069
|
+
async run(name, params2, executionOptions) {
|
|
10064
10070
|
try {
|
|
10065
|
-
const
|
|
10066
|
-
|
|
10067
|
-
|
|
10068
|
-
|
|
10069
|
-
|
|
10070
|
-
|
|
10071
|
-
|
|
10072
|
-
|
|
10073
|
-
|
|
10074
|
-
|
|
10075
|
-
|
|
10076
|
-
|
|
10077
|
-
|
|
10078
|
-
|
|
10079
|
-
|
|
10071
|
+
const remoteSettings = await this.getApiSettings(
|
|
10072
|
+
name,
|
|
10073
|
+
executionOptions?.remoteSettingName
|
|
10074
|
+
);
|
|
10075
|
+
const executionMode = remoteSettings.executionMode || executionOptions?.mode || "ASYNC";
|
|
10076
|
+
if (executionMode === "SYNC") {
|
|
10077
|
+
return this.executeTaskSync(name, params2, executionOptions);
|
|
10078
|
+
}
|
|
10079
|
+
if (executionMode === "STREAM") {
|
|
10080
|
+
return this.executeTaskStream(name, params2, executionOptions);
|
|
10081
|
+
}
|
|
10082
|
+
if (executionMode === "SOCKET") {
|
|
10083
|
+
const submit = async () => {
|
|
10084
|
+
try {
|
|
10085
|
+
const id = await this.postTask(name, params2, executionOptions);
|
|
10086
|
+
await executionOptions?.onAccepted?.(id);
|
|
10087
|
+
return id;
|
|
10088
|
+
} catch (err) {
|
|
10089
|
+
throw this.wrapError(name, err);
|
|
10090
|
+
}
|
|
10091
|
+
};
|
|
10092
|
+
return this.sockets.runTask(
|
|
10093
|
+
name,
|
|
10094
|
+
submit,
|
|
10095
|
+
executionOptions,
|
|
10096
|
+
(taskId2) => this.disableNotification(taskId2, { headers: executionOptions?.headers })
|
|
10097
|
+
);
|
|
10098
|
+
}
|
|
10099
|
+
const taskId = await this.postTask(name, params2, executionOptions);
|
|
10100
|
+
await executionOptions?.onAccepted?.(taskId);
|
|
10101
|
+
return this.runPolling(name, taskId, executionOptions);
|
|
10102
|
+
} catch (err) {
|
|
10103
|
+
throw this.wrapError(name, err);
|
|
10080
10104
|
}
|
|
10081
10105
|
}
|
|
10082
|
-
|
|
10106
|
+
/**
|
|
10107
|
+
* Submits a task WITHOUT waiting for its result — the standalone counterpart of {@link run}.
|
|
10108
|
+
* Consume the result later with {@link runPolling} or {@link subscribe} (`{ name, taskId }`).
|
|
10109
|
+
*
|
|
10110
|
+
* Only the submission-related execution options apply here (`headers`, `notificationConfig`,
|
|
10111
|
+
* `remoteSettingName`); result-consumption options (mode, callbacks, polling) belong to the consumer.
|
|
10112
|
+
*
|
|
10113
|
+
* @param name - Workflow name.
|
|
10114
|
+
* @param params - Workflow input parameters.
|
|
10115
|
+
* @param executionOptions - Submission-related options only.
|
|
10116
|
+
* @returns The taskId of the submitted task.
|
|
10117
|
+
* @throws {WorkflowsError} On a failed request (`httpStatusCode` carries the HTTP status)
|
|
10118
|
+
* or an unexpected failure.
|
|
10119
|
+
*/
|
|
10120
|
+
async submit(name, params2, executionOptions) {
|
|
10083
10121
|
try {
|
|
10084
|
-
|
|
10085
|
-
|
|
10086
|
-
|
|
10087
|
-
const file = data.response;
|
|
10088
|
-
return file && typeof file === "object" && !Array.isArray(file) ? file : null;
|
|
10089
|
-
} catch {
|
|
10090
|
-
return null;
|
|
10122
|
+
return await this.postTask(name, params2, executionOptions);
|
|
10123
|
+
} catch (err) {
|
|
10124
|
+
throw this.wrapError(name, err);
|
|
10091
10125
|
}
|
|
10092
10126
|
}
|
|
10093
|
-
|
|
10127
|
+
/**
|
|
10128
|
+
* Fetches the options a workflow offers for the given input — what the adapter resolves for THIS
|
|
10129
|
+
* caller (subscription tier, country, the `x-config-id` CMS card), which is why it is read at call
|
|
10130
|
+
* time rather than described by the workflow's types.
|
|
10131
|
+
*
|
|
10132
|
+
* @param name - Workflow name, including the version when the workflow has one (`pipelineName/v1`).
|
|
10133
|
+
* @param params - Workflow input to resolve the options for; defaults to `{}` for the common case
|
|
10134
|
+
* of asking before anything is chosen.
|
|
10135
|
+
* @param requestOptions - `remoteSettingName` to resolve the `x-config-id` under a name other
|
|
10136
|
+
* than the workflow's own.
|
|
10137
|
+
* @returns The options payload — the envelope's `response`, unwrapped.
|
|
10138
|
+
* @throws {WorkflowsError} On a failed request; `httpStatusCode` carries the HTTP status.
|
|
10139
|
+
*/
|
|
10140
|
+
async options(name, params2 = {}, requestOptions) {
|
|
10094
10141
|
try {
|
|
10095
|
-
const
|
|
10096
|
-
|
|
10097
|
-
|
|
10098
|
-
|
|
10142
|
+
const remoteSettings = await this.getApiSettings(name, requestOptions?.remoteSettingName);
|
|
10143
|
+
const response = await this._fetch(`${this.workflowsApiBaseUrl}/${name}/options`, {
|
|
10144
|
+
method: "POST",
|
|
10145
|
+
headers: this.requestHeaders(remoteSettings.configId),
|
|
10146
|
+
body: JSON.stringify({ params: params2 })
|
|
10099
10147
|
});
|
|
10100
|
-
|
|
10101
|
-
|
|
10102
|
-
|
|
10148
|
+
const json = await this.toSuccessResponse(response);
|
|
10149
|
+
return json.response;
|
|
10150
|
+
} catch (err) {
|
|
10151
|
+
throw this.wrapError(name, err);
|
|
10103
10152
|
}
|
|
10104
10153
|
}
|
|
10105
|
-
|
|
10106
|
-
|
|
10107
|
-
|
|
10108
|
-
|
|
10109
|
-
|
|
10110
|
-
|
|
10111
|
-
|
|
10112
|
-
|
|
10113
|
-
|
|
10114
|
-
|
|
10115
|
-
|
|
10116
|
-
|
|
10117
|
-
|
|
10118
|
-
|
|
10119
|
-
|
|
10120
|
-
|
|
10121
|
-
|
|
10122
|
-
|
|
10123
|
-
|
|
10124
|
-
|
|
10125
|
-
|
|
10126
|
-
|
|
10127
|
-
|
|
10128
|
-
|
|
10129
|
-
|
|
10130
|
-
|
|
10131
|
-
|
|
10132
|
-
|
|
10133
|
-
|
|
10134
|
-
|
|
10135
|
-
|
|
10136
|
-
|
|
10137
|
-
|
|
10138
|
-
|
|
10139
|
-
|
|
10140
|
-
|
|
10141
|
-
|
|
10142
|
-
|
|
10143
|
-
|
|
10144
|
-
|
|
10145
|
-
|
|
10146
|
-
|
|
10147
|
-
|
|
10148
|
-
|
|
10149
|
-
|
|
10150
|
-
|
|
10151
|
-
|
|
10152
|
-
const [rootLevel, subfolders] = await Promise.all([
|
|
10153
|
-
fetchFolders(),
|
|
10154
|
-
rootUid ? fetchFolders(rootUid) : Promise.resolve([])
|
|
10155
|
-
]);
|
|
10156
|
-
const lowerName = name.toLowerCase();
|
|
10157
|
-
return [...rootLevel, ...subfolders].find((f2) => f2.name.toLowerCase() === lowerName) ?? null;
|
|
10158
|
-
},
|
|
10159
|
-
/**
|
|
10160
|
-
* List media items. When no folder is given, lists across all folders (flattened).
|
|
10161
|
-
* Optionally filter by media type (sent to backend, not client-side).
|
|
10162
|
-
*/
|
|
10163
|
-
async list(options) {
|
|
10164
|
-
const folderUid = options?.folder?.uid ?? void 0;
|
|
10165
|
-
const files = await fetchMedia({ folderUid, type: options?.type });
|
|
10166
|
-
const items = [];
|
|
10167
|
-
for (const file of files) {
|
|
10168
|
-
const item = toMediaItem(file);
|
|
10169
|
-
if (item) items.push(item);
|
|
10170
|
-
}
|
|
10171
|
-
return items;
|
|
10172
|
-
},
|
|
10173
|
-
/**
|
|
10174
|
-
* List media items with full generation metadata (model, prompt, params, etc.).
|
|
10175
|
-
* Same options as list() — folder and type filter.
|
|
10176
|
-
*/
|
|
10177
|
-
async listDetailed(options) {
|
|
10178
|
-
const folderUid = options?.folder?.uid ?? void 0;
|
|
10179
|
-
const files = await fetchMedia({ folderUid, type: options?.type });
|
|
10180
|
-
const items = [];
|
|
10181
|
-
for (const file of files) {
|
|
10182
|
-
const item = toDetailedItem(file);
|
|
10183
|
-
if (item) items.push(item);
|
|
10184
|
-
}
|
|
10185
|
-
return items;
|
|
10186
|
-
},
|
|
10187
|
-
async getGeneration(fileUid) {
|
|
10188
|
-
const file = await fetchFileByUid(fileUid);
|
|
10189
|
-
return file ? parseGeneration(file) : null;
|
|
10190
|
-
},
|
|
10191
|
-
/** Save a file to Drive. Returns save result or null on failure. */
|
|
10192
|
-
async save(params2, folder) {
|
|
10193
|
-
const targetUid = folder?.uid ?? await ensureRootFolder();
|
|
10194
|
-
if (!targetUid) return null;
|
|
10195
|
-
const targetFolder = folder ?? { name: rootFolderName, uid: targetUid };
|
|
10196
|
-
const body = {
|
|
10197
|
-
name: params2.name,
|
|
10198
|
-
sourceUrl: params2.url,
|
|
10199
|
-
parentFolderUid: targetUid,
|
|
10200
|
-
content: {
|
|
10201
|
-
type: "STANDALONE",
|
|
10202
|
-
resourceType: params2.resourceType,
|
|
10203
|
-
sourcePlatform: "WEB"
|
|
10204
|
-
},
|
|
10205
|
-
preview: {
|
|
10206
|
-
url: params2.previewUrl || params2.url,
|
|
10207
|
-
width: 1024,
|
|
10208
|
-
height: 1024
|
|
10209
|
-
},
|
|
10210
|
-
attributes: Object.entries(params2.attributes ?? {}).map(([property, value]) => ({
|
|
10211
|
-
property,
|
|
10212
|
-
value
|
|
10213
|
-
}))
|
|
10214
|
-
};
|
|
10154
|
+
async postTask(taskName, command, executionOptions) {
|
|
10155
|
+
const remoteSettings = await this.getApiSettings(taskName, executionOptions?.remoteSettingName);
|
|
10156
|
+
const response = await this._fetch(`${this.workflowsApiBaseUrl}/${taskName}/submit`, {
|
|
10157
|
+
method: "POST",
|
|
10158
|
+
headers: this.requestHeaders(remoteSettings.configId, executionOptions?.headers),
|
|
10159
|
+
body: JSON.stringify({
|
|
10160
|
+
params: command,
|
|
10161
|
+
notification: executionOptions?.notificationConfig
|
|
10162
|
+
})
|
|
10163
|
+
});
|
|
10164
|
+
const json = await this.toSuccessResponse(response);
|
|
10165
|
+
return json.response.id;
|
|
10166
|
+
}
|
|
10167
|
+
/**
|
|
10168
|
+
* Polls an already-submitted task until it reaches a terminal status (COMPLETED/FAILED)
|
|
10169
|
+
* and resolves with its result. Progress and partial-result callbacks from
|
|
10170
|
+
* `executionOptions` are invoked on each update.
|
|
10171
|
+
*
|
|
10172
|
+
* A poll that never reaches the server (dropped wifi, DNS failure, a reset connection) does not
|
|
10173
|
+
* end the run — the task keeps going server-side, so polling backs off and retries, giving up
|
|
10174
|
+
* only once the drops outlast the retry budget. Anything the server did answer, and any abort,
|
|
10175
|
+
* still fails immediately.
|
|
10176
|
+
*
|
|
10177
|
+
* @typeParam R - Shape of the workflow result.
|
|
10178
|
+
* @param taskName - Workflow name.
|
|
10179
|
+
* @param taskId - Task id returned by {@link submit} (or `onAccepted`).
|
|
10180
|
+
* @param executionOptions - `pollingInterval` (default 300ms), `retriesCount` (default 1000),
|
|
10181
|
+
* callbacks and abort signal.
|
|
10182
|
+
* @returns The workflow result and usage info.
|
|
10183
|
+
* @throws {WorkflowsError} With `httpStatusCode` 408 when the retry budget is exhausted
|
|
10184
|
+
* before the task completes, or the connection error when the connection never came back.
|
|
10185
|
+
*/
|
|
10186
|
+
async runPolling(taskName, taskId, executionOptions) {
|
|
10187
|
+
const pollingInterval = executionOptions?.pollingInterval || DEFAULT_POLLING_INTERVAL;
|
|
10188
|
+
let retriesCounter = executionOptions?.retriesCount || DEFAULT_RETRIES_COUNT;
|
|
10189
|
+
let pollingResponse;
|
|
10190
|
+
let networkFailures = 0;
|
|
10191
|
+
let lastNetworkError;
|
|
10192
|
+
const progressAware = new WorkflowResultUpdateAware(
|
|
10193
|
+
executionOptions?.onProgress,
|
|
10194
|
+
executionOptions?.onPartialResult,
|
|
10195
|
+
executionOptions?.onEvent
|
|
10196
|
+
);
|
|
10197
|
+
do {
|
|
10198
|
+
if (executionOptions?.abortSignal?.aborted) throw new DOMException("Aborted", "AbortError");
|
|
10199
|
+
await sleep(this.pollingDelay(pollingInterval, networkFailures));
|
|
10200
|
+
retriesCounter--;
|
|
10215
10201
|
try {
|
|
10216
|
-
|
|
10217
|
-
|
|
10218
|
-
|
|
10219
|
-
|
|
10220
|
-
|
|
10221
|
-
|
|
10222
|
-
|
|
10223
|
-
|
|
10224
|
-
|
|
10225
|
-
|
|
10226
|
-
|
|
10227
|
-
|
|
10228
|
-
const data = await res.json();
|
|
10229
|
-
const file = data.response;
|
|
10230
|
-
const uid = file?.uid;
|
|
10231
|
-
if (!uid) return null;
|
|
10232
|
-
return { uid, folder: targetFolder };
|
|
10233
|
-
} catch {
|
|
10234
|
-
return null;
|
|
10202
|
+
pollingResponse = await this.getResult(taskName, taskId);
|
|
10203
|
+
networkFailures = 0;
|
|
10204
|
+
lastNetworkError = void 0;
|
|
10205
|
+
} catch (err) {
|
|
10206
|
+
if (!this.isConnectionError(err)) throw err;
|
|
10207
|
+
if (++networkFailures > NETWORK_RETRIES_COUNT) throw this.connectionError(err);
|
|
10208
|
+
lastNetworkError = err;
|
|
10209
|
+
logger_default.warn(
|
|
10210
|
+
`workflows.runPolling - poll ${networkFailures}/${NETWORK_RETRIES_COUNT} of ${taskName}/${taskId} did not reach the server, retrying`,
|
|
10211
|
+
err
|
|
10212
|
+
);
|
|
10213
|
+
continue;
|
|
10235
10214
|
}
|
|
10236
|
-
|
|
10237
|
-
|
|
10238
|
-
|
|
10239
|
-
|
|
10240
|
-
|
|
10241
|
-
|
|
10242
|
-
|
|
10243
|
-
|
|
10244
|
-
|
|
10245
|
-
model: modelId,
|
|
10246
|
-
prompt: prompt || "",
|
|
10247
|
-
service: modelName
|
|
10248
|
-
}
|
|
10249
|
-
};
|
|
10250
|
-
},
|
|
10251
|
-
async addReaction(fileUid, reaction) {
|
|
10252
|
-
return setReaction(fileUid, reaction);
|
|
10253
|
-
},
|
|
10254
|
-
async removeReaction(fileUid) {
|
|
10255
|
-
return setReaction(fileUid, null);
|
|
10215
|
+
await progressAware.onUpdate(pollingResponse.response);
|
|
10216
|
+
} while (retriesCounter > 0 && !this.isTerminal(pollingResponse));
|
|
10217
|
+
if (lastNetworkError) throw this.connectionError(lastNetworkError);
|
|
10218
|
+
if (!this.isTerminal(pollingResponse) || !pollingResponse?.response.result) {
|
|
10219
|
+
throw new WorkflowsError({
|
|
10220
|
+
httpStatusCode: 408,
|
|
10221
|
+
reason: "client_timeout",
|
|
10222
|
+
message: "Polling timeout reached. Consider increasing polling interval or retries count from execution options."
|
|
10223
|
+
});
|
|
10256
10224
|
}
|
|
10257
|
-
|
|
10258
|
-
}
|
|
10259
|
-
|
|
10260
|
-
// ../../node_modules/@picsart/workflows-client/dist/index.mjs
|
|
10261
|
-
var logger_default = {
|
|
10262
|
-
error: (...args) => {
|
|
10263
|
-
console.error(...args);
|
|
10264
|
-
},
|
|
10265
|
-
warn: (...args) => {
|
|
10266
|
-
console.debug(...args);
|
|
10267
|
-
},
|
|
10268
|
-
info: (...args) => {
|
|
10269
|
-
console.info(...args);
|
|
10270
|
-
},
|
|
10271
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
10272
|
-
debug: (...args) => {
|
|
10273
|
-
console.debug(...args);
|
|
10225
|
+
return pollingResponse.response;
|
|
10274
10226
|
}
|
|
10275
|
-
|
|
10276
|
-
|
|
10277
|
-
|
|
10278
|
-
|
|
10279
|
-
|
|
10280
|
-
|
|
10281
|
-
})
|
|
10282
|
-
var WorkflowsServerError = class extends Error {
|
|
10283
|
-
constructor(message) {
|
|
10284
|
-
super(`WorkflowsServerError: ${message}`);
|
|
10285
|
-
this.name = this.constructor.name;
|
|
10227
|
+
// Raw transport rejections (a TypeError from fetch) become the client's own error on the way out,
|
|
10228
|
+
// keeping what fetch said but labelling it for callers. No httpStatusCode: the server never answered.
|
|
10229
|
+
connectionError(error) {
|
|
10230
|
+
return new WorkflowsError({
|
|
10231
|
+
reason: "connection_error",
|
|
10232
|
+
message: error?.message || "The request did not reach the server"
|
|
10233
|
+
});
|
|
10286
10234
|
}
|
|
10287
|
-
|
|
10288
|
-
|
|
10289
|
-
constructor(action, status, responseBody) {
|
|
10290
|
-
super(
|
|
10291
|
-
`WorkflowsClientError: [${status}] ${action} failed: ${responseBody.reason} - ${responseBody.message}`
|
|
10292
|
-
);
|
|
10293
|
-
this.name = this.constructor.name;
|
|
10294
|
-
this.status = status;
|
|
10295
|
-
this.details = responseBody;
|
|
10235
|
+
isTerminal(response) {
|
|
10236
|
+
return !!response && this.terminalStatuses.includes(response.response.status);
|
|
10296
10237
|
}
|
|
10297
|
-
|
|
10298
|
-
|
|
10299
|
-
|
|
10300
|
-
|
|
10301
|
-
|
|
10238
|
+
/**
|
|
10239
|
+
* Whether a failed poll never got an answer from the server — the connection dropped, DNS failed,
|
|
10240
|
+
* the request was reset. Classified by what the failure is NOT, so it holds in a browser
|
|
10241
|
+
* (`TypeError: Failed to fetch`) and in Node (`TypeError: fetch failed`) alike: anything the
|
|
10242
|
+
* server answered carries an `httpStatusCode`, and an abort is the caller's own doing.
|
|
10243
|
+
*/
|
|
10244
|
+
isConnectionError(error) {
|
|
10245
|
+
const name = error?.name;
|
|
10246
|
+
if (name === "AbortError" || name === "TimeoutError") return false;
|
|
10247
|
+
if (error instanceof WorkflowsError) return error.httpStatusCode === void 0;
|
|
10248
|
+
return true;
|
|
10302
10249
|
}
|
|
10303
|
-
|
|
10304
|
-
|
|
10305
|
-
|
|
10306
|
-
|
|
10307
|
-
|
|
10308
|
-
this.onEventFn = onEventFn;
|
|
10250
|
+
// Back off while the connection is down instead of hammering a dead radio — never below the
|
|
10251
|
+
// caller's own interval, never above MAX_POLLING_BACKOFF.
|
|
10252
|
+
pollingDelay(interval, consecutiveFailures) {
|
|
10253
|
+
if (consecutiveFailures === 0) return interval;
|
|
10254
|
+
return Math.min(interval * 2 ** consecutiveFailures, Math.max(interval, MAX_POLLING_BACKOFF));
|
|
10309
10255
|
}
|
|
10310
|
-
|
|
10311
|
-
|
|
10312
|
-
|
|
10313
|
-
|
|
10314
|
-
|
|
10315
|
-
|
|
10316
|
-
|
|
10317
|
-
|
|
10318
|
-
|
|
10319
|
-
|
|
10320
|
-
|
|
10256
|
+
/**
|
|
10257
|
+
* Fetches the CURRENT state of an already-submitted task with a single request — no polling, no
|
|
10258
|
+
* waiting. Returns the task as it stands, so read `status` (and `progress`) to know what you got:
|
|
10259
|
+
* `result` may still be empty or partial while the task is not COMPLETED. Use {@link runPolling}
|
|
10260
|
+
* or {@link subscribe} to wait for a terminal status instead.
|
|
10261
|
+
*
|
|
10262
|
+
* @typeParam R - Shape of the workflow result.
|
|
10263
|
+
* @param taskName - Workflow name.
|
|
10264
|
+
* @param taskId - Task id returned by {@link submit} (or `onAccepted`).
|
|
10265
|
+
* @returns The task record as it stands at the moment of the call.
|
|
10266
|
+
* @throws {WorkflowsError} On a failed request (`httpStatusCode` carries the HTTP status)
|
|
10267
|
+
* or an unexpected failure.
|
|
10268
|
+
*/
|
|
10269
|
+
async result(taskName, taskId) {
|
|
10270
|
+
try {
|
|
10271
|
+
const response = await this.getResult(taskName, taskId);
|
|
10272
|
+
return response.response;
|
|
10273
|
+
} catch (err) {
|
|
10274
|
+
throw this.wrapError(taskName, err);
|
|
10321
10275
|
}
|
|
10322
10276
|
}
|
|
10323
|
-
|
|
10324
|
-
|
|
10325
|
-
|
|
10326
|
-
|
|
10327
|
-
|
|
10328
|
-
|
|
10329
|
-
|
|
10330
|
-
|
|
10331
|
-
|
|
10332
|
-
|
|
10333
|
-
|
|
10334
|
-
|
|
10335
|
-
|
|
10336
|
-
|
|
10337
|
-
|
|
10338
|
-
|
|
10339
|
-
|
|
10340
|
-
};
|
|
10341
|
-
async function* decodeSSE(stream) {
|
|
10342
|
-
for await (const chunk of readSSE(stream)) {
|
|
10343
|
-
const lines = chunk.split("\n");
|
|
10344
|
-
const sseData = {};
|
|
10345
|
-
for (const line of lines) {
|
|
10346
|
-
if (line.startsWith("data:")) {
|
|
10347
|
-
const data = line.replace(/^data:\s*/, "");
|
|
10348
|
-
if (data === "[DONE]") {
|
|
10349
|
-
return;
|
|
10350
|
-
}
|
|
10351
|
-
try {
|
|
10352
|
-
sseData.data = JSON.parse(data);
|
|
10353
|
-
} catch (err) {
|
|
10354
|
-
logger_default.warn(
|
|
10355
|
-
`Failed to parse data JSON from OpenAI event stream: - ${data}, err=${JSON.stringify(err)}`
|
|
10356
|
-
);
|
|
10357
|
-
}
|
|
10358
|
-
}
|
|
10359
|
-
}
|
|
10360
|
-
yield sseData;
|
|
10361
|
-
}
|
|
10362
|
-
}
|
|
10363
|
-
async function* readSSE(stream) {
|
|
10364
|
-
const reader = stream.getReader();
|
|
10365
|
-
let buffer = new Uint8Array();
|
|
10366
|
-
const decoder = new TextDecoder("utf-8");
|
|
10367
|
-
try {
|
|
10368
|
-
while (true) {
|
|
10369
|
-
const { value, done } = await reader.read();
|
|
10370
|
-
if (done) break;
|
|
10371
|
-
const tmp = new Uint8Array(buffer.length + value.length);
|
|
10372
|
-
tmp.set(buffer);
|
|
10373
|
-
tmp.set(value, buffer.length);
|
|
10374
|
-
buffer = tmp;
|
|
10375
|
-
let index;
|
|
10376
|
-
while ((index = findDoubleNewlineIndex(buffer)) !== -1) {
|
|
10377
|
-
const slice = buffer.subarray(0, index);
|
|
10378
|
-
yield decoder.decode(slice);
|
|
10379
|
-
buffer = buffer.subarray(index);
|
|
10380
|
-
}
|
|
10381
|
-
}
|
|
10382
|
-
if (buffer.length > 0) {
|
|
10383
|
-
yield decoder.decode(buffer);
|
|
10384
|
-
}
|
|
10385
|
-
} finally {
|
|
10386
|
-
reader.releaseLock();
|
|
10387
|
-
}
|
|
10388
|
-
}
|
|
10389
|
-
function findDoubleNewlineIndex(buffer) {
|
|
10390
|
-
const newline = 10;
|
|
10391
|
-
const carriage = 13;
|
|
10392
|
-
for (let i = 0; i < buffer.length - 1; i++) {
|
|
10393
|
-
if (buffer[i] === newline && buffer[i + 1] === newline) {
|
|
10394
|
-
return i + 2;
|
|
10395
|
-
}
|
|
10396
|
-
if (buffer[i] === carriage && buffer[i + 1] === carriage) {
|
|
10397
|
-
return i + 2;
|
|
10398
|
-
}
|
|
10399
|
-
if (buffer[i] === carriage && buffer[i + 1] === newline && i + 3 < buffer.length && buffer[i + 2] === carriage && buffer[i + 3] === newline) {
|
|
10400
|
-
return i + 4;
|
|
10277
|
+
/**
|
|
10278
|
+
* Watches already-submitted work LIVE over the socket (no re-submit), as an async iterable
|
|
10279
|
+
* of the raw `StreamSocketMessage` the gateway pushes — iterate with `for await` and switch
|
|
10280
|
+
* on `msg.type`. With a `taskId` it watches that task (ending after its COMPLETED/FAILED);
|
|
10281
|
+
* without it, it watches EVERY task of the workflow until stopped. A lost session throws out
|
|
10282
|
+
* of the loop; `break` (or an AbortSignal in options) stops watching.
|
|
10283
|
+
*
|
|
10284
|
+
* @param options - Subscription target: `name` (required), optional `taskId` and abort signal.
|
|
10285
|
+
* @returns Async iterable of socket messages for the subscribed workflow/task.
|
|
10286
|
+
* @throws {WorkflowsError} If `options.name` is missing.
|
|
10287
|
+
*/
|
|
10288
|
+
subscribe(options) {
|
|
10289
|
+
if (!options.name) {
|
|
10290
|
+
throw new WorkflowsError({
|
|
10291
|
+
httpStatusCode: 400,
|
|
10292
|
+
reason: "INVALID_ARGUMENTS",
|
|
10293
|
+
message: "subscribe() requires `name`."
|
|
10294
|
+
});
|
|
10401
10295
|
}
|
|
10296
|
+
return this.sockets.subscribe(options);
|
|
10402
10297
|
}
|
|
10403
|
-
|
|
10404
|
-
|
|
10405
|
-
|
|
10406
|
-
|
|
10407
|
-
|
|
10408
|
-
|
|
10409
|
-
constructor(options) {
|
|
10410
|
-
this.defaultHeaders = {
|
|
10411
|
-
Accept: "application/json",
|
|
10412
|
-
"Content-Type": "application/json"
|
|
10413
|
-
};
|
|
10414
|
-
this.terminalStatuses = [
|
|
10415
|
-
"COMPLETED",
|
|
10416
|
-
"FAILED"
|
|
10417
|
-
/* FAILED */
|
|
10418
|
-
];
|
|
10419
|
-
this.options = options || {};
|
|
10420
|
-
this.options.baseUrl = this.options.baseUrl || "https://api.picsart.com/";
|
|
10421
|
-
if (!this.options.baseUrl.endsWith("/")) this.options.baseUrl += "/";
|
|
10422
|
-
if (this.options.apiKey) {
|
|
10423
|
-
this.options.apiKey = this.options.apiKey.replace("Bearer ", "");
|
|
10424
|
-
}
|
|
10425
|
-
if (this.options.identityToken) {
|
|
10426
|
-
this.options.identityToken = this.options.identityToken.replace("Bearer ", "");
|
|
10427
|
-
}
|
|
10428
|
-
this.workflowsApiBaseUrl = `${this.options.baseUrl}workflows`;
|
|
10298
|
+
/**
|
|
10299
|
+
* Closes the socket the client created from `socketConnection`. No-op for an injected
|
|
10300
|
+
* `socket` (the caller owns that one). Safe to call more than once.
|
|
10301
|
+
*/
|
|
10302
|
+
async disconnect() {
|
|
10303
|
+
return this.sockets.disconnect();
|
|
10429
10304
|
}
|
|
10430
|
-
|
|
10305
|
+
/**
|
|
10306
|
+
* Marks a task's notification as seen so it is no longer surfaced to the user.
|
|
10307
|
+
* Called automatically after socket-mode runs; call it manually when consuming
|
|
10308
|
+
* results yourself (e.g. after {@link submit} + {@link subscribe}).
|
|
10309
|
+
*
|
|
10310
|
+
* @param taskId - Task id whose notification should be dismissed.
|
|
10311
|
+
* @param options - Optional extra request headers.
|
|
10312
|
+
* @throws {WorkflowsError} On a failed request; `httpStatusCode` carries the HTTP status.
|
|
10313
|
+
*/
|
|
10314
|
+
async disableNotification(taskId, options) {
|
|
10431
10315
|
try {
|
|
10432
|
-
const
|
|
10433
|
-
|
|
10434
|
-
|
|
10435
|
-
);
|
|
10436
|
-
const executionMode = remoteSettings.executionMode || executionOptions?.mode || "ASYNC";
|
|
10437
|
-
if (executionMode === "SYNC") {
|
|
10438
|
-
return this.executeTaskSync(name, params2, executionOptions);
|
|
10439
|
-
}
|
|
10440
|
-
if (executionMode === "STREAM") {
|
|
10441
|
-
return this.executeTaskStream(name, params2, executionOptions);
|
|
10442
|
-
}
|
|
10443
|
-
const taskId = await this.postTask(name, params2, executionOptions);
|
|
10444
|
-
await executionOptions?.onAccepted?.(taskId);
|
|
10445
|
-
return this.runPolling(name, taskId, executionOptions);
|
|
10316
|
+
const url = `${this.clientOptions.baseUrl}workflow-notifications/${taskId}/seen-status`;
|
|
10317
|
+
const response = await this._fetch(url, { method: "PATCH", headers: options?.headers });
|
|
10318
|
+
await this.throwIfError(response);
|
|
10446
10319
|
} catch (err) {
|
|
10447
|
-
throw this.wrapError(
|
|
10448
|
-
}
|
|
10449
|
-
}
|
|
10450
|
-
async runTypeSafe(name, params2, executionOptions) {
|
|
10451
|
-
return this.run(name, params2, executionOptions);
|
|
10452
|
-
}
|
|
10453
|
-
async postTask(taskName, command, executionOptions) {
|
|
10454
|
-
const remoteSettings = await this.getApiSettings(taskName, executionOptions?.remoteSettingName);
|
|
10455
|
-
const response = await this._fetch(`${this.workflowsApiBaseUrl}/${taskName}/submit`, {
|
|
10456
|
-
method: "POST",
|
|
10457
|
-
headers: {
|
|
10458
|
-
"x-config-id": remoteSettings.configId || "",
|
|
10459
|
-
...executionOptions?.headers
|
|
10460
|
-
},
|
|
10461
|
-
body: JSON.stringify({
|
|
10462
|
-
params: command,
|
|
10463
|
-
notification: executionOptions?.notificationConfig
|
|
10464
|
-
})
|
|
10465
|
-
});
|
|
10466
|
-
const json = await this.toSuccessResponse(response, taskName);
|
|
10467
|
-
return json.response.id;
|
|
10468
|
-
}
|
|
10469
|
-
async runPolling(taskName, taskId, executionOptions) {
|
|
10470
|
-
let retriesCounter = executionOptions?.retriesCount || DEFAULT_RETRIES_COUNT;
|
|
10471
|
-
let pollingResponse;
|
|
10472
|
-
const progressAware = new WorkflowResultUpdateAware(
|
|
10473
|
-
executionOptions?.onProgress,
|
|
10474
|
-
executionOptions?.onPartialResult,
|
|
10475
|
-
executionOptions?.onEvent
|
|
10476
|
-
);
|
|
10477
|
-
do {
|
|
10478
|
-
await sleep(executionOptions?.pollingInterval || DEFAULT_POLLING_INTERVAL);
|
|
10479
|
-
pollingResponse = await this.getResult(taskName, taskId, executionOptions?.abortSignal, executionOptions?.headers);
|
|
10480
|
-
await progressAware.onUpdate(pollingResponse.response);
|
|
10481
|
-
retriesCounter--;
|
|
10482
|
-
} while (retriesCounter > 0 && !this.terminalStatuses.includes(pollingResponse.response.status));
|
|
10483
|
-
if (!this.terminalStatuses.includes(pollingResponse.response.status) || !pollingResponse.response.result) {
|
|
10484
|
-
throw new WorkflowsClientError(taskName, 408, {
|
|
10485
|
-
status: "error",
|
|
10486
|
-
reason: "client_timeout",
|
|
10487
|
-
message: "Polling timeout reached. Consider increasing polling interval or retries count from execution options. "
|
|
10488
|
-
});
|
|
10320
|
+
throw this.wrapError("disableNotification", err);
|
|
10489
10321
|
}
|
|
10490
|
-
return {
|
|
10491
|
-
result: pollingResponse.response.result,
|
|
10492
|
-
usage: pollingResponse.response.usage
|
|
10493
|
-
};
|
|
10494
10322
|
}
|
|
10495
10323
|
async executeTaskSync(taskName, command, executionOptions) {
|
|
10496
10324
|
const remoteSettings = await this.getApiSettings(taskName, executionOptions?.remoteSettingName);
|
|
@@ -10499,58 +10327,45 @@ var WorkflowsClient = class {
|
|
|
10499
10327
|
{
|
|
10500
10328
|
signal: executionOptions?.abortSignal,
|
|
10501
10329
|
method: "POST",
|
|
10502
|
-
headers:
|
|
10503
|
-
"x-config-id": remoteSettings.configId || "",
|
|
10504
|
-
...executionOptions?.headers
|
|
10505
|
-
},
|
|
10330
|
+
headers: this.requestHeaders(remoteSettings.configId, executionOptions?.headers),
|
|
10506
10331
|
body: JSON.stringify({ params: command })
|
|
10507
10332
|
}
|
|
10508
10333
|
);
|
|
10509
|
-
const successResponse = await this.toSuccessResponse(
|
|
10510
|
-
|
|
10511
|
-
taskName
|
|
10512
|
-
);
|
|
10513
|
-
return {
|
|
10514
|
-
result: successResponse.response.result,
|
|
10515
|
-
usage: successResponse.response.usage
|
|
10516
|
-
};
|
|
10334
|
+
const successResponse = await this.toSuccessResponse(response);
|
|
10335
|
+
return successResponse.response;
|
|
10517
10336
|
}
|
|
10518
|
-
async getResult(taskName, taskId
|
|
10519
|
-
const response = await this._fetch(
|
|
10520
|
-
|
|
10521
|
-
|
|
10522
|
-
|
|
10523
|
-
headers: {
|
|
10524
|
-
...headers
|
|
10525
|
-
},
|
|
10526
|
-
signal: abortSignal
|
|
10527
|
-
}
|
|
10528
|
-
);
|
|
10529
|
-
return this.toSuccessResponse(response, taskName);
|
|
10337
|
+
async getResult(taskName, taskId) {
|
|
10338
|
+
const response = await this._fetch(`${this.workflowsApiBaseUrl}/${taskName}/${taskId}/result`, {
|
|
10339
|
+
method: "GET"
|
|
10340
|
+
});
|
|
10341
|
+
return this.toSuccessResponse(response);
|
|
10530
10342
|
}
|
|
10531
10343
|
async executeTaskStream(taskName, command, executionOptions) {
|
|
10532
10344
|
const remoteSettings = await this.getApiSettings(taskName, executionOptions?.remoteSettingName);
|
|
10533
10345
|
const onEvent = executionOptions?.onEvent;
|
|
10534
|
-
const actionName = `Executing ${taskName} task in stream mode`;
|
|
10535
10346
|
if (!onEvent) {
|
|
10536
|
-
throw new
|
|
10537
|
-
|
|
10538
|
-
|
|
10539
|
-
|
|
10347
|
+
throw new WorkflowsError({
|
|
10348
|
+
httpStatusCode: 400,
|
|
10349
|
+
reason: "INVALID_ARGUMENTS",
|
|
10350
|
+
message: "onEvent is required for streaming"
|
|
10540
10351
|
});
|
|
10541
10352
|
}
|
|
10353
|
+
const streamHeaders = this.requestHeaders(remoteSettings.configId, executionOptions?.headers);
|
|
10354
|
+
streamHeaders.set("Accept", "text/event-stream");
|
|
10542
10355
|
const response = await this._fetch(`${this.workflowsApiBaseUrl}/${taskName}/stream`, {
|
|
10543
10356
|
signal: executionOptions?.abortSignal,
|
|
10544
10357
|
method: "POST",
|
|
10545
|
-
headers:
|
|
10546
|
-
"x-config-id": remoteSettings.configId || "",
|
|
10547
|
-
...executionOptions?.headers,
|
|
10548
|
-
Accept: "text/event-stream"
|
|
10549
|
-
},
|
|
10358
|
+
headers: streamHeaders,
|
|
10550
10359
|
body: JSON.stringify({ params: command })
|
|
10551
10360
|
});
|
|
10552
|
-
await this.throwIfError(response
|
|
10553
|
-
if (!response.body)
|
|
10361
|
+
await this.throwIfError(response);
|
|
10362
|
+
if (!response.body) {
|
|
10363
|
+
throw new WorkflowsError({
|
|
10364
|
+
httpStatusCode: 500,
|
|
10365
|
+
reason: "invalid_response",
|
|
10366
|
+
message: "No response body"
|
|
10367
|
+
});
|
|
10368
|
+
}
|
|
10554
10369
|
let completedEvent = {};
|
|
10555
10370
|
for await (const event of decodeSSE(response.body)) {
|
|
10556
10371
|
if (executionOptions?.abortSignal?.aborted) break;
|
|
@@ -10568,15 +10383,11 @@ var WorkflowsClient = class {
|
|
|
10568
10383
|
});
|
|
10569
10384
|
}
|
|
10570
10385
|
if (data.type === "task.failed") {
|
|
10571
|
-
|
|
10572
|
-
|
|
10573
|
-
|
|
10574
|
-
|
|
10575
|
-
}
|
|
10576
|
-
if (statusCode >= 400) {
|
|
10577
|
-
throw new WorkflowsClientError(actionName, statusCode, failedResult);
|
|
10578
|
-
}
|
|
10579
|
-
throw new WorkflowsUnknownError(failedResult.message || failedResult.reason);
|
|
10386
|
+
throw WorkflowsError.fromBody(data.result, {
|
|
10387
|
+
httpStatusCode: 500,
|
|
10388
|
+
reason: "workflow_failed",
|
|
10389
|
+
message: "Workflow failed"
|
|
10390
|
+
});
|
|
10580
10391
|
}
|
|
10581
10392
|
if (data.type === "task.completed") {
|
|
10582
10393
|
completedEvent = data;
|
|
@@ -10584,43 +10395,56 @@ var WorkflowsClient = class {
|
|
|
10584
10395
|
}
|
|
10585
10396
|
return {
|
|
10586
10397
|
result: completedEvent.result,
|
|
10587
|
-
usage: completedEvent.usage
|
|
10398
|
+
usage: completedEvent.usage,
|
|
10399
|
+
status: "COMPLETED"
|
|
10400
|
+
/* COMPLETED */
|
|
10401
|
+
// the loop only leaves the FAILED branch by throwing
|
|
10588
10402
|
};
|
|
10589
10403
|
}
|
|
10404
|
+
/**
|
|
10405
|
+
* Fetches the execution history of a workflow, paginated.
|
|
10406
|
+
*
|
|
10407
|
+
* @typeParam R - Shape of each execution's result in the history entries.
|
|
10408
|
+
* @param taskName - Workflow name to fetch history for.
|
|
10409
|
+
* @param offset - Pagination offset (default 0).
|
|
10410
|
+
* @param limit - Page size (default 10).
|
|
10411
|
+
* @param isGrouped - When true, fetches the grouped history endpoint.
|
|
10412
|
+
* @returns The history page for the workflow.
|
|
10413
|
+
* @throws {WorkflowsError} On a failed request (`httpStatusCode` carries the HTTP status)
|
|
10414
|
+
* or an unexpected failure.
|
|
10415
|
+
*/
|
|
10590
10416
|
async executionsHistory(taskName, offset = 0, limit = 10, isGrouped = false) {
|
|
10591
10417
|
try {
|
|
10592
10418
|
const grouped = isGrouped ? "/grouped" : "";
|
|
10593
|
-
const url = `${this.
|
|
10419
|
+
const url = `${this.clientOptions.baseUrl}workflows-history${grouped}?name=${taskName}&limit=${limit}&offset=${offset}`;
|
|
10594
10420
|
const res = await this._fetch(url);
|
|
10595
|
-
return this.toSuccessResponse(res
|
|
10421
|
+
return this.toSuccessResponse(res);
|
|
10596
10422
|
} catch (err) {
|
|
10597
10423
|
throw this.wrapError("requestHistory", err);
|
|
10598
10424
|
}
|
|
10599
10425
|
}
|
|
10600
|
-
async toSuccessResponse(response
|
|
10601
|
-
await this.throwIfError(response
|
|
10602
|
-
|
|
10603
|
-
|
|
10604
|
-
|
|
10605
|
-
|
|
10606
|
-
|
|
10607
|
-
|
|
10608
|
-
|
|
10609
|
-
message
|
|
10610
|
-
}
|
|
10611
|
-
message = "Non json response was returned from server";
|
|
10612
|
-
}
|
|
10613
|
-
throw new WorkflowsServerError(`[${response.status}] - ${actionName} failed with message: ${message}.`);
|
|
10614
|
-
}
|
|
10615
|
-
if (!response.ok) {
|
|
10616
|
-
throw new WorkflowsClientError(actionName, response.status, await response.json());
|
|
10426
|
+
async toSuccessResponse(response) {
|
|
10427
|
+
await this.throwIfError(response);
|
|
10428
|
+
const body = await response.text();
|
|
10429
|
+
try {
|
|
10430
|
+
return JSON.parse(body);
|
|
10431
|
+
} catch {
|
|
10432
|
+
throw new WorkflowsError({
|
|
10433
|
+
httpStatusCode: response.status,
|
|
10434
|
+
reason: "invalid_response",
|
|
10435
|
+
message: "Non json response was returned from server"
|
|
10436
|
+
});
|
|
10617
10437
|
}
|
|
10618
10438
|
}
|
|
10439
|
+
async throwIfError(response) {
|
|
10440
|
+
if (response.ok) return;
|
|
10441
|
+
throw await WorkflowsError.fromResponse(response, { reason: "request_failed" });
|
|
10442
|
+
}
|
|
10619
10443
|
async getApiSettings(name, remoteSettingName) {
|
|
10620
|
-
if (!this.
|
|
10444
|
+
if (!this.clientOptions.getRemoteSettings) return {};
|
|
10621
10445
|
const settingName = remoteSettingName || `${name.replace(/-/g, "_").toLowerCase()}_api`;
|
|
10622
10446
|
try {
|
|
10623
|
-
const apiSetting = await this.
|
|
10447
|
+
const apiSetting = await this.clientOptions.getRemoteSettings(
|
|
10624
10448
|
settingName,
|
|
10625
10449
|
"miniapp"
|
|
10626
10450
|
);
|
|
@@ -10637,30 +10461,35 @@ var WorkflowsClient = class {
|
|
|
10637
10461
|
}
|
|
10638
10462
|
}
|
|
10639
10463
|
wrapError(actionName, error) {
|
|
10640
|
-
if (error instanceof
|
|
10464
|
+
if (error instanceof WorkflowsError || error instanceof DOMException) {
|
|
10641
10465
|
return error;
|
|
10642
10466
|
}
|
|
10643
|
-
logger_default.error(`
|
|
10644
|
-
return
|
|
10645
|
-
|
|
10646
|
-
|
|
10467
|
+
logger_default.error(`WorkflowsError - ${actionName} failed`, error);
|
|
10468
|
+
return WorkflowsError.fromUnknown(error);
|
|
10469
|
+
}
|
|
10470
|
+
// Per-call headers plus the resolved config id, with the call's own value winning — again via
|
|
10471
|
+
// Headers, so every HeadersInit shape survives.
|
|
10472
|
+
requestHeaders(configId, headers) {
|
|
10473
|
+
const merged = new Headers(headers);
|
|
10474
|
+
if (!merged.has("x-config-id")) merged.set("x-config-id", configId || "");
|
|
10475
|
+
return merged;
|
|
10647
10476
|
}
|
|
10648
10477
|
buildRequestHeaders(initHeaders) {
|
|
10649
10478
|
const headers = new Headers(initHeaders);
|
|
10650
|
-
const optionHeaders = new Headers(
|
|
10651
|
-
|
|
10652
|
-
|
|
10653
|
-
}
|
|
10479
|
+
const optionHeaders = new Headers(this.defaultHeaders);
|
|
10480
|
+
for (const [key, value] of new Headers(this.clientOptions.headers).entries()) {
|
|
10481
|
+
optionHeaders.set(key, value);
|
|
10482
|
+
}
|
|
10654
10483
|
for (const [key, value] of optionHeaders.entries()) {
|
|
10655
10484
|
if (!headers.has(key)) {
|
|
10656
10485
|
headers.set(key, value);
|
|
10657
10486
|
}
|
|
10658
10487
|
}
|
|
10659
|
-
if (this.
|
|
10660
|
-
headers.set("Authorization", `Bearer ${this.
|
|
10488
|
+
if (this.clientOptions.apiKey) {
|
|
10489
|
+
headers.set("Authorization", `Bearer ${this.clientOptions.apiKey}`);
|
|
10661
10490
|
}
|
|
10662
|
-
if (this.
|
|
10663
|
-
headers.set("x-app-authorization", `Bearer ${this.
|
|
10491
|
+
if (this.clientOptions.identityToken) {
|
|
10492
|
+
headers.set("x-app-authorization", `Bearer ${this.clientOptions.identityToken}`);
|
|
10664
10493
|
}
|
|
10665
10494
|
return headers;
|
|
10666
10495
|
}
|
|
@@ -10670,830 +10499,1202 @@ var WorkflowsClient = class {
|
|
|
10670
10499
|
...init,
|
|
10671
10500
|
headers
|
|
10672
10501
|
};
|
|
10673
|
-
if (this.
|
|
10674
|
-
return this.
|
|
10502
|
+
if (this.clientOptions.fetch) {
|
|
10503
|
+
return this.clientOptions.fetch(input, {
|
|
10675
10504
|
...requestInit,
|
|
10676
10505
|
// return headers as a plain object for easier handling in custom fetch
|
|
10677
10506
|
headers: Object.fromEntries(headers.entries())
|
|
10678
10507
|
});
|
|
10679
10508
|
}
|
|
10680
10509
|
if (!headers.has("Authorization") && !headers.has("x-app-authorization")) {
|
|
10681
|
-
throw new
|
|
10510
|
+
throw new WorkflowsError({
|
|
10511
|
+
httpStatusCode: 400,
|
|
10512
|
+
reason: "invalid_state",
|
|
10513
|
+
message: "apiKey is not provided"
|
|
10514
|
+
});
|
|
10682
10515
|
}
|
|
10683
10516
|
return fetch(input, requestInit);
|
|
10684
10517
|
}
|
|
10685
10518
|
};
|
|
10686
10519
|
var WorkflowsClient_default = WorkflowsClient;
|
|
10687
10520
|
|
|
10688
|
-
// src/client/
|
|
10689
|
-
|
|
10690
|
-
|
|
10691
|
-
|
|
10692
|
-
|
|
10693
|
-
|
|
10694
|
-
|
|
10695
|
-
|
|
10696
|
-
|
|
10697
|
-
|
|
10698
|
-
|
|
10699
|
-
|
|
10700
|
-
|
|
10701
|
-
|
|
10702
|
-
|
|
10703
|
-
|
|
10704
|
-
|
|
10521
|
+
// src/client/workflows-error.ts
|
|
10522
|
+
var GENERIC_REASONS = /* @__PURE__ */ new Set(["request_failed", "unknown_error"]);
|
|
10523
|
+
function isWorkflowsError(err) {
|
|
10524
|
+
if (err instanceof WorkflowsError) return true;
|
|
10525
|
+
const e = err;
|
|
10526
|
+
return err instanceof Error && typeof e.reason === "string" && /WorkflowsError$/.test(e.name ?? "");
|
|
10527
|
+
}
|
|
10528
|
+
function toApiError(err, workflow, id) {
|
|
10529
|
+
if (err instanceof ApiError) return err;
|
|
10530
|
+
if (err instanceof DOMException && err.name === "AbortError") return err;
|
|
10531
|
+
const e = err;
|
|
10532
|
+
if (isWorkflowsError(err)) {
|
|
10533
|
+
if (e.reason === "client_timeout") {
|
|
10534
|
+
return new ApiError(`Timed out waiting for workflow ${workflow}${id ? `:${id}` : ""}`, {
|
|
10535
|
+
status: 408,
|
|
10536
|
+
code: "timeout"
|
|
10537
|
+
});
|
|
10705
10538
|
}
|
|
10706
|
-
|
|
10707
|
-
|
|
10708
|
-
|
|
10539
|
+
const status = e.httpStatusCode ?? 502;
|
|
10540
|
+
return new ApiError(e.message ?? "Request failed", {
|
|
10541
|
+
status,
|
|
10542
|
+
code: e.reason && !GENERIC_REASONS.has(e.reason) ? e.reason : codeForStatus(status)
|
|
10543
|
+
});
|
|
10544
|
+
}
|
|
10545
|
+
return new ApiError(err instanceof Error ? err.message : String(err), {
|
|
10546
|
+
status: 502,
|
|
10547
|
+
code: "generation_failed"
|
|
10548
|
+
});
|
|
10709
10549
|
}
|
|
10710
10550
|
|
|
10711
|
-
// src/client/
|
|
10712
|
-
var
|
|
10713
|
-
|
|
10714
|
-
|
|
10715
|
-
|
|
10716
|
-
|
|
10717
|
-
|
|
10718
|
-
|
|
10719
|
-
|
|
10720
|
-
|
|
10721
|
-
|
|
10722
|
-
|
|
10723
|
-
|
|
10724
|
-
|
|
10725
|
-
const settle = () => signal.removeEventListener("abort", onAbort);
|
|
10726
|
-
promise.then(
|
|
10727
|
-
(value) => {
|
|
10728
|
-
settle();
|
|
10729
|
-
resolve(value);
|
|
10730
|
-
},
|
|
10731
|
-
(err) => {
|
|
10732
|
-
settle();
|
|
10733
|
-
reject(err);
|
|
10551
|
+
// src/client/transport.ts
|
|
10552
|
+
var GATEWAY_HEADERS = {
|
|
10553
|
+
"platform": "api",
|
|
10554
|
+
"X-Touchpoint": "sdk"
|
|
10555
|
+
};
|
|
10556
|
+
function maybeFetch(config) {
|
|
10557
|
+
if (config.fetch) return config.fetch;
|
|
10558
|
+
if (config.apiKey) {
|
|
10559
|
+
const token = config.apiKey.replace(/^Bearer\s+/i, "");
|
|
10560
|
+
return (url, init) => {
|
|
10561
|
+
const headers = new Headers(init?.headers);
|
|
10562
|
+
headers.set("Authorization", `Bearer ${token}`);
|
|
10563
|
+
for (const [name, value] of Object.entries(GATEWAY_HEADERS)) {
|
|
10564
|
+
if (!headers.has(name)) headers.set(name, value);
|
|
10734
10565
|
}
|
|
10735
|
-
|
|
10736
|
-
|
|
10737
|
-
}
|
|
10738
|
-
function createCatalogs(transport, options) {
|
|
10739
|
-
const stores = /* @__PURE__ */ new Map();
|
|
10740
|
-
const inflight = /* @__PURE__ */ new Map();
|
|
10741
|
-
const keyOf2 = (s) => `${s.workflow} ${s.modelId ?? ""}`;
|
|
10742
|
-
async function fetchPage(workflow, query) {
|
|
10743
|
-
const payload = {};
|
|
10744
|
-
if (query.modelId) payload.modelId = query.modelId;
|
|
10745
|
-
if (query.cursor) payload.cursor = query.cursor;
|
|
10746
|
-
if (query.limit) payload.limit = query.limit;
|
|
10747
|
-
const raw = await transport.execute({ workflow, payload });
|
|
10748
|
-
const container = raw?.response ?? raw;
|
|
10749
|
-
if (raw?.status === "error" || container?.status === "FAILED") {
|
|
10750
|
-
const message = container?.message ?? container?.error ?? raw?.message;
|
|
10751
|
-
throw new Error(`${workflow} failed${message ? `: ${String(message)}` : ""}`);
|
|
10752
|
-
}
|
|
10753
|
-
const result = container?.result;
|
|
10754
|
-
if (!result || !Array.isArray(result.items)) {
|
|
10755
|
-
throw new Error(`${workflow} returned no catalog result`);
|
|
10756
|
-
}
|
|
10757
|
-
return { ...result, nextCursor: result.nextCursor ?? null };
|
|
10566
|
+
return globalThis.fetch(url, { ...init, headers });
|
|
10567
|
+
};
|
|
10758
10568
|
}
|
|
10759
|
-
|
|
10760
|
-
|
|
10761
|
-
|
|
10762
|
-
|
|
10763
|
-
|
|
10764
|
-
|
|
10765
|
-
|
|
10766
|
-
|
|
10767
|
-
|
|
10768
|
-
|
|
10769
|
-
|
|
10770
|
-
|
|
10771
|
-
|
|
10569
|
+
return null;
|
|
10570
|
+
}
|
|
10571
|
+
function createWorkflowsClient(apiUrl, authedFetch) {
|
|
10572
|
+
return new WorkflowsClient_default({
|
|
10573
|
+
baseUrl: apiUrl,
|
|
10574
|
+
// AuthenticatedFetch takes a string url; the client's fetch type accepts
|
|
10575
|
+
// URL/Request inputs too. Normalize without losing the Request's own url,
|
|
10576
|
+
// method, headers, or body (the client passes plain string urls today,
|
|
10577
|
+
// but the contract allows more).
|
|
10578
|
+
fetch: (input, init) => {
|
|
10579
|
+
if (input instanceof Request) {
|
|
10580
|
+
return authedFetch(input.url, init ?? {
|
|
10581
|
+
method: input.method,
|
|
10582
|
+
headers: input.headers,
|
|
10583
|
+
body: input.body,
|
|
10584
|
+
signal: input.signal
|
|
10585
|
+
});
|
|
10586
|
+
}
|
|
10587
|
+
return authedFetch(typeof input === "string" ? input : input.toString(), init);
|
|
10772
10588
|
}
|
|
10773
|
-
|
|
10774
|
-
}
|
|
10775
|
-
function accumulated(store) {
|
|
10776
|
-
const byId = /* @__PURE__ */ new Map();
|
|
10777
|
-
for (const page of store.pages.values()) {
|
|
10778
|
-
for (const item of page.items) byId.set(item.id, item);
|
|
10779
|
-
}
|
|
10780
|
-
return [...byId.values()];
|
|
10781
|
-
}
|
|
10782
|
-
async function loadPage(def, paramKey, source, options2) {
|
|
10783
|
-
const store = storeFor(source, options2?.forceRefresh);
|
|
10784
|
-
const cursorKey = options2?.cursor ?? "";
|
|
10785
|
-
const cached = store.pages.get(cursorKey);
|
|
10786
|
-
if (cached) return abortable(Promise.resolve(copyPage(cached)), options2?.signal);
|
|
10787
|
-
const inflightKey = `${keyOf2(source)} ${cursorKey}`;
|
|
10788
|
-
if (!options2?.forceRefresh) {
|
|
10789
|
-
const pending = inflight.get(inflightKey);
|
|
10790
|
-
if (pending) return abortable(pending.then(copyPage), options2?.signal);
|
|
10791
|
-
}
|
|
10792
|
-
const gen = store.gen;
|
|
10793
|
-
const run = fetchPage(source.workflow, {
|
|
10794
|
-
modelId: source.modelId,
|
|
10795
|
-
cursor: options2?.cursor,
|
|
10796
|
-
limit: options2?.limit ?? DEFAULT_LIMIT
|
|
10797
|
-
}).then((res) => {
|
|
10798
|
-
const page = { items: res.items, nextCursor: res.nextCursor };
|
|
10799
|
-
if (store.gen === gen) {
|
|
10800
|
-
store.pages.set(cursorKey, page);
|
|
10801
|
-
store.version = res.version;
|
|
10802
|
-
if (store.expiresAt === 0) {
|
|
10803
|
-
store.expiresAt = Date.now() + Math.max(MIN_TTL_SECONDS, res.ttlSeconds || 0) * 1e3;
|
|
10804
|
-
}
|
|
10805
|
-
installHydratedCatalog(source, paramKey, accumulated(store), def.provider, store.version);
|
|
10806
|
-
}
|
|
10807
|
-
return page;
|
|
10808
|
-
}).finally(() => {
|
|
10809
|
-
if (inflight.get(inflightKey) === run) inflight.delete(inflightKey);
|
|
10810
|
-
});
|
|
10811
|
-
inflight.set(inflightKey, run);
|
|
10812
|
-
return abortable(run.then(copyPage), options2?.signal);
|
|
10813
|
-
}
|
|
10814
|
-
function requireSource(def, key) {
|
|
10815
|
-
const d = def.paramConfig[key]?.descriptor;
|
|
10816
|
-
const source = d?.kind === "catalog" ? d.source : void 0;
|
|
10817
|
-
if (!source) {
|
|
10818
|
-
throw new Error(`Model "${def.id}" has no runtime catalog on param "${key}" \u2014 its options are static.`);
|
|
10819
|
-
}
|
|
10820
|
-
return source;
|
|
10821
|
-
}
|
|
10822
|
-
async function loadParam(model, key, options2) {
|
|
10823
|
-
const def = resolveModel(model);
|
|
10824
|
-
return loadPage(def, key, requireSource(def, key), options2);
|
|
10825
|
-
}
|
|
10826
|
-
const client = {
|
|
10827
|
-
voices: (model, options2) => loadParam(model, "voiceId", options2),
|
|
10828
|
-
avatars: (model, options2) => loadParam(model, "videoId", options2),
|
|
10829
|
-
templates: (model, options2) => loadParam(model, "templateId", options2)
|
|
10830
|
-
};
|
|
10831
|
-
if (options?.preload) {
|
|
10832
|
-
const seen = /* @__PURE__ */ new Set();
|
|
10833
|
-
for (const def of ALL_MODELS) {
|
|
10834
|
-
for (const [key, entry] of Object.entries(def.paramConfig)) {
|
|
10835
|
-
const d = entry.descriptor;
|
|
10836
|
-
const source = d.kind === "catalog" ? d.source : void 0;
|
|
10837
|
-
if (!source || seen.has(keyOf2(source))) continue;
|
|
10838
|
-
seen.add(keyOf2(source));
|
|
10839
|
-
void loadPage(def, key, source).catch(() => {
|
|
10840
|
-
});
|
|
10841
|
-
}
|
|
10842
|
-
}
|
|
10843
|
-
}
|
|
10844
|
-
return client;
|
|
10845
|
-
}
|
|
10846
|
-
|
|
10847
|
-
// src/client/index.ts
|
|
10848
|
-
var MODE_POLL_DEFAULTS = {
|
|
10849
|
-
video: { intervalMs: 2e3, maxAttempts: 1800 },
|
|
10850
|
-
// 2s × 1800 = 1 hour
|
|
10851
|
-
image: { intervalMs: 1e3, maxAttempts: 1200 },
|
|
10852
|
-
// 1s × 1200 = 20 min
|
|
10853
|
-
audio: { intervalMs: 1e3, maxAttempts: 1200 },
|
|
10854
|
-
// 1s × 1200 = 20 min
|
|
10855
|
-
text: { intervalMs: 1e3, maxAttempts: 1200 }
|
|
10856
|
-
// 1s × 1200 = 20 min
|
|
10857
|
-
};
|
|
10858
|
-
function resolvePollOptions(model, overrides) {
|
|
10859
|
-
const resolved = { ...MODE_POLL_DEFAULTS[model.mode], ...model.pollOptions };
|
|
10860
|
-
if (overrides?.intervalMs !== void 0) resolved.intervalMs = overrides.intervalMs;
|
|
10861
|
-
if (overrides?.maxAttempts !== void 0) resolved.maxAttempts = overrides.maxAttempts;
|
|
10862
|
-
if (overrides?.signal !== void 0) resolved.signal = overrides.signal;
|
|
10863
|
-
return resolved;
|
|
10589
|
+
});
|
|
10864
10590
|
}
|
|
10865
|
-
function
|
|
10866
|
-
const
|
|
10867
|
-
|
|
10868
|
-
|
|
10869
|
-
|
|
10870
|
-
|
|
10871
|
-
const catalogs = createCatalogs(transport, isConfig ? config.catalogs : void 0);
|
|
10872
|
-
const inputsTransformationConfig = isConfig ? config.inputsTransformation : void 0;
|
|
10873
|
-
const driveConfig = isConfig ? config.drive : void 0;
|
|
10874
|
-
const driveClient = isConfig && driveConfig ? createDriveClient(resolveFetch(config), config.apiUrl, driveConfig.folder) : null;
|
|
10875
|
-
async function executeModel(model, workflow, payload, options) {
|
|
10876
|
-
const signal = options?.signal;
|
|
10877
|
-
if (model.syncExecute || !supportsSubmit) {
|
|
10878
|
-
const syncResponse = await client.run(
|
|
10879
|
-
{ workflow, payload, signal },
|
|
10880
|
-
{ mode: "sync" }
|
|
10881
|
-
);
|
|
10882
|
-
return toCompletedStatus(
|
|
10883
|
-
syncResponse.handle,
|
|
10884
|
-
extractSyncResult(syncResponse.raw),
|
|
10885
|
-
syncResponse.raw,
|
|
10886
|
-
syncResponse.usage
|
|
10887
|
-
);
|
|
10888
|
-
}
|
|
10889
|
-
return client.run({ workflow, payload, signal }, resolvePollOptions(model, options));
|
|
10890
|
-
}
|
|
10891
|
-
function buildDrivePayloadOptions(model, params2, options) {
|
|
10892
|
-
const explicit = options?.drive;
|
|
10893
|
-
if (!driveConfig && !explicit) return void 0;
|
|
10894
|
-
const attributes = buildGenerationAttributes({
|
|
10895
|
-
modelId: model.id,
|
|
10896
|
-
params: params2,
|
|
10897
|
-
app: options?.app
|
|
10898
|
-
});
|
|
10899
|
-
const folderPath = options?.folder?.name ?? driveConfig?.folder;
|
|
10900
|
-
return {
|
|
10901
|
-
name: explicit?.name ?? buildFilename(params2.prompt, model.mode),
|
|
10902
|
-
// SDK-assembled attributes are the baseline; explicit attributes win per-key.
|
|
10903
|
-
attributes: { ...attributes, ...explicit?.attributes ?? {} },
|
|
10904
|
-
folder: explicit?.folder ?? (folderPath ? { path: folderPath } : void 0)
|
|
10905
|
-
};
|
|
10906
|
-
}
|
|
10907
|
-
function injectPayloadOptions(payload, drive, inputsTransformation) {
|
|
10908
|
-
const record = payload;
|
|
10909
|
-
const existing = record.options ?? {};
|
|
10910
|
-
return {
|
|
10911
|
-
...record,
|
|
10912
|
-
options: {
|
|
10913
|
-
...existing,
|
|
10914
|
-
inputs_transformation: {
|
|
10915
|
-
downscale_oversized_images: inputsTransformation?.downscaleOversizedImages ?? inputsTransformationConfig?.downscaleOversizedImages ?? false
|
|
10916
|
-
},
|
|
10917
|
-
...drive ? { drive } : {}
|
|
10918
|
-
}
|
|
10919
|
-
};
|
|
10920
|
-
}
|
|
10591
|
+
function buildTransport(wc) {
|
|
10592
|
+
const asResult = (res) => ({
|
|
10593
|
+
result: res.result,
|
|
10594
|
+
usage: res.usage,
|
|
10595
|
+
raw: res.result
|
|
10596
|
+
});
|
|
10921
10597
|
return {
|
|
10922
|
-
|
|
10923
|
-
|
|
10924
|
-
|
|
10925
|
-
|
|
10926
|
-
|
|
10927
|
-
* submits the job, polls to completion, and returns the result URL.
|
|
10928
|
-
* If drive options are provided (or DriveConfig is set), the backend
|
|
10929
|
-
* saves the result to Picsart Drive.
|
|
10930
|
-
*/
|
|
10931
|
-
async generate(model, params2, options) {
|
|
10932
|
-
const resolved = resolveModel(model);
|
|
10933
|
-
if (resolved.mode === "text") {
|
|
10934
|
-
throw new ApiError(`${resolved.name} is a text model \u2014 use generateText() instead.`, {
|
|
10935
|
-
status: 400,
|
|
10936
|
-
code: "wrong_model_mode"
|
|
10598
|
+
async execute(request) {
|
|
10599
|
+
try {
|
|
10600
|
+
const res = await wc.run(request.workflow, request.payload, {
|
|
10601
|
+
mode: ExecutionMode.SYNC,
|
|
10602
|
+
abortSignal: request.signal
|
|
10937
10603
|
});
|
|
10604
|
+
return asResult(res);
|
|
10605
|
+
} catch (err) {
|
|
10606
|
+
throw toApiError(err, request.workflow);
|
|
10938
10607
|
}
|
|
10939
|
-
const { workflow, payload, contract } = prepareRequest(resolved, params2);
|
|
10940
|
-
const drive = buildDrivePayloadOptions(resolved, params2, options);
|
|
10941
|
-
const finalPayload = injectPayloadOptions(payload, drive, options?.inputsTransformation);
|
|
10942
|
-
const completed = await executeModel(resolved, workflow, finalPayload, options);
|
|
10943
|
-
return parseResult(completed, resolved, contract);
|
|
10944
10608
|
},
|
|
10945
|
-
|
|
10946
|
-
|
|
10947
|
-
|
|
10948
|
-
|
|
10949
|
-
|
|
10950
|
-
|
|
10951
|
-
|
|
10952
|
-
|
|
10953
|
-
|
|
10954
|
-
const resolved = resolveModel(model);
|
|
10955
|
-
if (resolved.mode !== "text") {
|
|
10956
|
-
throw new ApiError(`${resolved.name} is not a text model \u2014 use generate() instead.`, {
|
|
10957
|
-
status: 400,
|
|
10958
|
-
code: "wrong_model_mode"
|
|
10959
|
-
});
|
|
10609
|
+
async submit(request) {
|
|
10610
|
+
try {
|
|
10611
|
+
const id = await wc.submit(request.workflow, request.payload, { abortSignal: request.signal });
|
|
10612
|
+
if (!id) {
|
|
10613
|
+
throw new ApiError("No task id in response", { status: 502, code: "invalid_response" });
|
|
10614
|
+
}
|
|
10615
|
+
return id;
|
|
10616
|
+
} catch (err) {
|
|
10617
|
+
throw toApiError(err, request.workflow);
|
|
10960
10618
|
}
|
|
10961
|
-
const { workflow, payload } = prepareRequest(resolved, params2);
|
|
10962
|
-
const completed = await executeModel(resolved, workflow, payload, options);
|
|
10963
|
-
return parseTextResult(completed, resolved);
|
|
10964
|
-
},
|
|
10965
|
-
/** @deprecated Use `getCredits()` instead. */
|
|
10966
|
-
async estimate(model, params2) {
|
|
10967
|
-
if (!transport.options) return null;
|
|
10968
|
-
const resolved = resolveModel(model);
|
|
10969
|
-
const { workflow, payload } = prepareRequest(resolved, params2);
|
|
10970
|
-
return await transport.options(workflow, payload) ?? null;
|
|
10971
|
-
},
|
|
10972
|
-
/**
|
|
10973
|
-
* Get exact credit cost for a model with specific parameters.
|
|
10974
|
-
* Calls the backend /options endpoint for real-time pricing.
|
|
10975
|
-
* Returns null if pricing is unavailable.
|
|
10976
|
-
*/
|
|
10977
|
-
async getCredits(model, params2) {
|
|
10978
|
-
if (!transport.options) return null;
|
|
10979
|
-
const resolved = resolveModel(model);
|
|
10980
|
-
const { workflow, payload } = prepareRequest(resolved, params2);
|
|
10981
|
-
return await transport.options(workflow, payload) ?? null;
|
|
10982
|
-
},
|
|
10983
|
-
/** Build the vendor-specific payload for a model without submitting. */
|
|
10984
|
-
buildPayload(model, params2) {
|
|
10985
|
-
const resolved = resolveModel(model);
|
|
10986
|
-
const { payload } = prepareRequest(resolved, params2);
|
|
10987
|
-
return payload;
|
|
10988
10619
|
},
|
|
10989
|
-
|
|
10990
|
-
|
|
10991
|
-
|
|
10992
|
-
|
|
10993
|
-
|
|
10994
|
-
|
|
10995
|
-
|
|
10996
|
-
|
|
10620
|
+
async poll(handle, options) {
|
|
10621
|
+
try {
|
|
10622
|
+
const res = await wc.runPolling(handle.workflow, handle.id, {
|
|
10623
|
+
pollingInterval: options?.intervalMs,
|
|
10624
|
+
retriesCount: options?.maxAttempts,
|
|
10625
|
+
abortSignal: options?.signal,
|
|
10626
|
+
onProgress: options?.onProgress
|
|
10627
|
+
});
|
|
10628
|
+
return asResult(res);
|
|
10629
|
+
} catch (err) {
|
|
10630
|
+
throw toApiError(err, handle.workflow, handle.id);
|
|
10631
|
+
}
|
|
10997
10632
|
},
|
|
10998
|
-
/** Check the current status of a submitted job. */
|
|
10999
10633
|
async status(handle, signal) {
|
|
11000
|
-
|
|
11001
|
-
|
|
11002
|
-
/** Poll a submitted job until it completes and return the parsed result. */
|
|
11003
|
-
async result(handle, model, options) {
|
|
11004
|
-
const resolved = resolveModel(model);
|
|
11005
|
-
const contract = getModelContract(resolved.id);
|
|
11006
|
-
const completed = await client.result(handle, resolvePollOptions(resolved, options));
|
|
11007
|
-
return parseResult(completed, resolved, contract);
|
|
11008
|
-
},
|
|
11009
|
-
/**
|
|
11010
|
-
* Subscribe to live status updates for a submitted job.
|
|
11011
|
-
*
|
|
11012
|
-
* ```ts
|
|
11013
|
-
* const handle = await ai.submit(Models.Flux2Pro, { prompt: 'a cat' });
|
|
11014
|
-
* for await (const update of ai.subscribe(handle)) {
|
|
11015
|
-
* console.log(update.status, update.progress);
|
|
11016
|
-
* }
|
|
11017
|
-
* ```
|
|
11018
|
-
*/
|
|
11019
|
-
subscribe(handle, options) {
|
|
11020
|
-
return client.subscribe(handle, options);
|
|
11021
|
-
},
|
|
11022
|
-
// ── Raw workflow access ──────────────────────────────────────────
|
|
11023
|
-
/**
|
|
11024
|
-
* Run a raw workflow (not tied to a model).
|
|
11025
|
-
* @deprecated Use `apis.run()` instead.
|
|
11026
|
-
*/
|
|
11027
|
-
async runWorkflow(workflow, payload, options) {
|
|
11028
|
-
const done = await client.run(
|
|
11029
|
-
{ workflow, payload, signal: options?.signal },
|
|
11030
|
-
options
|
|
11031
|
-
);
|
|
11032
|
-
if (done.status === "FAILED" || done.status === "CANCELED") {
|
|
11033
|
-
throw new ApiError(done.error ?? `${workflow} failed with status ${done.status}`, {
|
|
11034
|
-
status: done.statusCode ?? (done.status === "CANCELED" ? 499 : 502),
|
|
11035
|
-
code: done.reason ?? (done.status === "CANCELED" ? "canceled" : "generation_failed")
|
|
11036
|
-
});
|
|
10634
|
+
if (signal?.aborted) {
|
|
10635
|
+
throw new ApiError("Operation aborted", { status: 499, code: "aborted" });
|
|
11037
10636
|
}
|
|
11038
|
-
|
|
11039
|
-
|
|
11040
|
-
|
|
11041
|
-
|
|
11042
|
-
});
|
|
10637
|
+
try {
|
|
10638
|
+
return asResult(await wc.result(handle.workflow, handle.id));
|
|
10639
|
+
} catch (err) {
|
|
10640
|
+
throw toApiError(err, handle.workflow, handle.id);
|
|
11043
10641
|
}
|
|
11044
|
-
return done.result;
|
|
11045
10642
|
},
|
|
11046
|
-
|
|
11047
|
-
|
|
11048
|
-
|
|
11049
|
-
|
|
11050
|
-
|
|
11051
|
-
|
|
11052
|
-
|
|
11053
|
-
|
|
11054
|
-
drive: driveClient ?? void 0
|
|
10643
|
+
async options(workflow, payload) {
|
|
10644
|
+
try {
|
|
10645
|
+
const res = await wc.options(workflow, payload);
|
|
10646
|
+
return typeof res?.credits === "number" ? res.credits : null;
|
|
10647
|
+
} catch {
|
|
10648
|
+
return null;
|
|
10649
|
+
}
|
|
10650
|
+
}
|
|
11055
10651
|
};
|
|
11056
10652
|
}
|
|
11057
10653
|
|
|
11058
|
-
// src/
|
|
11059
|
-
function
|
|
11060
|
-
|
|
11061
|
-
return {
|
|
10654
|
+
// src/client/prepare.ts
|
|
10655
|
+
function resolvePayloadBuild(model, ctx) {
|
|
10656
|
+
const hasImages = Array.isArray(ctx.imageUrls) && ctx.imageUrls.length > 0 || !!ctx.startFrame || !!ctx.endFrame;
|
|
10657
|
+
return {
|
|
10658
|
+
hasImages,
|
|
10659
|
+
workflow: hasImages && model.editWorkflow ? model.editWorkflow : model.workflow,
|
|
10660
|
+
buildPayload: hasImages && model.buildEditPayload ? model.buildEditPayload : model.buildPayload ?? ((ctx2) => ({ prompt: ctx2.prompt }))
|
|
10661
|
+
};
|
|
11062
10662
|
}
|
|
11063
|
-
function
|
|
11064
|
-
|
|
11065
|
-
|
|
11066
|
-
|
|
11067
|
-
|
|
11068
|
-
|
|
11069
|
-
return
|
|
11070
|
-
}
|
|
11071
|
-
function matchCondition(when, values) {
|
|
11072
|
-
return Object.entries(when).every(
|
|
11073
|
-
([key, op]) => matchOperator(op, values[key])
|
|
11074
|
-
);
|
|
10663
|
+
function prepareRequest(model, params2) {
|
|
10664
|
+
const ctx = { ...params2 };
|
|
10665
|
+
const contract = getModelContract(model.id);
|
|
10666
|
+
const validatedCtx = contract ? contract.input.parse(ctx) : ctx;
|
|
10667
|
+
const resolved = resolvePayloadBuild(model, validatedCtx);
|
|
10668
|
+
const payload = resolved.buildPayload(validatedCtx);
|
|
10669
|
+
return { ctx, workflow: resolved.workflow, payload, contract };
|
|
11075
10670
|
}
|
|
11076
|
-
function
|
|
11077
|
-
|
|
11078
|
-
|
|
11079
|
-
|
|
10671
|
+
function parseResult(completed, model, contract) {
|
|
10672
|
+
throwIfErrorResult(completed.result, model.name);
|
|
10673
|
+
const parsed = contract?.output ? contract.output.parse(completed.result) : completed.result;
|
|
10674
|
+
const multiItems = extractAllResults(parsed);
|
|
10675
|
+
if (multiItems?.length) {
|
|
10676
|
+
const items2 = multiItems.map((item, i) => ({
|
|
10677
|
+
url: item.url,
|
|
10678
|
+
metadata: buildItemMetadata(parsed, item.source, i, model.provider)
|
|
10679
|
+
}));
|
|
10680
|
+
return {
|
|
10681
|
+
url: items2[0].url,
|
|
10682
|
+
items: items2,
|
|
10683
|
+
results: items2,
|
|
10684
|
+
// Sync executions have no job id (nothing to poll) — omit rather than ''.
|
|
10685
|
+
...completed.handle.id ? { generationId: completed.handle.id } : {},
|
|
10686
|
+
usage: completed.usage
|
|
10687
|
+
};
|
|
11080
10688
|
}
|
|
11081
|
-
const
|
|
10689
|
+
const url = extractUrl(parsed);
|
|
10690
|
+
if (!url) {
|
|
10691
|
+
throw new ApiError(`${model.name}: unexpected response \u2014 no result URL`, {
|
|
10692
|
+
status: 502,
|
|
10693
|
+
code: "invalid_response"
|
|
10694
|
+
});
|
|
10695
|
+
}
|
|
10696
|
+
const obj = parsed && typeof parsed === "object" ? parsed : void 0;
|
|
10697
|
+
let source = parsed;
|
|
10698
|
+
for (const key of ["images", "items", "imageUrls", "data", "previews"]) {
|
|
10699
|
+
const arr = obj?.[key];
|
|
10700
|
+
if (Array.isArray(arr) && arr.length > 0) {
|
|
10701
|
+
source = arr[0];
|
|
10702
|
+
break;
|
|
10703
|
+
}
|
|
10704
|
+
}
|
|
10705
|
+
const items = [{ url, metadata: buildItemMetadata(parsed, source, 0, model.provider) }];
|
|
11082
10706
|
return {
|
|
11083
|
-
|
|
11084
|
-
|
|
11085
|
-
|
|
10707
|
+
url,
|
|
10708
|
+
items,
|
|
10709
|
+
results: items,
|
|
10710
|
+
// Sync executions have no job id (nothing to poll) — omit rather than ''.
|
|
10711
|
+
...completed.handle.id ? { generationId: completed.handle.id } : {},
|
|
10712
|
+
usage: completed.usage
|
|
11086
10713
|
};
|
|
11087
10714
|
}
|
|
11088
|
-
function
|
|
11089
|
-
|
|
11090
|
-
|
|
11091
|
-
|
|
11092
|
-
|
|
11093
|
-
|
|
11094
|
-
|
|
11095
|
-
|
|
10715
|
+
function parseTextResult(completed, model) {
|
|
10716
|
+
throwIfErrorResult(completed.result, model.name);
|
|
10717
|
+
throwIfErrorResult(completed.raw, model.name);
|
|
10718
|
+
const text = extractText(completed.result) ?? extractText(completed.raw);
|
|
10719
|
+
if (text == null) {
|
|
10720
|
+
throw new ApiError(`${model.name}: unexpected response \u2014 no text`, {
|
|
10721
|
+
status: 502,
|
|
10722
|
+
code: "invalid_response"
|
|
10723
|
+
});
|
|
11096
10724
|
}
|
|
11097
|
-
return
|
|
10725
|
+
return { text, model: model.id, raw: completed.raw ?? completed.result, usage: completed.usage };
|
|
11098
10726
|
}
|
|
11099
10727
|
|
|
11100
|
-
// src/core/
|
|
11101
|
-
var
|
|
11102
|
-
|
|
11103
|
-
|
|
11104
|
-
var
|
|
11105
|
-
function
|
|
11106
|
-
|
|
11107
|
-
|
|
11108
|
-
|
|
10728
|
+
// src/core/limits.ts
|
|
10729
|
+
var MAX_DRIVE_PROMPT_LENGTH = 18e3;
|
|
10730
|
+
|
|
10731
|
+
// src/client/drive.ts
|
|
10732
|
+
var USER_REACTION_ATTR = "userReaction";
|
|
10733
|
+
function inferResourceType(mode) {
|
|
10734
|
+
if (mode === "video") return "VIDEO";
|
|
10735
|
+
if (mode === "audio") return "AUDIO";
|
|
10736
|
+
return "PHOTO";
|
|
11109
10737
|
}
|
|
11110
|
-
function
|
|
11111
|
-
|
|
11112
|
-
|
|
11113
|
-
|
|
11114
|
-
|
|
11115
|
-
|
|
11116
|
-
}
|
|
11117
|
-
if (!_loadPromise) {
|
|
11118
|
-
const client = _client;
|
|
11119
|
-
_loadPromise = client.init().then(() => {
|
|
11120
|
-
const byModel = /* @__PURE__ */ new Map();
|
|
11121
|
-
for (const entry of client.getModelPricing()) {
|
|
11122
|
-
const id = entry.metadata.modelId;
|
|
11123
|
-
const list = byModel.get(id);
|
|
11124
|
-
if (list) list.push(entry);
|
|
11125
|
-
else byModel.set(id, [entry]);
|
|
11126
|
-
}
|
|
11127
|
-
_byModel = byModel;
|
|
11128
|
-
}).catch((err) => {
|
|
11129
|
-
_loadPromise = null;
|
|
11130
|
-
throw err;
|
|
11131
|
-
});
|
|
11132
|
-
}
|
|
11133
|
-
return _loadPromise;
|
|
10738
|
+
function buildFilename(prompt, mode) {
|
|
10739
|
+
const shortId = String(Date.now()).slice(-6);
|
|
10740
|
+
const ext = mode === "video" ? "mp4" : mode === "audio" ? "mp3" : "png";
|
|
10741
|
+
if (!prompt) return `ai-generation-${shortId}.${ext}`;
|
|
10742
|
+
const slug = prompt.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 50);
|
|
10743
|
+
return `${slug}-${shortId}.${ext}`;
|
|
11134
10744
|
}
|
|
11135
|
-
function
|
|
11136
|
-
|
|
10745
|
+
function inferMediaType(file) {
|
|
10746
|
+
const name = String(file.name || "");
|
|
10747
|
+
if (/\.(mp3|wav|ogg|aac|flac|m4a)$/i.test(name)) return "audio";
|
|
10748
|
+
if (/\.(mp4|webm|mov|avi|mkv|m4v|wmv)$/i.test(name)) return "video";
|
|
10749
|
+
const contentType = file.contentType ?? file.content;
|
|
10750
|
+
const resourceType = String(contentType?.resourceType || "").toUpperCase();
|
|
10751
|
+
if (resourceType === "VIDEO") return "video";
|
|
10752
|
+
if (resourceType === "AUDIO") return "audio";
|
|
10753
|
+
return "image";
|
|
11137
10754
|
}
|
|
11138
|
-
function
|
|
11139
|
-
if (
|
|
11140
|
-
|
|
11141
|
-
if (
|
|
11142
|
-
|
|
11143
|
-
entries = entries.filter((e) => {
|
|
11144
|
-
if (ctx.generateAudio !== void 0 && e.metadata.audio !== ctx.generateAudio) return false;
|
|
11145
|
-
if (ctx.resolution !== void 0 && e.metadata.quality !== ctx.resolution) return false;
|
|
11146
|
-
return true;
|
|
11147
|
-
});
|
|
11148
|
-
if (entries.length === 0) return null;
|
|
11149
|
-
}
|
|
11150
|
-
let min = Infinity;
|
|
11151
|
-
let max = -Infinity;
|
|
11152
|
-
let unit = entries[0].unit;
|
|
11153
|
-
for (const e of entries) {
|
|
11154
|
-
if (e.credits < min) min = e.credits;
|
|
11155
|
-
if (e.credits > max) max = e.credits;
|
|
11156
|
-
if (e.unit !== unit) unit = void 0;
|
|
11157
|
-
}
|
|
11158
|
-
const tiers = entries.map((e) => ({
|
|
11159
|
-
credits: e.credits,
|
|
11160
|
-
unit: e.unit,
|
|
11161
|
-
quality: e.metadata.quality || void 0,
|
|
11162
|
-
audio: e.metadata.audio,
|
|
11163
|
-
useCase: e.metadata.useCase
|
|
11164
|
-
}));
|
|
11165
|
-
return unit ? { min, max, unit, tiers } : { min, max, tiers };
|
|
10755
|
+
function contentResourceTypes(type) {
|
|
10756
|
+
if (type === "image") return "PHOTO";
|
|
10757
|
+
if (type === "video") return "VIDEO";
|
|
10758
|
+
if (type === "audio") return "AUDIO";
|
|
10759
|
+
return "PHOTO,VIDEO,AUDIO";
|
|
11166
10760
|
}
|
|
11167
|
-
|
|
11168
|
-
|
|
11169
|
-
|
|
11170
|
-
if (entry.descriptor.kind !== "catalog") return flat;
|
|
11171
|
-
const hydrated = getHydratedCatalog(entry.descriptor.source);
|
|
11172
|
-
if (!hydrated) return flat;
|
|
11173
|
-
return { ...flat, catalogOptions: hydrated.catalogOptions };
|
|
10761
|
+
function normalizeUrl(raw) {
|
|
10762
|
+
if (typeof raw !== "string") return void 0;
|
|
10763
|
+
return raw.trim() || void 0;
|
|
11174
10764
|
}
|
|
11175
|
-
|
|
11176
|
-
|
|
11177
|
-
|
|
11178
|
-
|
|
11179
|
-
|
|
11180
|
-
|
|
11181
|
-
|
|
11182
|
-
|
|
11183
|
-
const
|
|
11184
|
-
|
|
10765
|
+
function parseAttributes(raw) {
|
|
10766
|
+
const map = {};
|
|
10767
|
+
if (!raw || typeof raw !== "object") return map;
|
|
10768
|
+
if (Array.isArray(raw)) {
|
|
10769
|
+
for (const a of raw) {
|
|
10770
|
+
map[a.property] = String(a.value);
|
|
10771
|
+
}
|
|
10772
|
+
} else {
|
|
10773
|
+
for (const [k, v] of Object.entries(raw)) {
|
|
10774
|
+
map[k] = String(v);
|
|
10775
|
+
}
|
|
11185
10776
|
}
|
|
11186
|
-
|
|
11187
|
-
|
|
10777
|
+
return map;
|
|
10778
|
+
}
|
|
10779
|
+
function parseReaction(value) {
|
|
10780
|
+
return value === "like" || value === "dislike" ? value : void 0;
|
|
10781
|
+
}
|
|
10782
|
+
function parseJsonAttr(raw) {
|
|
10783
|
+
if (!raw) return void 0;
|
|
10784
|
+
try {
|
|
10785
|
+
const value = JSON.parse(raw);
|
|
10786
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
10787
|
+
} catch {
|
|
10788
|
+
return void 0;
|
|
11188
10789
|
}
|
|
11189
|
-
|
|
11190
|
-
|
|
11191
|
-
|
|
11192
|
-
|
|
11193
|
-
|
|
11194
|
-
|
|
11195
|
-
);
|
|
10790
|
+
}
|
|
10791
|
+
var asString = (v) => typeof v === "string" && v.trim() ? v : void 0;
|
|
10792
|
+
var asStringArray = (v) => Array.isArray(v) && v.length && v.every((x) => typeof x === "string") ? v : void 0;
|
|
10793
|
+
function toSdkPayload(params2) {
|
|
10794
|
+
const p2 = { prompt: String(params2.prompt ?? "").slice(0, MAX_DRIVE_PROMPT_LENGTH) };
|
|
10795
|
+
for (const [key, value] of Object.entries(params2)) {
|
|
10796
|
+
if (key === "prompt") continue;
|
|
10797
|
+
if (value === void 0 || value === null || value === "") continue;
|
|
10798
|
+
p2[key] = value;
|
|
11196
10799
|
}
|
|
11197
|
-
|
|
11198
|
-
|
|
11199
|
-
|
|
10800
|
+
return p2;
|
|
10801
|
+
}
|
|
10802
|
+
function buildGenerationAttributes(input) {
|
|
10803
|
+
const attrs = {
|
|
10804
|
+
model: input.modelId,
|
|
10805
|
+
aiSDKPayload: JSON.stringify(toSdkPayload(input.params))
|
|
10806
|
+
};
|
|
10807
|
+
if (input.app) {
|
|
10808
|
+
attrs.appId = input.app.id;
|
|
10809
|
+
attrs.appType = input.app.type;
|
|
11200
10810
|
}
|
|
11201
|
-
|
|
11202
|
-
|
|
10811
|
+
return attrs;
|
|
10812
|
+
}
|
|
10813
|
+
function toMediaItem(file) {
|
|
10814
|
+
const url = normalizeUrl(file.sourceUrl);
|
|
10815
|
+
if (!url || String(file.name || "").startsWith("__")) return null;
|
|
10816
|
+
const preview = file.preview;
|
|
10817
|
+
return {
|
|
10818
|
+
uid: String(file.uid ?? ""),
|
|
10819
|
+
url,
|
|
10820
|
+
name: String(file.name || ""),
|
|
10821
|
+
type: inferMediaType(file),
|
|
10822
|
+
previewUrl: normalizeUrl(preview?.url),
|
|
10823
|
+
timestamp: Number(file.updatedAt ?? file.createdAt ?? 0)
|
|
10824
|
+
};
|
|
10825
|
+
}
|
|
10826
|
+
function toDetailedItem(file) {
|
|
10827
|
+
const base2 = toMediaItem(file);
|
|
10828
|
+
if (!base2) return null;
|
|
10829
|
+
const attrs = parseAttributes(file.attributes);
|
|
10830
|
+
let extras = {};
|
|
10831
|
+
if (attrs.textScript) {
|
|
10832
|
+
try {
|
|
10833
|
+
extras = JSON.parse(attrs.textScript);
|
|
10834
|
+
} catch {
|
|
10835
|
+
}
|
|
11203
10836
|
}
|
|
11204
|
-
|
|
11205
|
-
|
|
11206
|
-
|
|
11207
|
-
|
|
11208
|
-
|
|
11209
|
-
|
|
11210
|
-
|
|
11211
|
-
|
|
11212
|
-
|
|
11213
|
-
|
|
11214
|
-
|
|
11215
|
-
|
|
11216
|
-
|
|
11217
|
-
|
|
11218
|
-
|
|
11219
|
-
}
|
|
11220
|
-
|
|
11221
|
-
|
|
11222
|
-
}
|
|
11223
|
-
|
|
11224
|
-
|
|
11225
|
-
|
|
11226
|
-
|
|
11227
|
-
|
|
11228
|
-
|
|
11229
|
-
|
|
11230
|
-
|
|
11231
|
-
return this.narrow("resolution", "enum");
|
|
11232
|
-
}
|
|
11233
|
-
generateAudio() {
|
|
11234
|
-
return this.narrow("generateAudio", "boolean");
|
|
11235
|
-
}
|
|
11236
|
-
startFrame() {
|
|
11237
|
-
return this.narrow("startFrame", "file");
|
|
10837
|
+
return {
|
|
10838
|
+
...base2,
|
|
10839
|
+
createdAt: file.createdAt,
|
|
10840
|
+
model: attrs.model,
|
|
10841
|
+
prompt: attrs.prompt || void 0,
|
|
10842
|
+
service: attrs.service,
|
|
10843
|
+
subType: attrs.subType,
|
|
10844
|
+
duration: attrs.duration,
|
|
10845
|
+
userReaction: parseReaction(attrs[USER_REACTION_ATTR]),
|
|
10846
|
+
referenceImageUrls: extras.referenceImageUrls,
|
|
10847
|
+
referenceVideoUrl: extras.referenceVideoUrl,
|
|
10848
|
+
referenceAudioUrl: extras.referenceAudioUrl,
|
|
10849
|
+
aspectRatio: extras.aspectRatio,
|
|
10850
|
+
resolution: extras.resolution,
|
|
10851
|
+
quality: extras.quality
|
|
10852
|
+
};
|
|
10853
|
+
}
|
|
10854
|
+
var LEGACY_TOOL_APP = {
|
|
10855
|
+
"ai-playground": { appId: "com.picsart.ai-playground", appType: "miniapp" }
|
|
10856
|
+
};
|
|
10857
|
+
function adaptLegacyGeneration(attrs) {
|
|
10858
|
+
let extras = {};
|
|
10859
|
+
if (attrs.textScript) {
|
|
10860
|
+
try {
|
|
10861
|
+
extras = JSON.parse(attrs.textScript);
|
|
10862
|
+
} catch {
|
|
10863
|
+
}
|
|
11238
10864
|
}
|
|
11239
|
-
|
|
11240
|
-
|
|
10865
|
+
const aiSDKPayload = { prompt: attrs.prompt || "" };
|
|
10866
|
+
const aspectRatio = asString(extras.aspectRatio);
|
|
10867
|
+
if (aspectRatio) aiSDKPayload.aspectRatio = aspectRatio;
|
|
10868
|
+
const resolution = asString(extras.resolution);
|
|
10869
|
+
if (resolution) aiSDKPayload.resolution = resolution;
|
|
10870
|
+
const duration = extras.duration ?? attrs.duration;
|
|
10871
|
+
if (duration != null && duration !== "") aiSDKPayload.duration = Number(duration);
|
|
10872
|
+
const imageUrls = asStringArray(extras.referenceImageUrls);
|
|
10873
|
+
if (imageUrls) aiSDKPayload.imageUrls = imageUrls;
|
|
10874
|
+
const videoUrl = asString(extras.referenceVideoUrl);
|
|
10875
|
+
if (videoUrl) aiSDKPayload.videoUrl = videoUrl;
|
|
10876
|
+
const audioUrl = asString(extras.referenceAudioUrl);
|
|
10877
|
+
if (audioUrl) aiSDKPayload.audioUrl = audioUrl;
|
|
10878
|
+
const startFrame = asString(extras.startFrame);
|
|
10879
|
+
if (startFrame) aiSDKPayload.startFrame = startFrame;
|
|
10880
|
+
const endFrame = asString(extras.endFrame);
|
|
10881
|
+
if (endFrame) aiSDKPayload.endFrame = endFrame;
|
|
10882
|
+
const quality = asString(extras.quality);
|
|
10883
|
+
if (quality) aiSDKPayload.quality = quality;
|
|
10884
|
+
const style = asString(extras.style);
|
|
10885
|
+
if (style) aiSDKPayload.style = style;
|
|
10886
|
+
const iterateModel = asString(extras.iterateModel);
|
|
10887
|
+
if (iterateModel) aiSDKPayload.iterateModel = iterateModel;
|
|
10888
|
+
const exploreImageId = asString(extras.exploreImageId);
|
|
10889
|
+
if (exploreImageId) aiSDKPayload.exploreImageId = exploreImageId;
|
|
10890
|
+
const app = attrs.tool ? LEGACY_TOOL_APP[attrs.tool] : void 0;
|
|
10891
|
+
return {
|
|
10892
|
+
appId: app?.appId,
|
|
10893
|
+
appType: app?.appType,
|
|
10894
|
+
model: attrs.model || void 0,
|
|
10895
|
+
aiSDKPayload,
|
|
10896
|
+
userReaction: parseReaction(attrs[USER_REACTION_ATTR])
|
|
10897
|
+
};
|
|
10898
|
+
}
|
|
10899
|
+
function parseGeneration(file) {
|
|
10900
|
+
const attrs = parseAttributes(file.attributes);
|
|
10901
|
+
if (!attrs.aiSDKPayload) {
|
|
10902
|
+
return adaptLegacyGeneration(attrs);
|
|
11241
10903
|
}
|
|
11242
|
-
|
|
11243
|
-
|
|
11244
|
-
|
|
10904
|
+
return {
|
|
10905
|
+
appId: attrs.appId || void 0,
|
|
10906
|
+
appType: attrs.appType === "native" || attrs.appType === "miniapp" ? attrs.appType : void 0,
|
|
10907
|
+
model: attrs.model || void 0,
|
|
10908
|
+
aiSDKPayload: parseJsonAttr(attrs.aiSDKPayload),
|
|
10909
|
+
userReaction: parseReaction(attrs[USER_REACTION_ATTR])
|
|
10910
|
+
};
|
|
10911
|
+
}
|
|
10912
|
+
function createDriveClient(f, apiUrl, rootFolderName) {
|
|
10913
|
+
let cachedRootUid = null;
|
|
10914
|
+
let rootPromise = null;
|
|
10915
|
+
const jsonPost = async (path, body) => f(`${apiUrl}${path}`, {
|
|
10916
|
+
method: "POST",
|
|
10917
|
+
headers: { "Content-Type": "application/json" },
|
|
10918
|
+
body: JSON.stringify(body)
|
|
10919
|
+
});
|
|
10920
|
+
const jsonGet = async (path) => f(`${apiUrl}${path}`);
|
|
10921
|
+
async function findFolderByPath(name) {
|
|
10922
|
+
try {
|
|
10923
|
+
const res = await jsonGet(`/cloud-storage/v1/me/files-by-path?path=${encodeURIComponent(name)}`);
|
|
10924
|
+
if (!res.ok) return null;
|
|
10925
|
+
const data = await res.json();
|
|
10926
|
+
if (data.status !== "success") return null;
|
|
10927
|
+
const response = data.response;
|
|
10928
|
+
const file = Array.isArray(response) ? response[0] : response;
|
|
10929
|
+
return file?.uid ?? null;
|
|
10930
|
+
} catch {
|
|
10931
|
+
return null;
|
|
10932
|
+
}
|
|
11245
10933
|
}
|
|
11246
|
-
|
|
11247
|
-
|
|
11248
|
-
|
|
11249
|
-
|
|
11250
|
-
|
|
10934
|
+
async function findFolderInList(name, parentUid) {
|
|
10935
|
+
try {
|
|
10936
|
+
const params2 = parentUid ? `parentFolderUid=${parentUid}&fileTypes=FOLDER&limit=100` : `fileTypes=FOLDER&limit=100`;
|
|
10937
|
+
const res = await jsonGet(`/cloud-storage/v1/me/files?${params2}`);
|
|
10938
|
+
if (!res.ok) return null;
|
|
10939
|
+
const data = await res.json();
|
|
10940
|
+
const response = data.response;
|
|
10941
|
+
const files = Array.isArray(response) ? response : [];
|
|
10942
|
+
const match = files.find((f2) => String(f2.name || "").toLowerCase() === name.toLowerCase());
|
|
10943
|
+
return match?.uid ?? null;
|
|
10944
|
+
} catch {
|
|
10945
|
+
return null;
|
|
10946
|
+
}
|
|
11251
10947
|
}
|
|
11252
|
-
|
|
11253
|
-
|
|
10948
|
+
async function createFolder(name, parentUid) {
|
|
10949
|
+
try {
|
|
10950
|
+
const body = { name };
|
|
10951
|
+
if (parentUid) body.parentFolderUid = parentUid;
|
|
10952
|
+
const res = await jsonPost("/cloud-storage/v1/me/folders", body);
|
|
10953
|
+
if (!res.ok) return null;
|
|
10954
|
+
const data = await res.json();
|
|
10955
|
+
const response = data.response;
|
|
10956
|
+
return response?.uid ?? null;
|
|
10957
|
+
} catch {
|
|
10958
|
+
return null;
|
|
10959
|
+
}
|
|
11254
10960
|
}
|
|
11255
|
-
|
|
11256
|
-
|
|
11257
|
-
|
|
11258
|
-
|
|
11259
|
-
|
|
10961
|
+
async function resolveRootFolder() {
|
|
10962
|
+
const byPath = await findFolderByPath(rootFolderName);
|
|
10963
|
+
if (byPath) return byPath;
|
|
10964
|
+
const inList = await findFolderInList(rootFolderName);
|
|
10965
|
+
if (inList) return inList;
|
|
10966
|
+
const recheck = await findFolderByPath(rootFolderName);
|
|
10967
|
+
if (recheck) return recheck;
|
|
10968
|
+
return createFolder(rootFolderName);
|
|
11260
10969
|
}
|
|
11261
|
-
|
|
11262
|
-
|
|
10970
|
+
async function ensureRootFolder() {
|
|
10971
|
+
if (cachedRootUid) return cachedRootUid;
|
|
10972
|
+
if (!rootPromise) {
|
|
10973
|
+
rootPromise = resolveRootFolder().then((uid) => {
|
|
10974
|
+
cachedRootUid = uid;
|
|
10975
|
+
rootPromise = null;
|
|
10976
|
+
return uid;
|
|
10977
|
+
}).catch((err) => {
|
|
10978
|
+
setTimeout(() => {
|
|
10979
|
+
rootPromise = null;
|
|
10980
|
+
}, 1e4);
|
|
10981
|
+
throw err;
|
|
10982
|
+
});
|
|
10983
|
+
}
|
|
10984
|
+
return rootPromise;
|
|
11263
10985
|
}
|
|
11264
|
-
|
|
11265
|
-
|
|
10986
|
+
async function fetchFolders(parentUid) {
|
|
10987
|
+
try {
|
|
10988
|
+
const params2 = parentUid ? `parentFolderUid=${parentUid}&fileTypes=FOLDER&limit=100` : `fileTypes=FOLDER&limit=100`;
|
|
10989
|
+
const res = await jsonGet(`/cloud-storage/v1/me/files?${params2}`);
|
|
10990
|
+
if (!res.ok) return [];
|
|
10991
|
+
const data = await res.json();
|
|
10992
|
+
const files = Array.isArray(data.response) ? data.response : [];
|
|
10993
|
+
return files.filter((f2) => f2.uid && f2.name).map((f2) => ({ name: String(f2.name), uid: String(f2.uid) }));
|
|
10994
|
+
} catch {
|
|
10995
|
+
return [];
|
|
10996
|
+
}
|
|
11266
10997
|
}
|
|
11267
|
-
|
|
11268
|
-
|
|
11269
|
-
|
|
11270
|
-
|
|
10998
|
+
async function fetchMedia(opts) {
|
|
10999
|
+
try {
|
|
11000
|
+
const endpoint = opts.folderUid ? "/cloud-storage/v1/me/files" : "/cloud-storage/v1/me/flattened-files";
|
|
11001
|
+
const params2 = [
|
|
11002
|
+
opts.folderUid ? `parentFolderUid=${opts.folderUid}` : "",
|
|
11003
|
+
"limit=100",
|
|
11004
|
+
"sortType=UPDATED",
|
|
11005
|
+
"sortOrder=DESC",
|
|
11006
|
+
"fileTypes=FILE",
|
|
11007
|
+
`contentResourceTypes=${contentResourceTypes(opts.type)}`
|
|
11008
|
+
].filter(Boolean).join("&");
|
|
11009
|
+
const res = await jsonGet(`${endpoint}?${params2}`);
|
|
11010
|
+
if (!res.ok) return [];
|
|
11011
|
+
const data = await res.json();
|
|
11012
|
+
return Array.isArray(data.response) ? data.response : [];
|
|
11013
|
+
} catch {
|
|
11014
|
+
return [];
|
|
11015
|
+
}
|
|
11271
11016
|
}
|
|
11272
|
-
|
|
11273
|
-
|
|
11274
|
-
|
|
11275
|
-
|
|
11276
|
-
|
|
11277
|
-
|
|
11278
|
-
|
|
11017
|
+
async function fetchFileByUid(fileUid) {
|
|
11018
|
+
try {
|
|
11019
|
+
const res = await jsonGet(`/drive/v1/files/${fileUid}`);
|
|
11020
|
+
if (!res.ok) return null;
|
|
11021
|
+
const data = await res.json();
|
|
11022
|
+
const file = data.response;
|
|
11023
|
+
return file && typeof file === "object" && !Array.isArray(file) ? file : null;
|
|
11024
|
+
} catch {
|
|
11025
|
+
return null;
|
|
11026
|
+
}
|
|
11279
11027
|
}
|
|
11280
|
-
|
|
11281
|
-
|
|
11282
|
-
|
|
11028
|
+
async function setReaction(fileUid, reaction) {
|
|
11029
|
+
try {
|
|
11030
|
+
const res = await f(`${apiUrl}/drive/v1/files/${fileUid}`, {
|
|
11031
|
+
method: "PATCH",
|
|
11032
|
+
headers: { "Content-Type": "application/json" },
|
|
11033
|
+
body: JSON.stringify({ attributes: { [USER_REACTION_ATTR]: reaction } })
|
|
11034
|
+
});
|
|
11035
|
+
return res.ok;
|
|
11036
|
+
} catch {
|
|
11037
|
+
return false;
|
|
11038
|
+
}
|
|
11283
11039
|
}
|
|
11284
|
-
|
|
11285
|
-
|
|
11286
|
-
|
|
11287
|
-
|
|
11288
|
-
|
|
11289
|
-
|
|
11290
|
-
|
|
11291
|
-
|
|
11292
|
-
|
|
11293
|
-
|
|
11294
|
-
|
|
11295
|
-
|
|
11296
|
-
|
|
11297
|
-
|
|
11298
|
-
|
|
11299
|
-
|
|
11300
|
-
|
|
11301
|
-
|
|
11302
|
-
|
|
11303
|
-
|
|
11304
|
-
|
|
11305
|
-
|
|
11306
|
-
|
|
11307
|
-
|
|
11308
|
-
|
|
11309
|
-
|
|
11310
|
-
|
|
11311
|
-
|
|
11312
|
-
|
|
11313
|
-
|
|
11040
|
+
return {
|
|
11041
|
+
/**
|
|
11042
|
+
* Ensure a subfolder exists inside the root folder.
|
|
11043
|
+
* Creates both root and subfolder if needed. Returns the folder reference.
|
|
11044
|
+
* Call with no argument to just ensure the root folder exists.
|
|
11045
|
+
*/
|
|
11046
|
+
async ensureFolder(subfolder) {
|
|
11047
|
+
const rootUid = await ensureRootFolder();
|
|
11048
|
+
if (!rootUid) return null;
|
|
11049
|
+
if (!subfolder) {
|
|
11050
|
+
return { name: rootFolderName, uid: rootUid };
|
|
11051
|
+
}
|
|
11052
|
+
const existingUid = await findFolderInList(subfolder, rootUid);
|
|
11053
|
+
if (existingUid) return { name: subfolder, uid: existingUid };
|
|
11054
|
+
const newUid = await createFolder(subfolder, rootUid);
|
|
11055
|
+
if (!newUid) return null;
|
|
11056
|
+
return { name: subfolder, uid: newUid };
|
|
11057
|
+
},
|
|
11058
|
+
/** List subfolders inside the root folder (boards). */
|
|
11059
|
+
async folders() {
|
|
11060
|
+
const rootUid = await ensureRootFolder();
|
|
11061
|
+
if (!rootUid) return [];
|
|
11062
|
+
return fetchFolders(rootUid);
|
|
11063
|
+
},
|
|
11064
|
+
/** List top-level Drive folders + root subfolders, deduplicated. */
|
|
11065
|
+
async allFolders() {
|
|
11066
|
+
const rootUid = await ensureRootFolder();
|
|
11067
|
+
const [rootLevel, subfolders] = await Promise.all([
|
|
11068
|
+
fetchFolders(),
|
|
11069
|
+
rootUid ? fetchFolders(rootUid) : Promise.resolve([])
|
|
11070
|
+
]);
|
|
11071
|
+
const seen = /* @__PURE__ */ new Set();
|
|
11072
|
+
const merged = [];
|
|
11073
|
+
for (const folder of [...rootLevel, ...subfolders]) {
|
|
11074
|
+
if (seen.has(folder.uid)) continue;
|
|
11075
|
+
seen.add(folder.uid);
|
|
11076
|
+
merged.push(folder);
|
|
11077
|
+
}
|
|
11078
|
+
return merged;
|
|
11079
|
+
},
|
|
11080
|
+
/** Find a folder by name (case-insensitive) across root and subfolders. */
|
|
11081
|
+
async findFolder(name) {
|
|
11082
|
+
if (name.toLowerCase() === rootFolderName.toLowerCase()) {
|
|
11083
|
+
const uid = await ensureRootFolder();
|
|
11084
|
+
return uid ? { name: rootFolderName, uid } : null;
|
|
11085
|
+
}
|
|
11086
|
+
const rootUid = await ensureRootFolder();
|
|
11087
|
+
const [rootLevel, subfolders] = await Promise.all([
|
|
11088
|
+
fetchFolders(),
|
|
11089
|
+
rootUid ? fetchFolders(rootUid) : Promise.resolve([])
|
|
11090
|
+
]);
|
|
11091
|
+
const lowerName = name.toLowerCase();
|
|
11092
|
+
return [...rootLevel, ...subfolders].find((f2) => f2.name.toLowerCase() === lowerName) ?? null;
|
|
11093
|
+
},
|
|
11094
|
+
/**
|
|
11095
|
+
* List media items. When no folder is given, lists across all folders (flattened).
|
|
11096
|
+
* Optionally filter by media type (sent to backend, not client-side).
|
|
11097
|
+
*/
|
|
11098
|
+
async list(options) {
|
|
11099
|
+
const folderUid = options?.folder?.uid ?? void 0;
|
|
11100
|
+
const files = await fetchMedia({ folderUid, type: options?.type });
|
|
11101
|
+
const items = [];
|
|
11102
|
+
for (const file of files) {
|
|
11103
|
+
const item = toMediaItem(file);
|
|
11104
|
+
if (item) items.push(item);
|
|
11105
|
+
}
|
|
11106
|
+
return items;
|
|
11107
|
+
},
|
|
11108
|
+
/**
|
|
11109
|
+
* List media items with full generation metadata (model, prompt, params, etc.).
|
|
11110
|
+
* Same options as list() — folder and type filter.
|
|
11111
|
+
*/
|
|
11112
|
+
async listDetailed(options) {
|
|
11113
|
+
const folderUid = options?.folder?.uid ?? void 0;
|
|
11114
|
+
const files = await fetchMedia({ folderUid, type: options?.type });
|
|
11115
|
+
const items = [];
|
|
11116
|
+
for (const file of files) {
|
|
11117
|
+
const item = toDetailedItem(file);
|
|
11118
|
+
if (item) items.push(item);
|
|
11119
|
+
}
|
|
11120
|
+
return items;
|
|
11121
|
+
},
|
|
11122
|
+
async getGeneration(fileUid) {
|
|
11123
|
+
const file = await fetchFileByUid(fileUid);
|
|
11124
|
+
return file ? parseGeneration(file) : null;
|
|
11125
|
+
},
|
|
11126
|
+
/** Save a file to Drive. Returns save result or null on failure. */
|
|
11127
|
+
async save(params2, folder) {
|
|
11128
|
+
const targetUid = folder?.uid ?? await ensureRootFolder();
|
|
11129
|
+
if (!targetUid) return null;
|
|
11130
|
+
const targetFolder = folder ?? { name: rootFolderName, uid: targetUid };
|
|
11131
|
+
const body = {
|
|
11132
|
+
name: params2.name,
|
|
11133
|
+
sourceUrl: params2.url,
|
|
11134
|
+
parentFolderUid: targetUid,
|
|
11135
|
+
content: {
|
|
11136
|
+
type: "STANDALONE",
|
|
11137
|
+
resourceType: params2.resourceType,
|
|
11138
|
+
sourcePlatform: "WEB"
|
|
11139
|
+
},
|
|
11140
|
+
preview: {
|
|
11141
|
+
url: params2.previewUrl || params2.url,
|
|
11142
|
+
width: 1024,
|
|
11143
|
+
height: 1024
|
|
11144
|
+
},
|
|
11145
|
+
attributes: Object.entries(params2.attributes ?? {}).map(([property, value]) => ({
|
|
11146
|
+
property,
|
|
11147
|
+
value
|
|
11148
|
+
}))
|
|
11149
|
+
};
|
|
11150
|
+
try {
|
|
11151
|
+
let res = await jsonPost("/cloud-storage/v1/me/files", body);
|
|
11152
|
+
if (res.status === 400) {
|
|
11153
|
+
const text = await res.text();
|
|
11154
|
+
if (text.includes("restricted_keywords")) {
|
|
11155
|
+
const ext = params2.name.split(".").pop() || "png";
|
|
11156
|
+
body.name = `ai-generation-${Date.now()}.${ext}`;
|
|
11157
|
+
res = await jsonPost("/cloud-storage/v1/me/files", body);
|
|
11158
|
+
} else {
|
|
11159
|
+
return null;
|
|
11160
|
+
}
|
|
11161
|
+
}
|
|
11162
|
+
if (!res.ok) return null;
|
|
11163
|
+
const data = await res.json();
|
|
11164
|
+
const file = data.response;
|
|
11165
|
+
const uid = file?.uid;
|
|
11166
|
+
if (!uid) return null;
|
|
11167
|
+
return { uid, folder: targetFolder };
|
|
11168
|
+
} catch {
|
|
11169
|
+
return null;
|
|
11170
|
+
}
|
|
11171
|
+
},
|
|
11172
|
+
/** Build standard save params from a generation result. */
|
|
11173
|
+
buildSaveParams(url, modelId, modelName, mode, prompt) {
|
|
11174
|
+
return {
|
|
11175
|
+
url,
|
|
11176
|
+
name: buildFilename(prompt, mode),
|
|
11177
|
+
resourceType: inferResourceType(mode),
|
|
11178
|
+
attributes: {
|
|
11179
|
+
tool: "ai-sdk",
|
|
11180
|
+
model: modelId,
|
|
11181
|
+
prompt: prompt || "",
|
|
11182
|
+
service: modelName
|
|
11183
|
+
}
|
|
11184
|
+
};
|
|
11185
|
+
},
|
|
11186
|
+
async addReaction(fileUid, reaction) {
|
|
11187
|
+
return setReaction(fileUid, reaction);
|
|
11188
|
+
},
|
|
11189
|
+
async removeReaction(fileUid) {
|
|
11190
|
+
return setReaction(fileUid, null);
|
|
11191
|
+
}
|
|
11192
|
+
};
|
|
11193
|
+
}
|
|
11194
|
+
|
|
11195
|
+
// src/client/apis.ts
|
|
11196
|
+
function createApis(client) {
|
|
11197
|
+
return {
|
|
11198
|
+
async run(api, payload, options) {
|
|
11199
|
+
if (!client) {
|
|
11200
|
+
throw new ApiError(
|
|
11201
|
+
"`ai.apis` requires `apiUrl` plus `fetch` or `apiKey` on createClient \u2014 the workflows APIs are not served by a custom transport.",
|
|
11202
|
+
{ status: 400, code: "unsupported_transport" }
|
|
11203
|
+
);
|
|
11204
|
+
}
|
|
11205
|
+
const forwarded = { ...options ?? {} };
|
|
11206
|
+
delete forwarded.remoteSettingName;
|
|
11207
|
+
delete forwarded.onPartialResult;
|
|
11208
|
+
delete forwarded.notificationConfig;
|
|
11209
|
+
try {
|
|
11210
|
+
return await client.run(api, payload, forwarded);
|
|
11211
|
+
} catch (err) {
|
|
11212
|
+
throw toApiError(err, api);
|
|
11213
|
+
}
|
|
11214
|
+
}
|
|
11215
|
+
// The public conditional-typed signature lives on ApisClient; the runtime
|
|
11216
|
+
// impl is uniform, so we assert the shape here.
|
|
11217
|
+
};
|
|
11218
|
+
}
|
|
11219
|
+
|
|
11220
|
+
// src/client/catalogs.ts
|
|
11221
|
+
var DEFAULT_LIMIT = 100;
|
|
11222
|
+
var MIN_TTL_SECONDS = 60;
|
|
11223
|
+
var copyPage = (page) => ({
|
|
11224
|
+
items: [...page.items],
|
|
11225
|
+
nextCursor: page.nextCursor
|
|
11226
|
+
});
|
|
11227
|
+
var abortError = (signal) => signal.reason ?? new DOMException("The catalog load was aborted.", "AbortError");
|
|
11228
|
+
function abortable(promise, signal) {
|
|
11229
|
+
if (!signal) return promise;
|
|
11230
|
+
if (signal.aborted) return Promise.reject(abortError(signal));
|
|
11231
|
+
return new Promise((resolve, reject) => {
|
|
11232
|
+
const onAbort = () => reject(abortError(signal));
|
|
11233
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
11234
|
+
const settle = () => signal.removeEventListener("abort", onAbort);
|
|
11235
|
+
promise.then(
|
|
11236
|
+
(value) => {
|
|
11237
|
+
settle();
|
|
11238
|
+
resolve(value);
|
|
11239
|
+
},
|
|
11240
|
+
(err) => {
|
|
11241
|
+
settle();
|
|
11242
|
+
reject(err);
|
|
11243
|
+
}
|
|
11244
|
+
);
|
|
11245
|
+
});
|
|
11246
|
+
}
|
|
11247
|
+
function createCatalogs(transport, options) {
|
|
11248
|
+
const stores = /* @__PURE__ */ new Map();
|
|
11249
|
+
const inflight = /* @__PURE__ */ new Map();
|
|
11250
|
+
const keyOf2 = (s) => `${s.workflow} ${s.modelId ?? ""}`;
|
|
11251
|
+
async function fetchPage(workflow, query) {
|
|
11252
|
+
const payload = {};
|
|
11253
|
+
if (query.modelId) payload.modelId = query.modelId;
|
|
11254
|
+
if (query.cursor) payload.cursor = query.cursor;
|
|
11255
|
+
if (query.limit) payload.limit = query.limit;
|
|
11256
|
+
let res;
|
|
11257
|
+
try {
|
|
11258
|
+
res = await transport.execute({ workflow, payload });
|
|
11259
|
+
} catch (err) {
|
|
11260
|
+
if (err instanceof ApiError || err instanceof DOMException && err.name === "AbortError") throw err;
|
|
11261
|
+
throw new ApiError(`${workflow} failed: ${err instanceof Error ? err.message : String(err)}`, {
|
|
11262
|
+
status: 502,
|
|
11263
|
+
code: "bad_gateway"
|
|
11264
|
+
});
|
|
11265
|
+
}
|
|
11266
|
+
const body = res.result ?? res.raw;
|
|
11267
|
+
const container = body?.response ?? body;
|
|
11268
|
+
const result = container?.result ?? container;
|
|
11269
|
+
throwIfErrorResult(result, workflow);
|
|
11270
|
+
if (!result || !Array.isArray(result.items)) {
|
|
11271
|
+
throw new ApiError(`${workflow} returned no catalog result`, {
|
|
11272
|
+
status: 502,
|
|
11273
|
+
code: "invalid_response"
|
|
11274
|
+
});
|
|
11275
|
+
}
|
|
11276
|
+
return { ...result, nextCursor: result.nextCursor ?? null };
|
|
11314
11277
|
}
|
|
11315
|
-
|
|
11316
|
-
|
|
11278
|
+
function storeFor(source, forceRefresh) {
|
|
11279
|
+
const key = keyOf2(source);
|
|
11280
|
+
let store = stores.get(key);
|
|
11281
|
+
if (!store) {
|
|
11282
|
+
store = { pages: /* @__PURE__ */ new Map(), version: "", expiresAt: 0, gen: 0 };
|
|
11283
|
+
stores.set(key, store);
|
|
11284
|
+
return store;
|
|
11285
|
+
}
|
|
11286
|
+
if (forceRefresh || store.expiresAt !== 0 && store.expiresAt <= Date.now()) {
|
|
11287
|
+
store.pages.clear();
|
|
11288
|
+
store.version = "";
|
|
11289
|
+
store.expiresAt = 0;
|
|
11290
|
+
store.gen += 1;
|
|
11291
|
+
}
|
|
11292
|
+
return store;
|
|
11317
11293
|
}
|
|
11318
|
-
|
|
11319
|
-
|
|
11294
|
+
function accumulated(store) {
|
|
11295
|
+
const byId = /* @__PURE__ */ new Map();
|
|
11296
|
+
for (const page of store.pages.values()) {
|
|
11297
|
+
for (const item of page.items) byId.set(item.id, item);
|
|
11298
|
+
}
|
|
11299
|
+
return [...byId.values()];
|
|
11320
11300
|
}
|
|
11321
|
-
|
|
11322
|
-
|
|
11323
|
-
|
|
11324
|
-
|
|
11325
|
-
|
|
11326
|
-
|
|
11327
|
-
|
|
11301
|
+
async function loadPage(def, paramKey, source, options2) {
|
|
11302
|
+
const store = storeFor(source, options2?.forceRefresh);
|
|
11303
|
+
const cursorKey = options2?.cursor ?? "";
|
|
11304
|
+
const cached = store.pages.get(cursorKey);
|
|
11305
|
+
if (cached) return abortable(Promise.resolve(copyPage(cached)), options2?.signal);
|
|
11306
|
+
const inflightKey = `${keyOf2(source)} ${cursorKey}`;
|
|
11307
|
+
if (!options2?.forceRefresh) {
|
|
11308
|
+
const pending = inflight.get(inflightKey);
|
|
11309
|
+
if (pending) return abortable(pending.then(copyPage), options2?.signal);
|
|
11310
|
+
}
|
|
11311
|
+
const gen = store.gen;
|
|
11312
|
+
const run = fetchPage(source.workflow, {
|
|
11313
|
+
modelId: source.modelId,
|
|
11314
|
+
cursor: options2?.cursor,
|
|
11315
|
+
limit: options2?.limit ?? DEFAULT_LIMIT
|
|
11316
|
+
}).then((res) => {
|
|
11317
|
+
const page = { items: res.items, nextCursor: res.nextCursor };
|
|
11318
|
+
if (store.gen === gen) {
|
|
11319
|
+
store.pages.set(cursorKey, page);
|
|
11320
|
+
store.version = res.version;
|
|
11321
|
+
if (store.expiresAt === 0) {
|
|
11322
|
+
store.expiresAt = Date.now() + Math.max(MIN_TTL_SECONDS, res.ttlSeconds || 0) * 1e3;
|
|
11323
|
+
}
|
|
11324
|
+
installHydratedCatalog(source, paramKey, accumulated(store), def.provider, store.version);
|
|
11325
|
+
}
|
|
11326
|
+
return page;
|
|
11327
|
+
}).finally(() => {
|
|
11328
|
+
if (inflight.get(inflightKey) === run) inflight.delete(inflightKey);
|
|
11328
11329
|
});
|
|
11330
|
+
inflight.set(inflightKey, run);
|
|
11331
|
+
return abortable(run.then(copyPage), options2?.signal);
|
|
11329
11332
|
}
|
|
11330
|
-
|
|
11331
|
-
|
|
11332
|
-
|
|
11333
|
+
function requireSource(def, key) {
|
|
11334
|
+
const d = def.paramConfig[key]?.descriptor;
|
|
11335
|
+
const source = d?.kind === "catalog" ? d.source : void 0;
|
|
11336
|
+
if (!source) {
|
|
11337
|
+
throw new Error(`Model "${def.id}" has no runtime catalog on param "${key}" \u2014 its options are static.`);
|
|
11338
|
+
}
|
|
11339
|
+
return source;
|
|
11333
11340
|
}
|
|
11334
|
-
|
|
11335
|
-
|
|
11341
|
+
async function loadParam(model, key, options2) {
|
|
11342
|
+
const def = resolveModel(model);
|
|
11343
|
+
return loadPage(def, key, requireSource(def, key), options2);
|
|
11336
11344
|
}
|
|
11337
|
-
|
|
11338
|
-
|
|
11345
|
+
const client = {
|
|
11346
|
+
voices: (model, options2) => loadParam(model, "voiceId", options2),
|
|
11347
|
+
avatars: (model, options2) => loadParam(model, "videoId", options2),
|
|
11348
|
+
templates: (model, options2) => loadParam(model, "templateId", options2)
|
|
11349
|
+
};
|
|
11350
|
+
if (options?.preload) {
|
|
11351
|
+
const seen = /* @__PURE__ */ new Set();
|
|
11352
|
+
for (const def of ALL_MODELS) {
|
|
11353
|
+
for (const [key, entry] of Object.entries(def.paramConfig)) {
|
|
11354
|
+
const d = entry.descriptor;
|
|
11355
|
+
const source = d.kind === "catalog" ? d.source : void 0;
|
|
11356
|
+
if (!source || seen.has(keyOf2(source))) continue;
|
|
11357
|
+
seen.add(keyOf2(source));
|
|
11358
|
+
void loadPage(def, key, source).catch(() => {
|
|
11359
|
+
});
|
|
11360
|
+
}
|
|
11361
|
+
}
|
|
11339
11362
|
}
|
|
11340
|
-
|
|
11341
|
-
|
|
11363
|
+
return client;
|
|
11364
|
+
}
|
|
11365
|
+
|
|
11366
|
+
// src/client/types.ts
|
|
11367
|
+
var GenerationEventType = {
|
|
11368
|
+
Progress: "generation.progress",
|
|
11369
|
+
Completed: "generation.completed",
|
|
11370
|
+
Failed: "generation.failed"
|
|
11371
|
+
};
|
|
11372
|
+
|
|
11373
|
+
// src/client/index.ts
|
|
11374
|
+
var MODE_POLL_DEFAULTS = {
|
|
11375
|
+
video: { intervalMs: 2e3, maxAttempts: 1800 },
|
|
11376
|
+
// 2s × 1800 = 1 hour
|
|
11377
|
+
image: { intervalMs: 1e3, maxAttempts: 1200 },
|
|
11378
|
+
// 1s × 1200 = 20 min
|
|
11379
|
+
audio: { intervalMs: 1e3, maxAttempts: 1200 },
|
|
11380
|
+
// 1s × 1200 = 20 min
|
|
11381
|
+
text: { intervalMs: 1e3, maxAttempts: 1200 }
|
|
11382
|
+
// 1s × 1200 = 20 min
|
|
11383
|
+
};
|
|
11384
|
+
function resolvePollOptions(model, overrides) {
|
|
11385
|
+
const resolved = { ...MODE_POLL_DEFAULTS[model.mode], ...model.pollOptions };
|
|
11386
|
+
if (overrides?.intervalMs !== void 0) resolved.intervalMs = overrides.intervalMs;
|
|
11387
|
+
if (overrides?.maxAttempts !== void 0) resolved.maxAttempts = overrides.maxAttempts;
|
|
11388
|
+
if (overrides?.signal !== void 0) resolved.signal = overrides.signal;
|
|
11389
|
+
return resolved;
|
|
11390
|
+
}
|
|
11391
|
+
function createClient(config) {
|
|
11392
|
+
const authedFetch = maybeFetch(config);
|
|
11393
|
+
const wc = config.apiUrl && authedFetch ? createWorkflowsClient(config.apiUrl, authedFetch) : null;
|
|
11394
|
+
function buildDefaultTransport() {
|
|
11395
|
+
if (wc) return buildTransport(wc);
|
|
11396
|
+
throw new Error(config.apiUrl ? "createClient config requires either `fetch` or `apiKey` (or a custom `transport`)." : "createClient config requires `apiUrl` (or a custom `transport`).");
|
|
11397
|
+
}
|
|
11398
|
+
const transport = config.transport ?? buildDefaultTransport();
|
|
11399
|
+
const supportsAsync = typeof transport.submit === "function" && typeof transport.poll === "function";
|
|
11400
|
+
const apis = createApis(wc);
|
|
11401
|
+
const inputsTransformationConfig = config.inputsTransformation;
|
|
11402
|
+
const catalogs = createCatalogs(transport, config.catalogs);
|
|
11403
|
+
const driveConfig = config.drive;
|
|
11404
|
+
const driveApiUrl = config.apiUrl;
|
|
11405
|
+
if (driveConfig && !(authedFetch && driveApiUrl)) {
|
|
11406
|
+
throw new Error("createClient `drive` requires `apiUrl` plus `fetch` or `apiKey` \u2014 Drive is a REST surface, not served by a custom transport.");
|
|
11407
|
+
}
|
|
11408
|
+
const driveClient = driveConfig && authedFetch && driveApiUrl ? createDriveClient(authedFetch, driveApiUrl, driveConfig.folder) : null;
|
|
11409
|
+
function toApiError2(err, signal) {
|
|
11410
|
+
if (err instanceof ApiError) return err;
|
|
11411
|
+
if (err instanceof DOMException && err.name === "AbortError") {
|
|
11412
|
+
if (signal?.aborted) {
|
|
11413
|
+
return new ApiError("Operation aborted", { status: 499, code: "aborted" });
|
|
11414
|
+
}
|
|
11415
|
+
throw err;
|
|
11416
|
+
}
|
|
11417
|
+
return new ApiError(err instanceof Error ? err.message : String(err), {
|
|
11418
|
+
status: 502,
|
|
11419
|
+
code: "generation_failed"
|
|
11420
|
+
});
|
|
11342
11421
|
}
|
|
11343
|
-
|
|
11344
|
-
return
|
|
11422
|
+
function unsupportedTransport(capability) {
|
|
11423
|
+
return new ApiError(`Transport does not support ${capability} (execute-only transport)`, {
|
|
11424
|
+
status: 400,
|
|
11425
|
+
code: "unsupported_transport"
|
|
11426
|
+
});
|
|
11345
11427
|
}
|
|
11346
|
-
|
|
11347
|
-
return
|
|
11428
|
+
function asCompleted(handle, res) {
|
|
11429
|
+
return toCompletedStatus(handle, res.result, res.raw ?? res.result, res.usage);
|
|
11348
11430
|
}
|
|
11349
|
-
|
|
11350
|
-
return
|
|
11431
|
+
function pollJob(handle, poll, onProgress) {
|
|
11432
|
+
if (!transport.poll) return Promise.reject(unsupportedTransport("polling"));
|
|
11433
|
+
return transport.poll(handle, { ...poll, onProgress });
|
|
11351
11434
|
}
|
|
11352
|
-
|
|
11353
|
-
|
|
11435
|
+
function assertAsyncLifecycle() {
|
|
11436
|
+
if (!transport.submit) throw unsupportedTransport("submit");
|
|
11437
|
+
if (!transport.poll) throw unsupportedTransport("polling");
|
|
11354
11438
|
}
|
|
11355
|
-
|
|
11356
|
-
|
|
11357
|
-
|
|
11358
|
-
|
|
11359
|
-
|
|
11360
|
-
|
|
11361
|
-
|
|
11439
|
+
function assertMediaModel(model) {
|
|
11440
|
+
if (model.mode === "text") {
|
|
11441
|
+
throw new ApiError(`${model.name} is a text model \u2014 use generateText() instead.`, {
|
|
11442
|
+
status: 400,
|
|
11443
|
+
code: "wrong_model_mode"
|
|
11444
|
+
});
|
|
11445
|
+
}
|
|
11362
11446
|
}
|
|
11363
|
-
|
|
11364
|
-
if (
|
|
11365
|
-
|
|
11366
|
-
if (!r) return entry;
|
|
11367
|
-
if (r.kind === "disabled") {
|
|
11368
|
-
const options2 = entry.options.map((opt) => ({ ...opt, disabled: true, disabledReason: r.reason }));
|
|
11369
|
-
return { ...entry, options: options2, disabled: true, disabledReason: r.reason };
|
|
11447
|
+
async function submitJob(workflow, payload, signal) {
|
|
11448
|
+
if (signal?.aborted) {
|
|
11449
|
+
throw new ApiError("Operation aborted", { status: 499, code: "aborted" });
|
|
11370
11450
|
}
|
|
11371
|
-
|
|
11372
|
-
const
|
|
11373
|
-
|
|
11374
|
-
|
|
11375
|
-
|
|
11451
|
+
if (!transport.submit) throw unsupportedTransport("submit");
|
|
11452
|
+
const id = await transport.submit({ workflow, payload, signal });
|
|
11453
|
+
if (!id) {
|
|
11454
|
+
throw new ApiError("No task id in response", { status: 502, code: "invalid_response" });
|
|
11455
|
+
}
|
|
11456
|
+
return id;
|
|
11376
11457
|
}
|
|
11377
|
-
|
|
11378
|
-
|
|
11379
|
-
|
|
11380
|
-
|
|
11381
|
-
|
|
11382
|
-
|
|
11383
|
-
|
|
11384
|
-
|
|
11385
|
-
|
|
11458
|
+
async function executeModel(model, workflow, payload, options) {
|
|
11459
|
+
const signal = options?.signal;
|
|
11460
|
+
try {
|
|
11461
|
+
if (model.syncExecute || !supportsAsync) {
|
|
11462
|
+
const res2 = await transport.execute({ workflow, payload, signal });
|
|
11463
|
+
return toCompletedStatus(
|
|
11464
|
+
{ workflow, id: "" },
|
|
11465
|
+
extractSyncResult(res2.result) ?? res2.result,
|
|
11466
|
+
res2.raw ?? res2.result,
|
|
11467
|
+
res2.usage
|
|
11468
|
+
);
|
|
11469
|
+
}
|
|
11470
|
+
const id = await submitJob(workflow, payload, signal);
|
|
11471
|
+
const res = await pollJob({ workflow, id }, resolvePollOptions(model, options));
|
|
11472
|
+
return asCompleted({ workflow, id }, res);
|
|
11473
|
+
} catch (err) {
|
|
11474
|
+
throw toApiError2(err, signal);
|
|
11386
11475
|
}
|
|
11387
|
-
const allowed = new Set(r.allowed.map(String));
|
|
11388
|
-
const options = entry.options.map(
|
|
11389
|
-
(opt) => allowed.has(String(opt.id)) ? opt : { ...opt, disabled: true, disabledReason: r.reason }
|
|
11390
|
-
);
|
|
11391
|
-
return { ...entry, options };
|
|
11392
11476
|
}
|
|
11393
|
-
|
|
11394
|
-
|
|
11395
|
-
|
|
11396
|
-
|
|
11397
|
-
|
|
11398
|
-
|
|
11399
|
-
|
|
11400
|
-
|
|
11401
|
-
|
|
11402
|
-
|
|
11403
|
-
|
|
11404
|
-
|
|
11405
|
-
|
|
11406
|
-
|
|
11407
|
-
this.features = def.features;
|
|
11408
|
-
this.badges = def.badge ?? [];
|
|
11409
|
-
this.release = def.release ?? "production";
|
|
11410
|
-
this.provider = {
|
|
11411
|
-
id: def.provider,
|
|
11412
|
-
name: def.providerName,
|
|
11413
|
-
color: def.providerColor,
|
|
11414
|
-
label: def.providerLabel
|
|
11477
|
+
function buildDrivePayloadOptions(model, params2, options) {
|
|
11478
|
+
const explicit = options?.drive;
|
|
11479
|
+
if (!driveConfig && !explicit) return void 0;
|
|
11480
|
+
const attributes = buildGenerationAttributes({
|
|
11481
|
+
modelId: model.id,
|
|
11482
|
+
params: params2,
|
|
11483
|
+
app: options?.app
|
|
11484
|
+
});
|
|
11485
|
+
const folderPath = options?.folder?.name ?? driveConfig?.folder;
|
|
11486
|
+
return {
|
|
11487
|
+
name: explicit?.name ?? buildFilename(params2.prompt, model.mode),
|
|
11488
|
+
// SDK-assembled attributes are the baseline; explicit attributes win per-key.
|
|
11489
|
+
attributes: { ...attributes, ...explicit?.attributes ?? {} },
|
|
11490
|
+
folder: explicit?.folder ?? (folderPath ? { path: folderPath } : void 0)
|
|
11415
11491
|
};
|
|
11416
|
-
this.addedAt = def.addedAt ?? null;
|
|
11417
|
-
}
|
|
11418
|
-
};
|
|
11419
|
-
var ModelDescriptorImpl = class {
|
|
11420
|
-
id;
|
|
11421
|
-
name;
|
|
11422
|
-
api;
|
|
11423
|
-
def;
|
|
11424
|
-
_params;
|
|
11425
|
-
_meta;
|
|
11426
|
-
constructor(def) {
|
|
11427
|
-
this.id = def.id;
|
|
11428
|
-
this.name = def.name;
|
|
11429
|
-
this.api = { workflow: def.workflow, editWorkflow: def.editWorkflow };
|
|
11430
|
-
this.def = def;
|
|
11431
11492
|
}
|
|
11432
|
-
|
|
11433
|
-
|
|
11434
|
-
|
|
11435
|
-
|
|
11436
|
-
|
|
11437
|
-
|
|
11438
|
-
|
|
11439
|
-
|
|
11493
|
+
function injectPayloadOptions(payload, drive, inputsTransformation) {
|
|
11494
|
+
const record = payload;
|
|
11495
|
+
const existing = record.options ?? {};
|
|
11496
|
+
return {
|
|
11497
|
+
...record,
|
|
11498
|
+
options: {
|
|
11499
|
+
...existing,
|
|
11500
|
+
inputs_transformation: {
|
|
11501
|
+
downscale_oversized_images: inputsTransformation?.downscaleOversizedImages ?? inputsTransformationConfig?.downscaleOversizedImages ?? false
|
|
11502
|
+
},
|
|
11503
|
+
...drive ? { drive } : {}
|
|
11504
|
+
}
|
|
11505
|
+
};
|
|
11440
11506
|
}
|
|
11441
|
-
|
|
11442
|
-
|
|
11443
|
-
|
|
11444
|
-
}
|
|
11507
|
+
async function resolveJobHandle(model, generationId, signal) {
|
|
11508
|
+
const primary = { workflow: model.workflow, id: generationId };
|
|
11509
|
+
if (!model.editWorkflow || !transport.status) return primary;
|
|
11445
11510
|
try {
|
|
11446
|
-
|
|
11447
|
-
return
|
|
11511
|
+
await transport.status(primary, signal);
|
|
11512
|
+
return primary;
|
|
11448
11513
|
} catch (err) {
|
|
11449
|
-
|
|
11450
|
-
|
|
11451
|
-
|
|
11452
|
-
|
|
11453
|
-
return this._meta ??= new ModelMetaImpl(this.def);
|
|
11454
|
-
}
|
|
11455
|
-
getCreditsInfo(ctx) {
|
|
11456
|
-
if (this.def.modelId) {
|
|
11457
|
-
const byModelId = getCreditsForModel(this.def.modelId, ctx);
|
|
11458
|
-
if (byModelId) return byModelId;
|
|
11514
|
+
if (err instanceof ApiError && err.status === 404) {
|
|
11515
|
+
return { workflow: model.editWorkflow, id: generationId };
|
|
11516
|
+
}
|
|
11517
|
+
throw err;
|
|
11459
11518
|
}
|
|
11460
|
-
return getCreditsForModel(this.def.id, ctx);
|
|
11461
11519
|
}
|
|
11462
|
-
|
|
11463
|
-
|
|
11464
|
-
|
|
11465
|
-
|
|
11466
|
-
|
|
11467
|
-
|
|
11468
|
-
|
|
11469
|
-
|
|
11470
|
-
|
|
11471
|
-
|
|
11472
|
-
|
|
11473
|
-
|
|
11474
|
-
|
|
11475
|
-
|
|
11476
|
-
|
|
11477
|
-
|
|
11478
|
-
|
|
11479
|
-
|
|
11480
|
-
|
|
11481
|
-
|
|
11482
|
-
|
|
11483
|
-
|
|
11484
|
-
|
|
11520
|
+
return {
|
|
11521
|
+
// ── Simple path ──────────────────────────────────────────────────
|
|
11522
|
+
/**
|
|
11523
|
+
* Generate content using a model.
|
|
11524
|
+
*
|
|
11525
|
+
* Validates input, builds the vendor payload, picks the right workflow,
|
|
11526
|
+
* submits the job, polls to completion, and returns the parsed result
|
|
11527
|
+
* (`items[]` with per-item URLs and promoted vendor metadata).
|
|
11528
|
+
* If drive options are provided (or DriveConfig is set), the backend
|
|
11529
|
+
* saves the result to Picsart Drive.
|
|
11530
|
+
*/
|
|
11531
|
+
async generate(model, params2, options) {
|
|
11532
|
+
const resolved = resolveModel(model);
|
|
11533
|
+
assertMediaModel(resolved);
|
|
11534
|
+
const { workflow, payload, contract } = prepareRequest(resolved, params2);
|
|
11535
|
+
const drive = buildDrivePayloadOptions(resolved, params2, options);
|
|
11536
|
+
const finalPayload = injectPayloadOptions(payload, drive, options?.inputsTransformation);
|
|
11537
|
+
const completed = await executeModel(resolved, workflow, finalPayload, options);
|
|
11538
|
+
return parseResult(completed, resolved, contract);
|
|
11539
|
+
},
|
|
11540
|
+
/**
|
|
11541
|
+
* Generate text using an LLM model (Claude, Gemini, OpenAI).
|
|
11542
|
+
*
|
|
11543
|
+
* Validates input, builds the vendor payload, runs the workflow, and
|
|
11544
|
+
* returns the generated text plus the raw response. Single-shot only —
|
|
11545
|
+
* pass text and optional image/video, get text back. Text results are not
|
|
11546
|
+
* saved to Drive.
|
|
11547
|
+
*/
|
|
11548
|
+
async generateText(model, params2, options) {
|
|
11549
|
+
const resolved = resolveModel(model);
|
|
11550
|
+
if (resolved.mode !== "text") {
|
|
11551
|
+
throw new ApiError(`${resolved.name} is not a text model \u2014 use generate() instead.`, {
|
|
11552
|
+
status: 400,
|
|
11553
|
+
code: "wrong_model_mode"
|
|
11554
|
+
});
|
|
11555
|
+
}
|
|
11556
|
+
const { workflow, payload } = prepareRequest(resolved, params2);
|
|
11557
|
+
const completed = await executeModel(resolved, workflow, payload, options);
|
|
11558
|
+
return parseTextResult(completed, resolved);
|
|
11559
|
+
},
|
|
11560
|
+
/**
|
|
11561
|
+
* Get exact credit cost for a model with specific parameters.
|
|
11562
|
+
* Calls the backend /options endpoint for real-time pricing.
|
|
11563
|
+
* Returns null if pricing is unavailable.
|
|
11564
|
+
*/
|
|
11565
|
+
async getCredits(model, params2) {
|
|
11566
|
+
if (!transport.options) return null;
|
|
11567
|
+
const resolved = resolveModel(model);
|
|
11568
|
+
const { workflow, payload } = prepareRequest(resolved, params2);
|
|
11569
|
+
return await transport.options(workflow, payload) ?? null;
|
|
11570
|
+
},
|
|
11571
|
+
/** Build the vendor-specific payload for a model without submitting. */
|
|
11572
|
+
buildPayload(model, params2) {
|
|
11573
|
+
const resolved = resolveModel(model);
|
|
11574
|
+
const { payload } = prepareRequest(resolved, params2);
|
|
11575
|
+
return payload;
|
|
11576
|
+
},
|
|
11577
|
+
// ── Advanced lifecycle ────────────────────────────────────────────
|
|
11578
|
+
/** Submit a generation job and get its generation id back. Media models
|
|
11579
|
+
* only — text models have no async lifecycle (`result()`/`subscribe()`
|
|
11580
|
+
* reject them). Pass the id to `result(model, id)` / `subscribe(model, id)`. */
|
|
11581
|
+
async submit(model, params2, options) {
|
|
11582
|
+
const resolved = resolveModel(model);
|
|
11583
|
+
assertMediaModel(resolved);
|
|
11584
|
+
assertAsyncLifecycle();
|
|
11585
|
+
const { workflow, payload } = prepareRequest(resolved, params2);
|
|
11586
|
+
const drive = buildDrivePayloadOptions(resolved, params2, options);
|
|
11587
|
+
const finalPayload = injectPayloadOptions(payload, drive, options?.inputsTransformation);
|
|
11588
|
+
try {
|
|
11589
|
+
return await submitJob(workflow, finalPayload, options?.signal);
|
|
11590
|
+
} catch (err) {
|
|
11591
|
+
throw toApiError2(err, options?.signal);
|
|
11592
|
+
}
|
|
11593
|
+
},
|
|
11594
|
+
/** Poll a submitted job until it completes and return the parsed result. */
|
|
11595
|
+
async result(model, generationId, options) {
|
|
11596
|
+
const resolved = resolveModel(model);
|
|
11597
|
+
assertMediaModel(resolved);
|
|
11598
|
+
const contract = getModelContract(resolved.id);
|
|
11599
|
+
assertAsyncLifecycle();
|
|
11600
|
+
const handle = await resolveJobHandle(resolved, generationId, options?.signal);
|
|
11601
|
+
let completed;
|
|
11602
|
+
try {
|
|
11603
|
+
completed = asCompleted(handle, await pollJob(handle, resolvePollOptions(resolved, options)));
|
|
11604
|
+
} catch (err) {
|
|
11605
|
+
throw toApiError2(err, options?.signal);
|
|
11606
|
+
}
|
|
11607
|
+
return parseResult(completed, resolved, contract);
|
|
11608
|
+
},
|
|
11609
|
+
/**
|
|
11610
|
+
* Subscribe to live updates for a submitted job. Yields one
|
|
11611
|
+
* {@link GenerationEvent} per poll: `generation.progress` while running,
|
|
11612
|
+
* then a single terminal `generation.completed` (with the parsed result)
|
|
11613
|
+
* or `generation.failed` (with the {@link ApiError} `result()` would have
|
|
11614
|
+
* thrown). Failures arrive as events, not exceptions.
|
|
11615
|
+
*
|
|
11616
|
+
* ```ts
|
|
11617
|
+
* const id = await ai.submit(Models.Flux2Pro, { prompt: 'a cat' });
|
|
11618
|
+
* for await (const e of ai.subscribe(Models.Flux2Pro, id)) {
|
|
11619
|
+
* if (e.type === 'generation.progress') console.log(e.progress?.percent);
|
|
11620
|
+
* if (e.type === 'generation.completed') console.log(e.result.url);
|
|
11621
|
+
* if (e.type === 'generation.failed') console.error(e.error.message);
|
|
11622
|
+
* }
|
|
11623
|
+
* ```
|
|
11624
|
+
*/
|
|
11625
|
+
subscribe(model, generationId, options) {
|
|
11626
|
+
const resolved = resolveModel(model);
|
|
11627
|
+
assertMediaModel(resolved);
|
|
11628
|
+
assertAsyncLifecycle();
|
|
11629
|
+
const contract = getModelContract(resolved.id);
|
|
11630
|
+
return (async function* () {
|
|
11631
|
+
let handle;
|
|
11632
|
+
try {
|
|
11633
|
+
handle = await resolveJobHandle(resolved, generationId, options?.signal);
|
|
11634
|
+
} catch (err) {
|
|
11635
|
+
yield { type: "generation.failed", error: toApiError2(err, options?.signal) };
|
|
11636
|
+
return;
|
|
11637
|
+
}
|
|
11638
|
+
const poll = new AbortController();
|
|
11639
|
+
if (options?.signal) {
|
|
11640
|
+
if (options.signal.aborted) poll.abort();
|
|
11641
|
+
else options.signal.addEventListener("abort", () => poll.abort(), { once: true });
|
|
11642
|
+
}
|
|
11643
|
+
const queue = [];
|
|
11644
|
+
let wake;
|
|
11645
|
+
let waker = new Promise((resolve) => {
|
|
11646
|
+
wake = () => resolve(null);
|
|
11647
|
+
});
|
|
11648
|
+
const done = pollJob(
|
|
11649
|
+
handle,
|
|
11650
|
+
{ ...resolvePollOptions(resolved, options), signal: poll.signal },
|
|
11651
|
+
(p2) => {
|
|
11652
|
+
queue.push(p2);
|
|
11653
|
+
wake();
|
|
11654
|
+
}
|
|
11655
|
+
).then(
|
|
11656
|
+
(res) => ({ ok: true, res }),
|
|
11657
|
+
(err) => ({ ok: false, err })
|
|
11658
|
+
);
|
|
11659
|
+
try {
|
|
11660
|
+
for (; ; ) {
|
|
11661
|
+
while (queue.length) yield { type: "generation.progress", progress: queue.shift() };
|
|
11662
|
+
const raced = await Promise.race([done, waker]);
|
|
11663
|
+
if (raced === null) {
|
|
11664
|
+
waker = new Promise((resolve) => {
|
|
11665
|
+
wake = () => resolve(null);
|
|
11666
|
+
});
|
|
11667
|
+
continue;
|
|
11668
|
+
}
|
|
11669
|
+
while (queue.length) yield { type: "generation.progress", progress: queue.shift() };
|
|
11670
|
+
if (!raced.ok) {
|
|
11671
|
+
yield { type: "generation.failed", error: toApiError2(raced.err, options?.signal) };
|
|
11672
|
+
return;
|
|
11673
|
+
}
|
|
11674
|
+
const completed = asCompleted(handle, raced.res);
|
|
11675
|
+
try {
|
|
11676
|
+
yield { type: "generation.completed", result: parseResult(completed, resolved, contract) };
|
|
11677
|
+
} catch (err) {
|
|
11678
|
+
yield { type: "generation.failed", error: toApiError2(err, options?.signal) };
|
|
11679
|
+
}
|
|
11680
|
+
return;
|
|
11681
|
+
}
|
|
11682
|
+
} finally {
|
|
11683
|
+
poll.abort();
|
|
11684
|
+
}
|
|
11685
|
+
})();
|
|
11686
|
+
},
|
|
11687
|
+
// ── apis (direct, low-level API access) ───────────────────────────
|
|
11688
|
+
/** Direct, low-level access to the Picsart model APIs. See `./apis.ts`. */
|
|
11689
|
+
apis,
|
|
11690
|
+
// ── Catalogs (voices / avatars) ──────────────────────────────────
|
|
11691
|
+
/** Voice/avatar catalogs — fetch, ttl-cache, hydrate model params. See `./catalogs.ts`. */
|
|
11692
|
+
catalogs,
|
|
11693
|
+
// ── Drive ────────────────────────────────────────────────────────
|
|
11694
|
+
/** Drive operations. Only available when drive config is provided. */
|
|
11695
|
+
drive: driveClient ?? void 0
|
|
11696
|
+
};
|
|
11485
11697
|
}
|
|
11486
|
-
var Model = _model;
|
|
11487
|
-
var catalog = {
|
|
11488
|
-
all: _all,
|
|
11489
|
-
find: _find,
|
|
11490
|
-
search: _search,
|
|
11491
|
-
pricing: {
|
|
11492
|
-
configure: configurePricing,
|
|
11493
|
-
load: loadPricing,
|
|
11494
|
-
isLoaded: isPricingLoaded
|
|
11495
|
-
}
|
|
11496
|
-
};
|
|
11497
11698
|
|
|
11498
11699
|
// src/generated/model-constants.ts
|
|
11499
11700
|
var AsyncFlashV1 = "async-flash-v1";
|
|
@@ -11611,7 +11812,6 @@ var LumaUni1Max = "luma-uni-1-max";
|
|
|
11611
11812
|
var Lyria3Clip = "lyria-3-clip";
|
|
11612
11813
|
var Lyria3Pro = "lyria-3-pro";
|
|
11613
11814
|
var Lyria35 = "lyria-3.5";
|
|
11614
|
-
var Minimax02Hd = "minimax-02-hd";
|
|
11615
11815
|
var MinimaxH3 = "minimax-h3";
|
|
11616
11816
|
var MinimaxH3Max = "minimax-h3-max";
|
|
11617
11817
|
var MinimaxH3MaxCameraControls = "minimax-h3-max-camera-controls";
|
|
@@ -11837,7 +12037,6 @@ var Models = {
|
|
|
11837
12037
|
Lyria3Clip,
|
|
11838
12038
|
Lyria3Pro,
|
|
11839
12039
|
Lyria35,
|
|
11840
|
-
Minimax02Hd,
|
|
11841
12040
|
MinimaxH3,
|
|
11842
12041
|
MinimaxH3Max,
|
|
11843
12042
|
MinimaxH3MaxCameraControls,
|
|
@@ -11946,39 +12145,437 @@ var Models = {
|
|
|
11946
12145
|
Wan27T2v,
|
|
11947
12146
|
Wan27VideoEdit,
|
|
11948
12147
|
Wan30Video,
|
|
11949
|
-
Wan30VideoPrime
|
|
11950
|
-
|
|
11951
|
-
|
|
11952
|
-
|
|
11953
|
-
|
|
11954
|
-
|
|
11955
|
-
|
|
12148
|
+
Wan30VideoPrime
|
|
12149
|
+
};
|
|
12150
|
+
|
|
12151
|
+
// src/core/constraints.ts
|
|
12152
|
+
function normalize(r) {
|
|
12153
|
+
if ("disabled" in r) return { kind: "disabled", reason: r.reason };
|
|
12154
|
+
return { kind: "allowed", allowed: r.allowed, reason: r.reason };
|
|
12155
|
+
}
|
|
12156
|
+
function matchOperator(op, actual) {
|
|
12157
|
+
if ("exists" in op) {
|
|
12158
|
+
const has = actual != null && (!Array.isArray(actual) || actual.length > 0) && (typeof actual !== "string" || actual.length > 0);
|
|
12159
|
+
return op.exists ? has : !has;
|
|
12160
|
+
}
|
|
12161
|
+
if ("is" in op) return actual === op.is;
|
|
12162
|
+
return false;
|
|
12163
|
+
}
|
|
12164
|
+
function matchCondition(when, values) {
|
|
12165
|
+
return Object.entries(when).every(
|
|
12166
|
+
([key, op]) => matchOperator(op, values[key])
|
|
12167
|
+
);
|
|
12168
|
+
}
|
|
12169
|
+
function merge(prev, next) {
|
|
12170
|
+
if (!prev) return next;
|
|
12171
|
+
if (prev.kind === "disabled" || next.kind === "disabled") {
|
|
12172
|
+
return { kind: "disabled", reason: next.kind === "disabled" ? next.reason : prev.kind === "disabled" ? prev.reason : void 0 };
|
|
12173
|
+
}
|
|
12174
|
+
const allowed = new Set(next.allowed.map(String));
|
|
12175
|
+
return {
|
|
12176
|
+
kind: "allowed",
|
|
12177
|
+
allowed: prev.allowed.filter((o) => allowed.has(String(o))),
|
|
12178
|
+
reason: next.reason ?? prev.reason
|
|
12179
|
+
};
|
|
12180
|
+
}
|
|
12181
|
+
function evaluateConstraints(constraints, values) {
|
|
12182
|
+
const effects = /* @__PURE__ */ new Map();
|
|
12183
|
+
if (!constraints?.length) return effects;
|
|
12184
|
+
for (const rule of constraints) {
|
|
12185
|
+
if (!matchCondition(rule.when, values)) continue;
|
|
12186
|
+
for (const [key, restriction] of Object.entries(rule.then)) {
|
|
12187
|
+
effects.set(key, merge(effects.get(key), normalize(restriction)));
|
|
12188
|
+
}
|
|
12189
|
+
}
|
|
12190
|
+
return effects;
|
|
12191
|
+
}
|
|
12192
|
+
|
|
12193
|
+
// src/core/descriptors/pricing.ts
|
|
12194
|
+
var import_pa_model_pricing_sdk = __toESM(require_build(), 1);
|
|
12195
|
+
var _client = null;
|
|
12196
|
+
var _byModel = null;
|
|
12197
|
+
var _loadPromise = null;
|
|
12198
|
+
function configurePricing(options) {
|
|
12199
|
+
_client = new import_pa_model_pricing_sdk.ModelPricingClient(options);
|
|
12200
|
+
_byModel = null;
|
|
12201
|
+
_loadPromise = null;
|
|
12202
|
+
}
|
|
12203
|
+
function loadPricing() {
|
|
12204
|
+
if (_byModel) return Promise.resolve();
|
|
12205
|
+
if (!_client) {
|
|
12206
|
+
return Promise.reject(new Error(
|
|
12207
|
+
"loadPricing(): not configured. Call catalog.pricing.configure({ baseUrl, fetch }) first."
|
|
12208
|
+
));
|
|
12209
|
+
}
|
|
12210
|
+
if (!_loadPromise) {
|
|
12211
|
+
const client = _client;
|
|
12212
|
+
_loadPromise = client.init().then(() => {
|
|
12213
|
+
const byModel = /* @__PURE__ */ new Map();
|
|
12214
|
+
for (const entry of client.getModelPricing()) {
|
|
12215
|
+
const id = entry.metadata.modelId;
|
|
12216
|
+
const list = byModel.get(id);
|
|
12217
|
+
if (list) list.push(entry);
|
|
12218
|
+
else byModel.set(id, [entry]);
|
|
12219
|
+
}
|
|
12220
|
+
_byModel = byModel;
|
|
12221
|
+
}).catch((err) => {
|
|
12222
|
+
_loadPromise = null;
|
|
12223
|
+
throw err;
|
|
12224
|
+
});
|
|
12225
|
+
}
|
|
12226
|
+
return _loadPromise;
|
|
12227
|
+
}
|
|
12228
|
+
function isPricingLoaded() {
|
|
12229
|
+
return _byModel !== null;
|
|
12230
|
+
}
|
|
12231
|
+
function getCreditsForModel(modelId, ctx) {
|
|
12232
|
+
if (!_byModel) return null;
|
|
12233
|
+
let entries = _byModel.get(modelId);
|
|
12234
|
+
if (!entries || entries.length === 0) return null;
|
|
12235
|
+
if (ctx) {
|
|
12236
|
+
entries = entries.filter((e) => {
|
|
12237
|
+
if (ctx.generateAudio !== void 0 && e.metadata.audio !== ctx.generateAudio) return false;
|
|
12238
|
+
if (ctx.resolution !== void 0 && e.metadata.quality !== ctx.resolution) return false;
|
|
11956
12239
|
return true;
|
|
11957
12240
|
});
|
|
11958
|
-
|
|
11959
|
-
|
|
11960
|
-
|
|
12241
|
+
if (entries.length === 0) return null;
|
|
12242
|
+
}
|
|
12243
|
+
let min = Infinity;
|
|
12244
|
+
let max = -Infinity;
|
|
12245
|
+
let unit = entries[0].unit;
|
|
12246
|
+
for (const e of entries) {
|
|
12247
|
+
if (e.credits < min) min = e.credits;
|
|
12248
|
+
if (e.credits > max) max = e.credits;
|
|
12249
|
+
if (e.unit !== unit) unit = void 0;
|
|
12250
|
+
}
|
|
12251
|
+
const tiers = entries.map((e) => ({
|
|
12252
|
+
credits: e.credits,
|
|
12253
|
+
unit: e.unit,
|
|
12254
|
+
quality: e.metadata.quality || void 0,
|
|
12255
|
+
audio: e.metadata.audio,
|
|
12256
|
+
useCase: e.metadata.useCase
|
|
12257
|
+
}));
|
|
12258
|
+
return unit ? { min, max, unit, tiers } : { min, max, tiers };
|
|
12259
|
+
}
|
|
12260
|
+
|
|
12261
|
+
// src/core/descriptors/model-accessor.ts
|
|
12262
|
+
function withHydration(entry, flat) {
|
|
12263
|
+
if (entry.descriptor.kind !== "catalog") return flat;
|
|
12264
|
+
const hydrated = getHydratedCatalog(entry.descriptor.source);
|
|
12265
|
+
if (!hydrated) return flat;
|
|
12266
|
+
return { ...flat, catalogOptions: hydrated.catalogOptions };
|
|
12267
|
+
}
|
|
12268
|
+
var ModelParamsAccessorImpl = class {
|
|
12269
|
+
def;
|
|
12270
|
+
constructor(def) {
|
|
12271
|
+
this.def = def;
|
|
12272
|
+
}
|
|
12273
|
+
param(key) {
|
|
12274
|
+
const entry = this.def.paramConfig[key];
|
|
12275
|
+
if (!entry) return void 0;
|
|
12276
|
+
const { descriptor, ...meta } = entry;
|
|
12277
|
+
return withHydration(entry, { ...meta, ...descriptor });
|
|
12278
|
+
}
|
|
12279
|
+
hasParam(key) {
|
|
12280
|
+
return key in this.def.paramConfig;
|
|
12281
|
+
}
|
|
12282
|
+
all() {
|
|
12283
|
+
return Object.entries(this.def.paramConfig).map(
|
|
12284
|
+
([key, entry]) => {
|
|
12285
|
+
const { descriptor, ...meta } = entry;
|
|
12286
|
+
return withHydration(entry, { key, ...meta, ...descriptor });
|
|
12287
|
+
}
|
|
12288
|
+
);
|
|
12289
|
+
}
|
|
12290
|
+
// Kind-narrowed accessors
|
|
12291
|
+
enum(key) {
|
|
12292
|
+
return this.narrow(key, "enum");
|
|
12293
|
+
}
|
|
12294
|
+
catalog(key) {
|
|
12295
|
+
return this.narrow(key, "catalog");
|
|
12296
|
+
}
|
|
12297
|
+
range(key) {
|
|
12298
|
+
return this.narrow(key, "range");
|
|
12299
|
+
}
|
|
12300
|
+
boolean(key) {
|
|
12301
|
+
return this.narrow(key, "boolean");
|
|
12302
|
+
}
|
|
12303
|
+
text(key) {
|
|
12304
|
+
return this.narrow(key, "text");
|
|
12305
|
+
}
|
|
12306
|
+
file(key) {
|
|
12307
|
+
return this.narrow(key, "file");
|
|
12308
|
+
}
|
|
12309
|
+
// Well-known shorthands
|
|
12310
|
+
prompt() {
|
|
12311
|
+
return this.narrow("prompt", "text");
|
|
12312
|
+
}
|
|
12313
|
+
aspectRatio() {
|
|
12314
|
+
return this.narrow("aspectRatio", "enum");
|
|
12315
|
+
}
|
|
12316
|
+
/** Enum on fixed-option models, range where the vendor accepts every value
|
|
12317
|
+
* in a span — callers narrow on `.kind`. */
|
|
12318
|
+
duration() {
|
|
12319
|
+
const entry = this.param("duration");
|
|
12320
|
+
if (!entry || entry.kind !== "enum" && entry.kind !== "range") return void 0;
|
|
12321
|
+
return entry;
|
|
12322
|
+
}
|
|
12323
|
+
resolution() {
|
|
12324
|
+
return this.narrow("resolution", "enum");
|
|
12325
|
+
}
|
|
12326
|
+
generateAudio() {
|
|
12327
|
+
return this.narrow("generateAudio", "boolean");
|
|
12328
|
+
}
|
|
12329
|
+
startFrame() {
|
|
12330
|
+
return this.narrow("startFrame", "file");
|
|
12331
|
+
}
|
|
12332
|
+
endFrame() {
|
|
12333
|
+
return this.narrow("endFrame", "file");
|
|
12334
|
+
}
|
|
12335
|
+
// Absorbed from Models namespace
|
|
12336
|
+
hasFileInput() {
|
|
12337
|
+
return Object.values(this.def.paramConfig).some((e) => e.descriptor.kind === "file");
|
|
12338
|
+
}
|
|
12339
|
+
getDefault(key) {
|
|
12340
|
+
const entry = this.def.paramConfig[key];
|
|
12341
|
+
if (!entry) return void 0;
|
|
12342
|
+
const d = entry.descriptor;
|
|
12343
|
+
return "default" in d ? d.default : void 0;
|
|
12344
|
+
}
|
|
12345
|
+
getDefaults() {
|
|
12346
|
+
return extractDefaults(this.def.paramConfig);
|
|
12347
|
+
}
|
|
12348
|
+
toSchema() {
|
|
12349
|
+
return descriptorsToSchema(this.def.paramConfig);
|
|
12350
|
+
}
|
|
12351
|
+
transferValues(prev) {
|
|
12352
|
+
return transferValues(this.def.paramConfig, prev);
|
|
12353
|
+
}
|
|
12354
|
+
narrow(key, kind) {
|
|
12355
|
+
const entry = this.param(key);
|
|
12356
|
+
if (!entry || entry.kind !== kind) return void 0;
|
|
12357
|
+
return entry;
|
|
12358
|
+
}
|
|
12359
|
+
};
|
|
12360
|
+
var ConstrainedParamsAccessor = class {
|
|
12361
|
+
inner;
|
|
12362
|
+
effects;
|
|
12363
|
+
constructor(inner, effects) {
|
|
12364
|
+
this.inner = inner;
|
|
12365
|
+
this.effects = effects;
|
|
12366
|
+
}
|
|
12367
|
+
// ── Decorated accessors ──────────────────────────────────────────
|
|
12368
|
+
enum(key) {
|
|
12369
|
+
return this.applyEnum(key, this.inner.enum(key));
|
|
12370
|
+
}
|
|
12371
|
+
catalog(key) {
|
|
12372
|
+
return this.applyEntry(key, this.inner.catalog(key));
|
|
12373
|
+
}
|
|
12374
|
+
range(key) {
|
|
12375
|
+
return this.applyEntry(key, this.inner.range(key));
|
|
12376
|
+
}
|
|
12377
|
+
boolean(key) {
|
|
12378
|
+
return this.applyEntry(key, this.inner.boolean(key));
|
|
12379
|
+
}
|
|
12380
|
+
text(key) {
|
|
12381
|
+
return this.applyEntry(key, this.inner.text(key));
|
|
12382
|
+
}
|
|
12383
|
+
file(key) {
|
|
12384
|
+
return this.applyEntry(key, this.inner.file(key));
|
|
12385
|
+
}
|
|
12386
|
+
prompt() {
|
|
12387
|
+
return this.applyEntry("prompt", this.inner.prompt());
|
|
12388
|
+
}
|
|
12389
|
+
aspectRatio() {
|
|
12390
|
+
return this.applyEnum("aspectRatio", this.inner.aspectRatio());
|
|
12391
|
+
}
|
|
12392
|
+
duration() {
|
|
12393
|
+
const entry = this.inner.duration();
|
|
12394
|
+
return entry?.kind === "enum" ? this.applyEnum("duration", entry) : this.applyEntry("duration", entry);
|
|
12395
|
+
}
|
|
12396
|
+
resolution() {
|
|
12397
|
+
return this.applyEnum("resolution", this.inner.resolution());
|
|
12398
|
+
}
|
|
12399
|
+
generateAudio() {
|
|
12400
|
+
return this.applyEntry("generateAudio", this.inner.generateAudio());
|
|
12401
|
+
}
|
|
12402
|
+
startFrame() {
|
|
12403
|
+
return this.applyEntry("startFrame", this.inner.startFrame());
|
|
12404
|
+
}
|
|
12405
|
+
endFrame() {
|
|
12406
|
+
return this.applyEntry("endFrame", this.inner.endFrame());
|
|
12407
|
+
}
|
|
12408
|
+
all() {
|
|
12409
|
+
return this.inner.all().map((e) => {
|
|
12410
|
+
const r = this.effects.get(e.key);
|
|
12411
|
+
if (!r) return e;
|
|
12412
|
+
if (e.kind === "enum") return this.decorateEnumFlat(e, r);
|
|
12413
|
+
if (r.kind === "disabled") return { ...e, disabled: true, disabledReason: r.reason };
|
|
12414
|
+
return e;
|
|
12415
|
+
});
|
|
12416
|
+
}
|
|
12417
|
+
// ── Pass-through delegates ───────────────────────────────────────
|
|
12418
|
+
param(key) {
|
|
12419
|
+
return this.inner.param(key);
|
|
12420
|
+
}
|
|
12421
|
+
hasParam(key) {
|
|
12422
|
+
return this.inner.hasParam(key);
|
|
12423
|
+
}
|
|
12424
|
+
hasFileInput() {
|
|
12425
|
+
return this.inner.hasFileInput();
|
|
12426
|
+
}
|
|
12427
|
+
getDefault(key) {
|
|
12428
|
+
return this.inner.getDefault(key);
|
|
12429
|
+
}
|
|
12430
|
+
getDefaults() {
|
|
12431
|
+
return this.inner.getDefaults();
|
|
12432
|
+
}
|
|
12433
|
+
toSchema() {
|
|
12434
|
+
return this.inner.toSchema();
|
|
12435
|
+
}
|
|
12436
|
+
transferValues(prev) {
|
|
12437
|
+
return this.inner.transferValues(prev);
|
|
12438
|
+
}
|
|
12439
|
+
// ── Private helpers ──────────────────────────────────────────────
|
|
12440
|
+
applyEntry(key, entry) {
|
|
12441
|
+
if (!entry) return void 0;
|
|
12442
|
+
const r = this.effects.get(key);
|
|
12443
|
+
if (!r) return entry;
|
|
12444
|
+
if (r.kind === "disabled") return { ...entry, disabled: true, disabledReason: r.reason };
|
|
12445
|
+
return entry;
|
|
12446
|
+
}
|
|
12447
|
+
applyEnum(key, entry) {
|
|
12448
|
+
if (!entry) return void 0;
|
|
12449
|
+
const r = this.effects.get(key);
|
|
12450
|
+
if (!r) return entry;
|
|
12451
|
+
if (r.kind === "disabled") {
|
|
12452
|
+
const options2 = entry.options.map((opt) => ({ ...opt, disabled: true, disabledReason: r.reason }));
|
|
12453
|
+
return { ...entry, options: options2, disabled: true, disabledReason: r.reason };
|
|
12454
|
+
}
|
|
12455
|
+
const allowed = new Set(r.allowed.map(String));
|
|
12456
|
+
const options = entry.options.map(
|
|
12457
|
+
(opt) => allowed.has(String(opt.id)) ? opt : { ...opt, disabled: true, disabledReason: r.reason }
|
|
12458
|
+
);
|
|
12459
|
+
return { ...entry, options };
|
|
12460
|
+
}
|
|
12461
|
+
decorateEnumFlat(entry, r) {
|
|
12462
|
+
if (entry.kind !== "enum") return entry;
|
|
12463
|
+
if (r.kind === "disabled") {
|
|
12464
|
+
const options2 = entry.options.map((opt) => ({
|
|
12465
|
+
...opt,
|
|
12466
|
+
disabled: true,
|
|
12467
|
+
disabledReason: r.reason
|
|
12468
|
+
}));
|
|
12469
|
+
return { ...entry, options: options2, disabled: true, disabledReason: r.reason };
|
|
12470
|
+
}
|
|
12471
|
+
const allowed = new Set(r.allowed.map(String));
|
|
12472
|
+
const options = entry.options.map(
|
|
12473
|
+
(opt) => allowed.has(String(opt.id)) ? opt : { ...opt, disabled: true, disabledReason: r.reason }
|
|
12474
|
+
);
|
|
12475
|
+
return { ...entry, options };
|
|
12476
|
+
}
|
|
12477
|
+
};
|
|
12478
|
+
var ModelMetaImpl = class {
|
|
12479
|
+
mode;
|
|
12480
|
+
inputType;
|
|
12481
|
+
description;
|
|
12482
|
+
features;
|
|
12483
|
+
badges;
|
|
12484
|
+
provider;
|
|
12485
|
+
addedAt;
|
|
12486
|
+
release;
|
|
12487
|
+
constructor(def) {
|
|
12488
|
+
this.mode = def.mode;
|
|
12489
|
+
this.inputType = def.inputType;
|
|
12490
|
+
this.description = def.description;
|
|
12491
|
+
this.features = def.features;
|
|
12492
|
+
this.badges = def.badge ?? [];
|
|
12493
|
+
this.release = def.release ?? "production";
|
|
12494
|
+
this.provider = {
|
|
12495
|
+
id: def.provider,
|
|
12496
|
+
name: def.providerName,
|
|
12497
|
+
color: def.providerColor,
|
|
12498
|
+
label: def.providerLabel
|
|
12499
|
+
};
|
|
12500
|
+
this.addedAt = def.addedAt ?? null;
|
|
12501
|
+
}
|
|
12502
|
+
};
|
|
12503
|
+
var ModelDescriptorImpl = class {
|
|
12504
|
+
id;
|
|
12505
|
+
name;
|
|
12506
|
+
api;
|
|
12507
|
+
def;
|
|
12508
|
+
_params;
|
|
12509
|
+
_meta;
|
|
12510
|
+
constructor(def) {
|
|
12511
|
+
this.id = def.id;
|
|
12512
|
+
this.name = def.name;
|
|
12513
|
+
this.api = { workflow: def.workflow, editWorkflow: def.editWorkflow };
|
|
12514
|
+
this.def = def;
|
|
12515
|
+
}
|
|
12516
|
+
params() {
|
|
12517
|
+
return this._params ??= new ModelParamsAccessorImpl(this.def);
|
|
12518
|
+
}
|
|
12519
|
+
paramsFor(values) {
|
|
12520
|
+
const inner = this.params();
|
|
12521
|
+
const effects = evaluateConstraints(this.def.constraints, values);
|
|
12522
|
+
if (!effects.size) return inner;
|
|
12523
|
+
return new ConstrainedParamsAccessor(inner, effects);
|
|
12524
|
+
}
|
|
12525
|
+
validate(input) {
|
|
12526
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
12527
|
+
return { valid: false, errors: [`Invalid input for model "${this.def.id}"`] };
|
|
12528
|
+
}
|
|
11961
12529
|
try {
|
|
11962
|
-
|
|
12530
|
+
validateAll(this.def.paramConfig, input);
|
|
11963
12531
|
return { valid: true };
|
|
11964
12532
|
} catch (err) {
|
|
11965
|
-
|
|
11966
|
-
return { valid: false, errors: [message] };
|
|
12533
|
+
return { valid: false, errors: [err instanceof Error ? err.message : String(err)] };
|
|
11967
12534
|
}
|
|
11968
|
-
}
|
|
11969
|
-
|
|
11970
|
-
|
|
11971
|
-
|
|
11972
|
-
|
|
11973
|
-
|
|
11974
|
-
|
|
11975
|
-
|
|
11976
|
-
|
|
11977
|
-
return
|
|
11978
|
-
}
|
|
11979
|
-
|
|
11980
|
-
|
|
11981
|
-
|
|
12535
|
+
}
|
|
12536
|
+
meta() {
|
|
12537
|
+
return this._meta ??= new ModelMetaImpl(this.def);
|
|
12538
|
+
}
|
|
12539
|
+
getCreditsInfo(ctx) {
|
|
12540
|
+
if (this.def.modelId) {
|
|
12541
|
+
const byModelId = getCreditsForModel(this.def.modelId, ctx);
|
|
12542
|
+
if (byModelId) return byModelId;
|
|
12543
|
+
}
|
|
12544
|
+
return getCreditsForModel(this.def.id, ctx);
|
|
12545
|
+
}
|
|
12546
|
+
};
|
|
12547
|
+
function _model(id) {
|
|
12548
|
+
return new ModelDescriptorImpl(resolveModel(id));
|
|
12549
|
+
}
|
|
12550
|
+
function _all(filter = {}) {
|
|
12551
|
+
const releases = filter.release ?? DEFAULT_VISIBLE_RELEASES;
|
|
12552
|
+
return ALL_MODELS.filter((m) => isVisibleForReleases(m, releases)).map((m) => new ModelDescriptorImpl(m));
|
|
12553
|
+
}
|
|
12554
|
+
function _find(filter) {
|
|
12555
|
+
const releases = filter.release ?? DEFAULT_VISIBLE_RELEASES;
|
|
12556
|
+
return ALL_MODELS.filter((m) => {
|
|
12557
|
+
if (!isVisibleForReleases(m, releases)) return false;
|
|
12558
|
+
if (filter.output && m.mode !== filter.output) return false;
|
|
12559
|
+
if (filter.provider && m.provider !== filter.provider) return false;
|
|
12560
|
+
return true;
|
|
12561
|
+
}).map((m) => new ModelDescriptorImpl(m));
|
|
12562
|
+
}
|
|
12563
|
+
function _search(query, filter = {}) {
|
|
12564
|
+
const releases = filter.release ?? DEFAULT_VISIBLE_RELEASES;
|
|
12565
|
+
const q = query.toLowerCase();
|
|
12566
|
+
return ALL_MODELS.filter(
|
|
12567
|
+
(m) => isVisibleForReleases(m, releases) && (m.id.toLowerCase().includes(q) || m.name.toLowerCase().includes(q) || m.provider.toLowerCase().includes(q))
|
|
12568
|
+
).map((m) => new ModelDescriptorImpl(m));
|
|
12569
|
+
}
|
|
12570
|
+
var Model = _model;
|
|
12571
|
+
var catalog = {
|
|
12572
|
+
all: _all,
|
|
12573
|
+
find: _find,
|
|
12574
|
+
search: _search,
|
|
12575
|
+
pricing: {
|
|
12576
|
+
configure: configurePricing,
|
|
12577
|
+
load: loadPricing,
|
|
12578
|
+
isLoaded: isPricingLoaded
|
|
11982
12579
|
}
|
|
11983
12580
|
};
|
|
11984
12581
|
function toBase64Url(bytes) {
|
|
@@ -12162,4 +12759,4 @@ function decodeDeepLinkPayload(encoded) {
|
|
|
12162
12759
|
return deserializePayload(encoded);
|
|
12163
12760
|
}
|
|
12164
12761
|
|
|
12165
|
-
export { ALL_MODELS, ApiError, ExecutionMode as ApiRunMode, DEFAULT_VISIBLE_RELEASES,
|
|
12762
|
+
export { ALL_MODELS, ApiError, ExecutionMode as ApiRunMode, DEFAULT_VISIBLE_RELEASES, GenerationEventType, Model, Models, buildFilename, buildGenerationAttributes, catalog, createClient, decodeDeepLinkPayload, encodeDeepLinkPayload, findModel, getModel, getModelsByMode, getVoiceById, inferResourceType, isVisibleForReleases, parseGeneration, releaseOf, toAvatarOption, toVoiceOption };
|