@ykurilov/agent-chat-images 0.81.0 → 0.82.1
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/CHANGELOG.md +28 -0
- package/dist/{chunk-5IORNMTX.js → chunk-JRBKNWG3.js} +3 -1
- package/dist/chunk-JRBKNWG3.js.map +1 -0
- package/dist/{chunk-33SNN6BB.js → chunk-W74LUWVQ.js} +31 -12
- package/dist/chunk-W74LUWVQ.js.map +1 -0
- package/dist/index.js +126 -44
- package/dist/index.js.map +1 -1
- package/dist/postgres.js +1 -1
- package/dist/react/index.js +1 -1
- package/dist/sqlite.js +1 -1
- package/dist/types/create-image-generation.d.ts +3 -0
- package/dist/types/image-catalog.d.ts +3 -0
- package/dist/types/image-failure.d.ts +11 -0
- package/dist/types/image-generation-error.d.ts +5 -0
- package/dist/types/image-queue/create-image-queue.d.ts +23 -0
- package/dist/types/image-queue/image-job-store.d.ts +12 -1
- package/dist/types/index.d.ts +2 -1
- package/package.json +6 -6
- package/dist/chunk-33SNN6BB.js.map +0 -1
- package/dist/chunk-5IORNMTX.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ImageJobKeyTakenError,
|
|
3
|
+
hasPendingImageFailure,
|
|
3
4
|
isOpenImageJob,
|
|
4
5
|
memoryImageJobStore
|
|
5
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-JRBKNWG3.js";
|
|
6
7
|
import {
|
|
7
8
|
ImageGenerationError,
|
|
8
9
|
aspectRatios,
|
|
10
|
+
canRetryImageFailure,
|
|
9
11
|
extendImageCatalog,
|
|
10
12
|
hideSecrets,
|
|
11
13
|
imageQualities,
|
|
12
14
|
imageSizes,
|
|
13
15
|
kieProvider,
|
|
16
|
+
providerFailureKind,
|
|
14
17
|
runwareProvider,
|
|
15
18
|
standardImageCatalog,
|
|
16
19
|
waitForImageTask
|
|
17
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-W74LUWVQ.js";
|
|
18
21
|
|
|
19
22
|
// src/create-image-generation.ts
|
|
20
23
|
import { tool } from "ai";
|
|
@@ -110,7 +113,7 @@ function pngSize(bytes) {
|
|
|
110
113
|
var DOWNLOAD_TIMEOUT_MS = 6e4;
|
|
111
114
|
var MAX_REDIRECTS = 3;
|
|
112
115
|
var imageExtensions = { "image/png": "png", "image/jpeg": "jpg", "image/webp": "webp" };
|
|
113
|
-
var failure = (reason) => new ImageGenerationError(reason, { refusedBeforeWork: false });
|
|
116
|
+
var failure = (reason, kind = "unknown") => new ImageGenerationError(reason, { refusedBeforeWork: false, kind });
|
|
114
117
|
var megabytes = (bytes) => `${Math.round(bytes / (1024 * 1024) * 10) / 10} \u041C\u0411`;
|
|
115
118
|
async function readWithin(response, maxBytes) {
|
|
116
119
|
if (!response.body) return new Uint8Array();
|
|
@@ -141,9 +144,9 @@ function checkedAddress(url, base, trustedOrigin) {
|
|
|
141
144
|
try {
|
|
142
145
|
address = new URL(url, base);
|
|
143
146
|
} catch {
|
|
144
|
-
throw failure("\u041A\u0430\u0440\u0442\u0438\u043D\u043A\u0430 \u043D\u0435 \u0441\u043A\u0430\u0447\u0430\u043B\u0430\u0441\u044C: \u043D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u0430\u0434\u0440\u0435\u0441");
|
|
147
|
+
throw failure("\u041A\u0430\u0440\u0442\u0438\u043D\u043A\u0430 \u043D\u0435 \u0441\u043A\u0430\u0447\u0430\u043B\u0430\u0441\u044C: \u043D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u0430\u0434\u0440\u0435\u0441", "configuration");
|
|
145
148
|
}
|
|
146
|
-
if (!isAllowedImageAddress(address, trustedOrigin)) throw failure("\u041A\u0430\u0440\u0442\u0438\u043D\u043A\u0430 \u043D\u0435 \u0441\u043A\u0430\u0447\u0430\u043B\u0430\u0441\u044C: \u043D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u0430\u0434\u0440\u0435\u0441");
|
|
149
|
+
if (!isAllowedImageAddress(address, trustedOrigin)) throw failure("\u041A\u0430\u0440\u0442\u0438\u043D\u043A\u0430 \u043D\u0435 \u0441\u043A\u0430\u0447\u0430\u043B\u0430\u0441\u044C: \u043D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u0430\u0434\u0440\u0435\u0441", "configuration");
|
|
147
150
|
return address;
|
|
148
151
|
}
|
|
149
152
|
function fetchFor(input, address) {
|
|
@@ -172,15 +175,15 @@ async function downloadGeneratedImage(input) {
|
|
|
172
175
|
let bytes;
|
|
173
176
|
try {
|
|
174
177
|
const response = await fetchImage(input, address, signal);
|
|
175
|
-
if (!response.ok) throw failure(`\u041A\u0430\u0440\u0442\u0438\u043D\u043A\u0430 \u043D\u0435 \u0441\u043A\u0430\u0447\u0430\u043B\u0430\u0441\u044C: ${response.status}
|
|
178
|
+
if (!response.ok) throw failure(`\u041A\u0430\u0440\u0442\u0438\u043D\u043A\u0430 \u043D\u0435 \u0441\u043A\u0430\u0447\u0430\u043B\u0430\u0441\u044C: ${response.status}`, response.status === 408 || response.status === 429 || response.status >= 500 ? "transient" : "configuration");
|
|
176
179
|
const declared = Number(response.headers.get("content-length"));
|
|
177
180
|
if (Number.isFinite(declared) && declared > input.maxImageBytes) throw failure(`\u041A\u0430\u0440\u0442\u0438\u043D\u043A\u0430 \u0431\u043E\u043B\u044C\u0448\u0435 ${megabytes(input.maxImageBytes)}`);
|
|
178
181
|
bytes = await readWithin(response, input.maxImageBytes);
|
|
179
182
|
} catch (error) {
|
|
180
183
|
if (input.abortSignal?.aborted) throw input.abortSignal.reason ?? error;
|
|
181
184
|
if (error instanceof ImageGenerationError) throw error;
|
|
182
|
-
if (error instanceof WebToolError && error.kind === "bad_address") throw failure("\u041A\u0430\u0440\u0442\u0438\u043D\u043A\u0430 \u043D\u0435 \u0441\u043A\u0430\u0447\u0430\u043B\u0430\u0441\u044C: \u043D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u0430\u0434\u0440\u0435\u0441");
|
|
183
|
-
throw failure(timeout.signal.aborted ? "\u041A\u0430\u0440\u0442\u0438\u043D\u043A\u0430 \u043D\u0435 \u0441\u043A\u0430\u0447\u0430\u043B\u0430\u0441\u044C: \u043D\u0435 \u0443\u0441\u043F\u0435\u043B\u0430" : "\u041A\u0430\u0440\u0442\u0438\u043D\u043A\u0430 \u043D\u0435 \u0441\u043A\u0430\u0447\u0430\u043B\u0430\u0441\u044C");
|
|
185
|
+
if (error instanceof WebToolError && error.kind === "bad_address") throw failure("\u041A\u0430\u0440\u0442\u0438\u043D\u043A\u0430 \u043D\u0435 \u0441\u043A\u0430\u0447\u0430\u043B\u0430\u0441\u044C: \u043D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u0430\u0434\u0440\u0435\u0441", "configuration");
|
|
186
|
+
throw failure(timeout.signal.aborted ? "\u041A\u0430\u0440\u0442\u0438\u043D\u043A\u0430 \u043D\u0435 \u0441\u043A\u0430\u0447\u0430\u043B\u0430\u0441\u044C: \u043D\u0435 \u0443\u0441\u043F\u0435\u043B\u0430" : "\u041A\u0430\u0440\u0442\u0438\u043D\u043A\u0430 \u043D\u0435 \u0441\u043A\u0430\u0447\u0430\u043B\u0430\u0441\u044C", "transient");
|
|
184
187
|
} finally {
|
|
185
188
|
clearTimeout(timer);
|
|
186
189
|
}
|
|
@@ -674,9 +677,14 @@ function markRecorded(record, usageId) {
|
|
|
674
677
|
}));
|
|
675
678
|
if (!found) return null;
|
|
676
679
|
if (isOpen(record)) return { steps };
|
|
677
|
-
const left = hasUnrecorded(steps);
|
|
680
|
+
const left = hasUnrecorded(steps) || hasPendingImageFailure(record);
|
|
678
681
|
return { steps, active: left, ...!left && { leaseOwner: void 0, leaseUntil: void 0 } };
|
|
679
682
|
}
|
|
683
|
+
function failureOf(record) {
|
|
684
|
+
if (record.failure) return record.failure;
|
|
685
|
+
if (record.status !== "failed" || record.lateUntil !== void 0 || !record.reason) return void 0;
|
|
686
|
+
return { kind: providerFailureKind(void 0, void 0, record.reason), reason: record.reason };
|
|
687
|
+
}
|
|
680
688
|
function view(record) {
|
|
681
689
|
return {
|
|
682
690
|
id: record.id,
|
|
@@ -691,6 +699,7 @@ function view(record) {
|
|
|
691
699
|
...record.data !== void 0 && { data: record.data },
|
|
692
700
|
status: record.status,
|
|
693
701
|
...record.reason !== void 0 && { reason: record.reason },
|
|
702
|
+
...failureOf(record) !== void 0 && { failure: structuredClone(failureOf(record)) },
|
|
694
703
|
...record.result !== void 0 && { result: record.result },
|
|
695
704
|
attempt: record.attempt,
|
|
696
705
|
...record.lateUntil !== void 0 && { lateUntil: record.lateUntil },
|
|
@@ -701,7 +710,7 @@ function view(record) {
|
|
|
701
710
|
}
|
|
702
711
|
var refused = (reason) => ({ status: "refused", reason });
|
|
703
712
|
function checkOptions(options) {
|
|
704
|
-
const positive = ["pollIntervalMs", "attemptTimeoutMs", "latePollIntervalMs", "callTimeoutMs", "acceptWaitMs", "leaseMs", "forgetFinishedAfterMs", "maxImageBytes"];
|
|
713
|
+
const positive = ["failureRetryMs", "pollIntervalMs", "attemptTimeoutMs", "latePollIntervalMs", "callTimeoutMs", "acceptWaitMs", "leaseMs", "forgetFinishedAfterMs", "maxImageBytes"];
|
|
705
714
|
for (const name of positive) {
|
|
706
715
|
const value = options[name];
|
|
707
716
|
if (value !== void 0 && !(Number.isFinite(value) && value > 0)) throw new Error(`\u041A\u0430\u0440\u0442\u0438\u043D\u043A\u0438: ${name} \u2014 \u043D\u0443\u0436\u043D\u043E \u0447\u0438\u0441\u043B\u043E \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0443\u043B\u044F`);
|
|
@@ -712,6 +721,7 @@ function checkOptions(options) {
|
|
|
712
721
|
}
|
|
713
722
|
const window = options.lateResultWindowMs;
|
|
714
723
|
if (window !== void 0 && !(Number.isFinite(window) && window >= 0)) throw new Error("\u041A\u0430\u0440\u0442\u0438\u043D\u043A\u0438: lateResultWindowMs \u2014 \u0447\u0438\u0441\u043B\u043E \u043D\u0435 \u043C\u0435\u043D\u044C\u0448\u0435 \u043D\u0443\u043B\u044F");
|
|
724
|
+
if (options.onFailed !== void 0 && typeof options.onFailed !== "function") throw new TypeError("\u041A\u0430\u0440\u0442\u0438\u043D\u043A\u0438: onFailed \u2014 \u043D\u0443\u0436\u043D\u0430 \u0444\u0443\u043D\u043A\u0446\u0438\u044F");
|
|
715
725
|
if (typeof options.deliver !== "function") throw new Error("\u041A\u0430\u0440\u0442\u0438\u043D\u043A\u0438: deliver \u2014 \u043D\u0443\u0436\u043D\u0430 \u0444\u0443\u043D\u043A\u0446\u0438\u044F-\u043F\u0440\u0438\u0451\u043C\u043D\u0438\u043A");
|
|
716
726
|
}
|
|
717
727
|
function createImageQueue(options) {
|
|
@@ -725,6 +735,7 @@ function createImageQueue(options) {
|
|
|
725
735
|
const nowMs = () => now().getTime();
|
|
726
736
|
const hide = setups.hideSecrets;
|
|
727
737
|
const logError = (message) => options.logError?.(hide(message));
|
|
738
|
+
const failureRetryMs = options.failureRetryMs ?? 3e4;
|
|
728
739
|
const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
729
740
|
const attemptTimeoutMs = options.attemptTimeoutMs ?? DEFAULT_ATTEMPT_TIMEOUT_MS;
|
|
730
741
|
const attempts = options.attempts ?? 1;
|
|
@@ -861,7 +872,7 @@ function createImageQueue(options) {
|
|
|
861
872
|
current = await updateJob(current, (fresh) => markRecorded(fresh, charge.usageId), () => true);
|
|
862
873
|
if (!current) return;
|
|
863
874
|
}
|
|
864
|
-
await updateJob(current, (fresh) => isOpen(fresh) || !fresh.active && fresh.leaseOwner === void 0 ? null : { active: hasUnrecorded(fresh.steps), leaseOwner: void 0, leaseUntil: void 0 }, () => true);
|
|
875
|
+
await updateJob(current, (fresh) => isOpen(fresh) || !fresh.active && fresh.leaseOwner === void 0 ? null : { active: hasUnrecorded(fresh.steps) || hasPendingImageFailure(fresh), leaseOwner: void 0, leaseUntil: void 0 }, () => true);
|
|
865
876
|
}
|
|
866
877
|
async function chargeTask(rt, index, costUsd) {
|
|
867
878
|
await write(rt, (record) => {
|
|
@@ -886,24 +897,29 @@ function createImageQueue(options) {
|
|
|
886
897
|
(fresh) => {
|
|
887
898
|
if (!isOpen(fresh)) return null;
|
|
888
899
|
const steps = fresh.steps.map((step) => step.task ? { ...withoutTask(step), charges: [...step.charges, chargeOf(step.task, step.task.priceUsd)] } : step);
|
|
889
|
-
return { status, reason, run: fresh.run + 1, steps, lateUntil: void 0, active: hasUnrecorded(steps), finishedAt: iso(nowMs()), leaseOwner: void 0, leaseUntil: void 0 };
|
|
900
|
+
return { status, reason, failure: status === "failed" ? { kind: "configuration", reason: reason ?? "\u041D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0430 \u043A\u0430\u0440\u0442\u0438\u043D\u043E\u043A \u0441\u043C\u0435\u043D\u0438\u043B\u0430\u0441\u044C" } : void 0, failureNotification: status === "failed" && options.onFailed ? { id: `image-failed:${fresh.id}:${fresh.run + 1}`, attempts: 0, nextAttemptAt: iso(nowMs()) } : void 0, run: fresh.run + 1, steps, lateUntil: void 0, active: hasUnrecorded(steps) || status === "failed" && options.onFailed !== void 0, finishedAt: iso(nowMs()), leaseOwner: void 0, leaseUntil: void 0 };
|
|
890
901
|
},
|
|
891
902
|
() => true
|
|
892
903
|
);
|
|
893
904
|
if (!closed) return;
|
|
894
905
|
byJob.get(record.id)?.controller.abort(RUN_ENDED);
|
|
895
906
|
await settleClosed(closed);
|
|
907
|
+
if (status === "failed" && options.onFailed) {
|
|
908
|
+
const pending = await store.get(closed.id);
|
|
909
|
+
if (pending && hasPendingImageFailure(pending)) await claim(pending);
|
|
910
|
+
}
|
|
896
911
|
}
|
|
897
912
|
function openUsd(rt) {
|
|
898
913
|
const record = rt.record;
|
|
899
914
|
if (!record) return rt.reservedUsd;
|
|
900
|
-
if (!isOpen(record)) return 0;
|
|
915
|
+
if (!isOpen(record) || !("setup" in rt)) return 0;
|
|
916
|
+
const setup = rt.setup;
|
|
901
917
|
const late = record.lateUntil !== void 0;
|
|
902
918
|
const last = record.steps.length - 1;
|
|
903
919
|
return record.steps.reduce((total, step, index) => {
|
|
904
|
-
if (step.urls) return rt.reorderOnDeliveryFailure && index === last ? total +
|
|
920
|
+
if (step.urls) return rt.reorderOnDeliveryFailure && index === last ? total + setup.steps[index].priceUsd : total;
|
|
905
921
|
if (step.task) return total + step.task.priceUsd;
|
|
906
|
-
return late ? total : total + (
|
|
922
|
+
return late ? total : total + (setup.steps[index]?.priceUsd ?? 0);
|
|
907
923
|
}, 0);
|
|
908
924
|
}
|
|
909
925
|
function pendingUsd(ownerId, chatId, before) {
|
|
@@ -949,9 +965,10 @@ function createImageQueue(options) {
|
|
|
949
965
|
}
|
|
950
966
|
async function failedSend(rt, index, error, wholeAttempt) {
|
|
951
967
|
throwIfEnded(rt);
|
|
968
|
+
const failure2 = { kind: error instanceof ImageGenerationError ? error.kind : error instanceof CallTimedOut ? "transient" : "unknown", reason: error instanceof ImageGenerationError ? hide(error.reason) : error instanceof CallTimedOut ? error.message : "\u041F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440 \u043D\u0435 \u0441\u043C\u043E\u0433 \u0441\u043E\u0437\u0434\u0430\u0442\u044C \u043A\u0430\u0440\u0442\u0438\u043D\u043A\u0443", ...error instanceof ImageGenerationError && error.providerCode && { providerCode: hide(error.providerCode) } };
|
|
952
969
|
if (error instanceof ImageGenerationError && error.refusedBeforeWork) {
|
|
953
970
|
await write(rt, (record) => ({ steps: withStep(record.steps, index, withoutTask) }));
|
|
954
|
-
return { kind: "refused", reason: hide(error.reason) };
|
|
971
|
+
return { kind: "refused", reason: hide(error.reason), failure: failure2 };
|
|
955
972
|
}
|
|
956
973
|
let reason;
|
|
957
974
|
if (error instanceof ImageGenerationError) reason = hide(error.reason);
|
|
@@ -961,7 +978,7 @@ function createImageQueue(options) {
|
|
|
961
978
|
reason = "\u041F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440 \u043D\u0435 \u0441\u043C\u043E\u0433 \u0441\u043E\u0437\u0434\u0430\u0442\u044C \u043A\u0430\u0440\u0442\u0438\u043D\u043A\u0443";
|
|
962
979
|
}
|
|
963
980
|
await chargeTask(rt, index);
|
|
964
|
-
return { kind: "failed", reason };
|
|
981
|
+
return { kind: canRetryImageFailure(failure2.kind) ? "failed" : "refused", reason, failure: failure2 };
|
|
965
982
|
}
|
|
966
983
|
async function send(rt, index) {
|
|
967
984
|
const step = rt.setup.steps[index];
|
|
@@ -1017,7 +1034,7 @@ function createImageQueue(options) {
|
|
|
1017
1034
|
throwIfEnded(rt);
|
|
1018
1035
|
if (error instanceof ImageGenerationError) {
|
|
1019
1036
|
await chargeTask(rt, index);
|
|
1020
|
-
return { kind: "failed", reason: hide(error.reason) };
|
|
1037
|
+
return { kind: canRetryImageFailure(error.kind) ? "failed" : "refused", reason: hide(error.reason), failure: { kind: error.kind, reason: hide(error.reason), ...error.providerCode && { providerCode: hide(error.providerCode) } } };
|
|
1021
1038
|
}
|
|
1022
1039
|
continue;
|
|
1023
1040
|
}
|
|
@@ -1028,21 +1045,23 @@ function createImageQueue(options) {
|
|
|
1028
1045
|
}
|
|
1029
1046
|
if (state.status === "failed") {
|
|
1030
1047
|
await chargeTask(rt, index, state.costUsd);
|
|
1031
|
-
return { kind: "failed", reason: hide(state.reason) };
|
|
1048
|
+
return { kind: canRetryImageFailure(state.failureKind) ? "failed" : "refused", reason: hide(state.reason), failure: { kind: state.failureKind ?? "unknown", reason: hide(state.reason), ...state.providerCode && { providerCode: hide(state.providerCode) } } };
|
|
1032
1049
|
}
|
|
1033
1050
|
}
|
|
1034
1051
|
}
|
|
1035
|
-
async function finishFailed(rt, reason, extra = {}) {
|
|
1052
|
+
async function finishFailed(rt, reason, extra = {}, failure2 = { kind: "unknown", reason }) {
|
|
1036
1053
|
await write(rt, (record) => ({
|
|
1037
1054
|
status: "failed",
|
|
1038
1055
|
reason,
|
|
1039
1056
|
lateUntil: void 0,
|
|
1040
1057
|
...extra,
|
|
1041
|
-
|
|
1058
|
+
failure: failure2,
|
|
1059
|
+
failureNotification: options.onFailed ? { id: `image-failed:${record.id}:${record.run}`, attempts: 0, nextAttemptAt: iso(nowMs()) } : void 0,
|
|
1060
|
+
active: hasUnrecorded(record.steps) || options.onFailed !== void 0,
|
|
1042
1061
|
finishedAt: iso(nowMs()),
|
|
1043
|
-
leaseOwner: void 0,
|
|
1044
|
-
leaseUntil: void 0
|
|
1062
|
+
...options.onFailed ? {} : { leaseOwner: void 0, leaseUntil: void 0 }
|
|
1045
1063
|
}));
|
|
1064
|
+
rt.sent.resolve();
|
|
1046
1065
|
}
|
|
1047
1066
|
const nextAttempt = (rt) => write(rt, (record) => ({ attempt: record.attempt + 1, attemptDeadline: iso(nowMs() + attemptTimeoutMs) }));
|
|
1048
1067
|
async function runStep(rt, index) {
|
|
@@ -1065,7 +1084,7 @@ function createImageQueue(options) {
|
|
|
1065
1084
|
return true;
|
|
1066
1085
|
}
|
|
1067
1086
|
if (outcome.kind === "refused") {
|
|
1068
|
-
await finishFailed(rt, outcome.reason);
|
|
1087
|
+
await finishFailed(rt, outcome.reason, {}, outcome.failure);
|
|
1069
1088
|
return false;
|
|
1070
1089
|
}
|
|
1071
1090
|
if (outcome.kind === "failed") {
|
|
@@ -1073,12 +1092,12 @@ function createImageQueue(options) {
|
|
|
1073
1092
|
await nextAttempt(rt);
|
|
1074
1093
|
continue;
|
|
1075
1094
|
}
|
|
1076
|
-
await finishFailed(rt, outcome.reason);
|
|
1095
|
+
await finishFailed(rt, outcome.reason, {}, outcome.failure);
|
|
1077
1096
|
return false;
|
|
1078
1097
|
}
|
|
1079
1098
|
if (late) {
|
|
1080
1099
|
await chargeTask(rt, index);
|
|
1081
|
-
await finishFailed(rt, rt.record.reason ?? timeoutReason);
|
|
1100
|
+
await finishFailed(rt, rt.record.reason ?? timeoutReason, {}, { kind: "transient", reason: rt.record.reason ?? timeoutReason });
|
|
1082
1101
|
return false;
|
|
1083
1102
|
}
|
|
1084
1103
|
if (rt.record.attempt < attempts) {
|
|
@@ -1091,7 +1110,7 @@ function createImageQueue(options) {
|
|
|
1091
1110
|
continue;
|
|
1092
1111
|
}
|
|
1093
1112
|
await chargeTask(rt, index);
|
|
1094
|
-
await finishFailed(rt, timeoutReason);
|
|
1113
|
+
await finishFailed(rt, timeoutReason, {}, { kind: "transient", reason: timeoutReason });
|
|
1095
1114
|
return false;
|
|
1096
1115
|
}
|
|
1097
1116
|
}
|
|
@@ -1121,7 +1140,7 @@ function createImageQueue(options) {
|
|
|
1121
1140
|
throwIfEnded(rt);
|
|
1122
1141
|
if (attempt >= downloadAttempts) {
|
|
1123
1142
|
if (rt.reorderOnDeliveryFailure) return "reorder";
|
|
1124
|
-
await finishFailed(rt, error instanceof ImageGenerationError ? hide(error.reason) : "\u041A\u0430\u0440\u0442\u0438\u043D\u043A\u0430 \u043D\u0435 \u0441\u043A\u0430\u0447\u0430\u043B\u0430\u0441\u044C", { deliveryFailed: true });
|
|
1143
|
+
await finishFailed(rt, error instanceof ImageGenerationError ? hide(error.reason) : "\u041A\u0430\u0440\u0442\u0438\u043D\u043A\u0430 \u043D\u0435 \u0441\u043A\u0430\u0447\u0430\u043B\u0430\u0441\u044C", { deliveryFailed: true }, { kind: error instanceof ImageGenerationError ? error.kind : "unknown", reason: error instanceof ImageGenerationError ? hide(error.reason) : "\u041A\u0430\u0440\u0442\u0438\u043D\u043A\u0430 \u043D\u0435 \u0441\u043A\u0430\u0447\u0430\u043B\u0430\u0441\u044C" });
|
|
1125
1144
|
return "done";
|
|
1126
1145
|
}
|
|
1127
1146
|
await sleep(pollIntervalMs, rt.controller.signal);
|
|
@@ -1146,7 +1165,7 @@ function createImageQueue(options) {
|
|
|
1146
1165
|
} catch (error) {
|
|
1147
1166
|
throwIfEnded(rt);
|
|
1148
1167
|
logError(`\u041A\u0430\u0440\u0442\u0438\u043D\u043A\u0438: \u043F\u0440\u0438\u0451\u043C\u043D\u0438\u043A \u043D\u0435 \u043F\u0440\u0438\u043D\u044F\u043B \u043A\u0430\u0440\u0442\u0438\u043D\u043A\u0443 \u0437\u0430\u0434\u0430\u0447\u0438 ${record.id}: ${errorText2(error)}`);
|
|
1149
|
-
await finishFailed(rt, "\u041F\u0440\u0438\u0451\u043C\u043D\u0438\u043A \u043D\u0435 \u043F\u0440\u0438\u043D\u044F\u043B \u043A\u0430\u0440\u0442\u0438\u043D\u043A\u0443", { deliveryFailed: true });
|
|
1168
|
+
await finishFailed(rt, "\u041F\u0440\u0438\u0451\u043C\u043D\u0438\u043A \u043D\u0435 \u043F\u0440\u0438\u043D\u044F\u043B \u043A\u0430\u0440\u0442\u0438\u043D\u043A\u0443", { deliveryFailed: true }, { kind: "delivery", reason: "\u041F\u0440\u0438\u0451\u043C\u043D\u0438\u043A \u043D\u0435 \u043F\u0440\u0438\u043D\u044F\u043B \u043A\u0430\u0440\u0442\u0438\u043D\u043A\u0443" });
|
|
1150
1169
|
return "done";
|
|
1151
1170
|
}
|
|
1152
1171
|
throwIfEnded(rt);
|
|
@@ -1182,16 +1201,62 @@ function createImageQueue(options) {
|
|
|
1182
1201
|
try {
|
|
1183
1202
|
await recordCharges(rt);
|
|
1184
1203
|
if (isOpen(rt.record)) await generate(rt);
|
|
1204
|
+
await notifyFailure(rt);
|
|
1185
1205
|
} catch (error) {
|
|
1186
1206
|
if (error !== RUN_ENDED && !rt.controller.signal.aborted) logError(`\u041A\u0430\u0440\u0442\u0438\u043D\u043A\u0438: \u0437\u0430\u0434\u0430\u0447\u0430 ${rt.record?.id ?? ""} \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u043B\u0430\u0441\u044C: ${errorText2(error)}`);
|
|
1187
1207
|
} finally {
|
|
1188
|
-
rt
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1208
|
+
finishRuntime(rt);
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
function finishRuntime(rt) {
|
|
1212
|
+
rt.ended = true;
|
|
1213
|
+
clearTimeout(rt.leaseTimer);
|
|
1214
|
+
runtimes.delete(rt);
|
|
1215
|
+
if (rt.record && byJob.get(rt.record.id) === rt) byJob.delete(rt.record.id);
|
|
1216
|
+
rt.sent.resolve();
|
|
1217
|
+
}
|
|
1218
|
+
async function notifyFailure(rt) {
|
|
1219
|
+
const record = rt.record;
|
|
1220
|
+
const notice = record.failureNotification;
|
|
1221
|
+
if (!options.onFailed || !notice || notice.deliveredAt || record.status !== "failed" || !record.failure || record.lateUntil) return;
|
|
1222
|
+
if (Date.parse(notice.nextAttemptAt) > nowMs()) return;
|
|
1223
|
+
const reserved = await write(rt, (fresh) => ({ failureNotification: { ...fresh.failureNotification, attempts: fresh.failureNotification.attempts + 1, nextAttemptAt: iso(nowMs() + failureRetryMs) } }));
|
|
1224
|
+
const id = notice.id;
|
|
1225
|
+
const isCurrent = async () => {
|
|
1226
|
+
if (rt.controller.signal.aborted) return false;
|
|
1227
|
+
const fresh = await store.get(record.id);
|
|
1228
|
+
const current = fresh?.status === "failed" && fresh.run === record.run && fresh.leaseOwner === me && Date.parse(fresh.leaseUntil ?? "") > nowMs() && fresh.failureNotification?.id === id && fresh.failureNotification.deliveredAt === void 0 && !rt.controller.signal.aborted;
|
|
1229
|
+
if (!current) rt.controller.abort(RUN_ENDED);
|
|
1230
|
+
return current;
|
|
1231
|
+
};
|
|
1232
|
+
if (!await isCurrent()) throw RUN_ENDED;
|
|
1233
|
+
try {
|
|
1234
|
+
await options.onFailed({ id, run: reserved.run, job: view(reserved), failure: structuredClone(reserved.failure) }, { signal: rt.controller.signal, isCurrent });
|
|
1235
|
+
if (!await isCurrent()) throw RUN_ENDED;
|
|
1236
|
+
await write(rt, (fresh) => ({ failureNotification: { ...fresh.failureNotification, deliveredAt: iso(nowMs()) }, active: hasUnrecorded(fresh.steps), leaseOwner: void 0, leaseUntil: void 0 }));
|
|
1237
|
+
} catch {
|
|
1238
|
+
throwIfEnded(rt);
|
|
1239
|
+
logError(`\u041A\u0430\u0440\u0442\u0438\u043D\u043A\u0438: \u0441\u043E\u0431\u044B\u0442\u0438\u0435 ${id} \u043D\u0435 \u043F\u0440\u0438\u043D\u044F\u0442\u043E, \u043F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u0441\u044F \u0431\u0435\u0437 \u0433\u0435\u043D\u0435\u0440\u0430\u0446\u0438\u0438`);
|
|
1240
|
+
await write(rt, () => ({ active: true, leaseOwner: void 0, leaseUntil: void 0 }));
|
|
1193
1241
|
}
|
|
1194
1242
|
}
|
|
1243
|
+
function launchFailure(record) {
|
|
1244
|
+
const rt = newJobRuntime(record.ownerId, record.chatId, 0);
|
|
1245
|
+
rt.record = record;
|
|
1246
|
+
runtimes.add(rt);
|
|
1247
|
+
byJob.set(record.id, rt);
|
|
1248
|
+
keepLease(rt);
|
|
1249
|
+
rt.done = (async () => {
|
|
1250
|
+
try {
|
|
1251
|
+
await recordCharges(rt);
|
|
1252
|
+
await notifyFailure(rt);
|
|
1253
|
+
} catch (error) {
|
|
1254
|
+
if (error !== RUN_ENDED && !rt.controller.signal.aborted) logError(`\u041A\u0430\u0440\u0442\u0438\u043D\u043A\u0438: \u0441\u043E\u0431\u044B\u0442\u0438\u0435 ${record.id} \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u043B\u043E\u0441\u044C, \u0431\u0443\u0434\u0435\u0442 \u043F\u043E\u0434\u043E\u0431\u0440\u0430\u043D\u043E \u043F\u043E\u0441\u043B\u0435 \u0430\u0440\u0435\u043D\u0434\u044B`);
|
|
1255
|
+
} finally {
|
|
1256
|
+
finishRuntime(rt);
|
|
1257
|
+
}
|
|
1258
|
+
})();
|
|
1259
|
+
}
|
|
1195
1260
|
function keepLease(rt) {
|
|
1196
1261
|
const renew = () => {
|
|
1197
1262
|
rt.leaseTimer = setTimeout(async () => {
|
|
@@ -1207,10 +1272,13 @@ function createImageQueue(options) {
|
|
|
1207
1272
|
};
|
|
1208
1273
|
renew();
|
|
1209
1274
|
}
|
|
1210
|
-
function
|
|
1275
|
+
function newJobRuntime(ownerId, chatId, reservedUsd) {
|
|
1211
1276
|
let resolve;
|
|
1212
1277
|
const promise = new Promise((done) => resolve = done);
|
|
1213
|
-
return { ownerId, chatId,
|
|
1278
|
+
return { ownerId, chatId, reservedUsd, controller: new AbortController(), chain: Promise.resolve(), sent: { promise, resolve }, ended: false, checkNow: false, reorderOnDeliveryFailure: false };
|
|
1279
|
+
}
|
|
1280
|
+
function newRuntime(ownerId, chatId, setup, reservedUsd) {
|
|
1281
|
+
return { ...newJobRuntime(ownerId, chatId, reservedUsd), setup };
|
|
1214
1282
|
}
|
|
1215
1283
|
function launch(rt, record) {
|
|
1216
1284
|
rt.record = record;
|
|
@@ -1237,9 +1305,13 @@ function createImageQueue(options) {
|
|
|
1237
1305
|
}
|
|
1238
1306
|
async function claim(record) {
|
|
1239
1307
|
if (stopped || byJob.has(record.id)) return;
|
|
1308
|
+
if (!isOpen(record) && hasPendingImageFailure(record) && !hasUnrecorded(record.steps) && Date.parse(record.failureNotification.nextAttemptAt) > nowMs()) return;
|
|
1240
1309
|
const claimed = await store.update(record.id, { leaseOwner: me, leaseUntil: iso(nowMs() + leaseMs), updatedAt: iso(nowMs()) }, record.version);
|
|
1241
1310
|
if (!claimed) return;
|
|
1242
|
-
if (!isOpen(claimed))
|
|
1311
|
+
if (!isOpen(claimed)) {
|
|
1312
|
+
if (options.onFailed && hasPendingImageFailure(claimed) && Date.parse(claimed.failureNotification.nextAttemptAt) <= nowMs()) return launchFailure(claimed);
|
|
1313
|
+
return settleClosed(claimed);
|
|
1314
|
+
}
|
|
1243
1315
|
const setup = setupOf(claimed);
|
|
1244
1316
|
if (!setup) return closeJob(claimed, "failed", `\u041D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0430 \u043A\u0430\u0440\u0442\u0438\u043D\u043E\u043A \u0441\u043C\u0435\u043D\u0438\u043B\u0430\u0441\u044C: \u043D\u0430\u0437\u043D\u0430\u0447\u0435\u043D\u0438\u044F \xAB${claimed.purpose}\xBB \u0432 \u043D\u0435\u0439 \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435\u0442`);
|
|
1245
1317
|
const rt = newRuntime(claimed.ownerId, claimed.chatId, setup, 0);
|
|
@@ -1358,11 +1430,13 @@ function createImageQueue(options) {
|
|
|
1358
1430
|
throw error;
|
|
1359
1431
|
}
|
|
1360
1432
|
}
|
|
1361
|
-
|
|
1433
|
+
const matchesRetry = (record, guard) => guard.expectedJobId === void 0 || record?.id === guard.expectedJobId && record.run === guard.expectedRun;
|
|
1434
|
+
function retryJob(ownerId, key, guard) {
|
|
1362
1435
|
return underKey(ownerId, key, async () => {
|
|
1363
1436
|
const latest = (await store.list({ ownerId, key })).at(-1);
|
|
1364
|
-
if (!latest) return null;
|
|
1437
|
+
if (!latest || !matchesRetry(latest, guard)) return null;
|
|
1365
1438
|
if (latest.status !== "failed") return view(latest);
|
|
1439
|
+
if (failureOf(latest)?.kind === "content") return refused("\u041F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440 \u043E\u0442\u043A\u043B\u043E\u043D\u0438\u043B \u0441\u043E\u0434\u0435\u0440\u0436\u0430\u043D\u0438\u0435: \u0442\u043E\u0442 \u0436\u0435 \u0437\u0430\u043F\u0440\u043E\u0441 \u043D\u0435 \u043F\u043E\u0432\u0442\u043E\u0440\u044F\u0435\u0442\u0441\u044F; \u0432\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0451\u043D\u043D\u0443\u044E \u0430\u043B\u044C\u0442\u0435\u0440\u043D\u0430\u0442\u0438\u0432\u0443");
|
|
1366
1440
|
const setup = setupOf(latest);
|
|
1367
1441
|
if (!setup) return refused(`\u043D\u0430\u0437\u043D\u0430\u0447\u0435\u043D\u0438\u044F \xAB${latest.purpose}\xBB \u0432 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0435 \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435\u0442`);
|
|
1368
1442
|
const remaining = latest.steps.reduce((total, step, index) => step.urls ? total : total + (step.task?.priceUsd ?? setup.steps[index].priceUsd), 0);
|
|
@@ -1376,6 +1450,7 @@ function createImageQueue(options) {
|
|
|
1376
1450
|
let current = latest;
|
|
1377
1451
|
let revived = null;
|
|
1378
1452
|
while (current?.status === "failed") {
|
|
1453
|
+
if (!matchesRetry(current, guard)) return null;
|
|
1379
1454
|
if (stopped) return refused(QUEUE_STOPPED);
|
|
1380
1455
|
const at = nowMs();
|
|
1381
1456
|
revived = await reviveOrTaken(
|
|
@@ -1386,6 +1461,8 @@ function createImageQueue(options) {
|
|
|
1386
1461
|
attempt: 1,
|
|
1387
1462
|
attemptDeadline: iso(at + attemptTimeoutMs),
|
|
1388
1463
|
reason: void 0,
|
|
1464
|
+
failure: void 0,
|
|
1465
|
+
failureNotification: void 0,
|
|
1389
1466
|
lateUntil: void 0,
|
|
1390
1467
|
deliveryFailed: void 0,
|
|
1391
1468
|
finishedAt: void 0,
|
|
@@ -1397,13 +1474,14 @@ function createImageQueue(options) {
|
|
|
1397
1474
|
current.version
|
|
1398
1475
|
);
|
|
1399
1476
|
if (revived === "taken") {
|
|
1477
|
+
if (guard.expectedJobId !== void 0) return null;
|
|
1400
1478
|
const newest = (await store.list({ ownerId, key })).at(-1);
|
|
1401
1479
|
return newest ? view(newest) : null;
|
|
1402
1480
|
}
|
|
1403
1481
|
if (revived) break;
|
|
1404
1482
|
current = await store.get(latest.id);
|
|
1405
1483
|
}
|
|
1406
|
-
if (!revived) return current ? view(current) : null;
|
|
1484
|
+
if (!revived) return current && matchesRetry(current, guard) ? view(current) : null;
|
|
1407
1485
|
if (stopped) {
|
|
1408
1486
|
await updateJob(revived, (record) => record.leaseOwner === me ? { leaseOwner: void 0, leaseUntil: void 0 } : null, (record) => record.leaseOwner === me);
|
|
1409
1487
|
return refused(QUEUE_STOPPED);
|
|
@@ -1449,12 +1527,14 @@ function createImageQueue(options) {
|
|
|
1449
1527
|
void start();
|
|
1450
1528
|
return {
|
|
1451
1529
|
order,
|
|
1452
|
-
retry({ ownerId, key }) {
|
|
1530
|
+
retry({ ownerId, key, expectedJobId, expectedRun }) {
|
|
1531
|
+
const guarded = expectedJobId !== void 0 || expectedRun !== void 0;
|
|
1532
|
+
if (guarded && (typeof expectedJobId !== "string" || expectedJobId.length === 0 || !Number.isInteger(expectedRun) || expectedRun < 1)) return Promise.reject(new TypeError("\u041A\u0430\u0440\u0442\u0438\u043D\u043A\u0438: expectedJobId \u0438 expectedRun \u043F\u0435\u0440\u0435\u0434\u0430\u044E\u0442\u0441\u044F \u0432\u043C\u0435\u0441\u0442\u0435; run \u2014 \u0446\u0435\u043B\u043E\u0435 \u0447\u0438\u0441\u043B\u043E \u043E\u0442 1"));
|
|
1453
1533
|
if (stopped) return Promise.reject(new Error("\u041A\u0430\u0440\u0442\u0438\u043D\u043A\u0438: \u043E\u0447\u0435\u0440\u0435\u0434\u044C \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u0430 (stop) \u2014 \u043F\u043E\u0432\u0442\u043E\u0440 \u043D\u0435 \u043F\u0440\u0438\u043D\u0438\u043C\u0430\u0435\u0442\u0441\u044F"));
|
|
1454
|
-
const flight = JSON.stringify([ownerId, key]);
|
|
1534
|
+
const flight = JSON.stringify([ownerId, key, expectedJobId, expectedRun]);
|
|
1455
1535
|
const running = retries.get(flight);
|
|
1456
1536
|
if (running) return running;
|
|
1457
|
-
const promise = retryJob(ownerId, key).finally(() => retries.delete(flight));
|
|
1537
|
+
const promise = retryJob(ownerId, key, { expectedJobId, expectedRun }).finally(() => retries.delete(flight));
|
|
1458
1538
|
retries.set(flight, promise);
|
|
1459
1539
|
return promise;
|
|
1460
1540
|
},
|
|
@@ -1541,7 +1621,7 @@ function checkNumbers(options) {
|
|
|
1541
1621
|
const limit = options.maxImagesPerTurn;
|
|
1542
1622
|
if (limit !== void 0 && !(Number.isInteger(limit) && limit >= 1)) throw new Error("\u041A\u0430\u0440\u0442\u0438\u043D\u043A\u0438: maxImagesPerTurn \u2014 \u0446\u0435\u043B\u043E\u0435 \u0447\u0438\u0441\u043B\u043E \u043E\u0442 1");
|
|
1543
1623
|
if (options.mode !== "background") {
|
|
1544
|
-
const backgroundOnly = ["jobStore", "pollIntervalMs", "attempts", "onQueued"].filter((name) => options[name] !== void 0);
|
|
1624
|
+
const backgroundOnly = ["jobStore", "pollIntervalMs", "attempts", "onQueued", "onFailed", "failureRetryMs"].filter((name) => options[name] !== void 0);
|
|
1545
1625
|
if (backgroundOnly.length > 0) throw new Error(`\u041A\u0430\u0440\u0442\u0438\u043D\u043A\u0438: ${backgroundOnly.join(", ")} \u2014 \u0442\u043E\u043B\u044C\u043A\u043E \u0434\u043B\u044F mode: "background"`);
|
|
1546
1626
|
}
|
|
1547
1627
|
}
|
|
@@ -1760,6 +1840,8 @@ function createImageGeneration(options) {
|
|
|
1760
1840
|
...options.purposes && { purposes: options.purposes },
|
|
1761
1841
|
...options.fetch && { fetch: options.fetch },
|
|
1762
1842
|
deliver: receiveQueued,
|
|
1843
|
+
...options.onFailed !== void 0 && { onFailed: options.onFailed },
|
|
1844
|
+
...options.failureRetryMs !== void 0 && { failureRetryMs: options.failureRetryMs },
|
|
1763
1845
|
...options.jobStore && { store: options.jobStore },
|
|
1764
1846
|
...options.spending && { spending: options.spending },
|
|
1765
1847
|
...options.pollIntervalMs !== void 0 && { pollIntervalMs: options.pollIntervalMs },
|