@ykurilov/agent-chat-images 0.80.1 → 0.82.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/CHANGELOG.md +35 -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 +136 -46
- 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 +8 -0
- package/dist/types/download-image.d.ts +9 -1
- 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 +28 -0
- package/dist/types/image-queue/image-job-store.d.ts +12 -1
- package/dist/types/image-setup.d.ts +2 -0
- package/dist/types/index.d.ts +2 -1
- package/package.json +6 -5
- package/dist/chunk-33SNN6BB.js.map +0 -1
- package/dist/chunk-5IORNMTX.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
# @ykurilov/agent-chat-images
|
|
2
2
|
|
|
3
|
+
## 0.82.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 92ee0a2: Очередь картинок сообщает о конечном сбое через необязательный `onFailed(event, context)`. Событие сохраняется до callback и повторяется с тем же id после ошибки или перезапуска без новой генерации. Приёмник подтверждает событие успешным завершением; приложение записывает его идемпотентно по event.id и ограничивает платное восстановление отдельным сохранённым бюджетом.
|
|
8
|
+
|
|
9
|
+
`ImageJob.failure` в get/list различает transient/content/limits/configuration/delivery/unknown. Повтор того же запроса с отказом содержания через queue.retry отклоняется без обращения к провайдеру; приложение скрывает кнопку. Старые явные safety-отказы распознаются по reason без массового создания событий. Окно позднего результата не является конечным сбоем.
|
|
10
|
+
|
|
11
|
+
Как обновить приложение: новые параметры необязательны. Для встроенных SQLite/PostgreSQL хранилищ SQL-миграция не требуется. Свой ImageJobStore должен сохранять новые JSON-поля failure/failureNotification и не удалять active-задачи с неподтверждённым событием. Полный договор: docs/image-failure-events.md.
|
|
12
|
+
|
|
13
|
+
Для worker событий добавлены необязательные `expectedJobId` и `expectedRun` в `queue.retry`. При смене выбранной задачи/запуска - null без обращения к провайдеру. Обычный ручной повтор не меняется; это CAS job/run, не транзакция с файлами приложения.
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- @ykurilov/agent-chat-attachments@0.82.0
|
|
18
|
+
- @ykurilov/agent-chat-core@0.82.0
|
|
19
|
+
- @ykurilov/agent-chat-react@0.82.0
|
|
20
|
+
- @ykurilov/agent-chat-web-tools@0.82.0
|
|
21
|
+
|
|
22
|
+
## 0.81.0
|
|
23
|
+
|
|
24
|
+
### Minor Changes
|
|
25
|
+
|
|
26
|
+
- 70ad21c: Снимки страниц, картинки провайдера и MCP без своего `fetch` приложения ходят закреплённым `fetch` из нового серверного входа `@ykurilov/agent-chat-web-tools/network`: имя проверяется в момент соединения, подмена DNS во внутреннюю сеть не пускает. Свой `fetch` приложения — граница доверия: закреплять соединение должен он сам. Предел `maxRunningPerUser` держится между копиями сервера и для ответов на подтверждение через новый метод `TaskStore.admitWithinLimit` (память, SQLite, PostgreSQL); своё хранилище без него теперь не запускается — метод надо добавить. Ответ на подтверждение фоновой задачи при занятых местах получает отказ `TurnRefusedError` с кодом `too_many_tasks`: вопрос ждёт, ответить можно, когда место освободится. Отказ проверки адреса в `person_link_view` больше не считается просмотром; битая %-кодировка в адресе экрана админа MCP даёт 404 вместо 500. Подробности и шаги обновления — `docs/audit-network.md`. SQL-миграций нет.
|
|
27
|
+
|
|
28
|
+
### Patch Changes
|
|
29
|
+
|
|
30
|
+
- Updated dependencies [038b6e2]
|
|
31
|
+
- Updated dependencies [70ad21c]
|
|
32
|
+
- Updated dependencies [58204d2]
|
|
33
|
+
- @ykurilov/agent-chat-react@0.81.0
|
|
34
|
+
- @ykurilov/agent-chat-attachments@0.81.0
|
|
35
|
+
- @ykurilov/agent-chat-web-tools@0.81.0
|
|
36
|
+
- @ykurilov/agent-chat-core@0.81.0
|
|
37
|
+
|
|
3
38
|
## 0.80.1
|
|
4
39
|
|
|
5
40
|
### Patch Changes
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/image-queue/image-job-store.ts
|
|
2
2
|
var isOpenImageJob = (job) => job.status === "generating" || job.lateUntil !== void 0;
|
|
3
|
+
var hasPendingImageFailure = (job) => job.failureNotification !== void 0 && job.failureNotification.deliveredAt === void 0;
|
|
3
4
|
var ImageJobKeyTakenError = class extends Error {
|
|
4
5
|
constructor(ownerId, key) {
|
|
5
6
|
super(`\u041A\u0430\u0440\u0442\u0438\u043D\u043A\u0438: \u043D\u0430 \u043C\u0435\u0441\u0442\u043E ${key} \u0443\u0436\u0435 \u0435\u0441\u0442\u044C \u043E\u0442\u043A\u0440\u044B\u0442\u0430\u044F \u0437\u0430\u0434\u0430\u0447\u0430`);
|
|
@@ -79,10 +80,11 @@ function memoryImageJobStore() {
|
|
|
79
80
|
|
|
80
81
|
export {
|
|
81
82
|
isOpenImageJob,
|
|
83
|
+
hasPendingImageFailure,
|
|
82
84
|
ImageJobKeyTakenError,
|
|
83
85
|
findErrorCause,
|
|
84
86
|
applyImageJobPatch,
|
|
85
87
|
imageJobBody,
|
|
86
88
|
memoryImageJobStore
|
|
87
89
|
};
|
|
88
|
-
//# sourceMappingURL=chunk-
|
|
90
|
+
//# sourceMappingURL=chunk-JRBKNWG3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/image-queue/image-job-store.ts"],"sourcesContent":["import type { ImageFailure } from \"../image-failure\";\nimport type { AspectRatio } from \"../image-catalog\";\n\n/**\n * Что с задачей картинки.\n * - `generating` — готовится: отправка, опрос, скачивание, доставка.\n * - `ready` — приёмник принял картинку.\n * - `failed` — не получилась; при `lateUntil` задача у провайдера ещё опрашивается и может прийти поздно.\n * - `superseded` — на то же место заказана новая картинка или задачу остановили.\n * - `discarded` — приёмник отказался от готовой картинки (место уже занято).\n */\nexport type ImageJobStatus = \"generating\" | \"ready\" | \"failed\" | \"superseded\" | \"discarded\";\n\n/** Оплата одной задачи провайдера. `usageId` — id записи расхода: вторая вставка того же id отвергается базой. */\nexport interface ImageJobCharge {\n usageId: string;\n modelId: string;\n providerId: string;\n costUsd: number;\n /** Запись расхода сделана. */\n recorded: boolean;\n}\n\n/** Задача шага, которая ушла провайдеру (или уходит прямо сейчас) и ещё не оплачена. */\nexport interface ImageJobTask {\n usageId: string;\n modelId: string;\n providerId: string;\n /** Цена модели: так задача оплачивается, если провайдер не сообщил свою (срок, обрыв, вытеснение). */\n priceUsd: number;\n sentAt: string;\n /** Id задачи в очереди провайдера; нет — отправка не закончилась или провайдер без очереди. */\n taskId?: string;\n}\n\n/** Шаг задачи: картинка, затем, если нужно, её же без фона. */\nexport interface ImageJobStep {\n task?: ImageJobTask;\n /** Адреса готового результата: дальше шаг не заказывается, только скачивается. */\n urls?: string[];\n charges: ImageJobCharge[];\n}\n\n/** Задача картинки, как она лежит в хранилище. Времена — ISO 8601. */\nexport interface ImageJobRecord {\n id: string;\n /** Место картинки: новый заказ с тем же `key` у того же владельца вытесняет прежний. */\n key: string;\n ownerId: string;\n chatId: string;\n turnId?: string;\n /** Группа для списка (например, приложение, в которое идут картинки). */\n group?: string;\n /** Назначение; `\"\"` — без назначений. */\n purpose: string;\n prompt: string;\n aspectRatio: AspectRatio;\n /** Данные приложения (путь, размер): отдаются приёмнику как есть. Только JSON. */\n data?: unknown;\n status: ImageJobStatus;\n reason?: string;\n failure?: ImageFailure;\n /** Outbox terminal failure: active до подтверждения; callback может повториться. */\n failureNotification?: { id: string; attempts: number; nextAttemptAt: string; deliveredAt?: string };\n /** Ответ приёмника (JSON). */\n result?: unknown;\n /** У задачи есть работа: опрос, позднее окно, незаписанный расход или неподтверждённое событие. Такие задачи подбирает старт сервера. */\n active: boolean;\n /** Номер запуска: «Повторить» начинает новый, прежний больше ничего не делает. */\n run: number;\n /** Номер попытки запуска, с 1. */\n attempt: number;\n attemptDeadline: string;\n /** Позднее окно: попытки кончились, а задача у провайдера ещё опрашивается. */\n lateUntil?: string;\n /** Готовая картинка не дошла до приёмника (скачивание, обработка, сам приёмник): «Повторить» сначала пробует тот же адрес. */\n deliveryFailed?: boolean;\n steps: ImageJobStep[];\n leaseOwner?: string;\n leaseUntil?: string;\n /** Версия записи: каждая правка условна по ней и прибавляет 1. */\n version: number;\n createdAt: string;\n updatedAt: string;\n finishedAt?: string;\n}\n\n/** Правка задачи: переданный `undefined` стирает поле. */\nexport type ImageJobPatch = Partial<Omit<ImageJobRecord, \"id\" | \"key\" | \"ownerId\" | \"chatId\" | \"createdAt\" | \"version\">>;\n\nexport interface ImageJobFilter {\n ownerId?: string;\n chatId?: string;\n key?: string;\n group?: string;\n}\n\n/** У задачи ещё идёт работа с провайдером: генерация или позднее окно. Открытая на место (`ownerId` + `key`) — одна. */\nexport const isOpenImageJob = (job: Pick<ImageJobRecord, \"status\" | \"lateUntil\">) => job.status === \"generating\" || job.lateUntil !== undefined;\n\n/** Долговечное уведомление ещё не подтверждено приложением. */\nexport const hasPendingImageFailure = (job: Pick<ImageJobRecord, \"failureNotification\">) => job.failureNotification !== undefined && job.failureNotification.deliveredAt === undefined;\n\n/** На это место (`ownerId` + `key`) уже есть открытая задача: вторую база не пускает, даже с двух копий сервера. */\nexport class ImageJobKeyTakenError extends Error {\n constructor(readonly ownerId: string, readonly key: string) {\n super(`Картинки: на место ${key} уже есть открытая задача`);\n this.name = \"ImageJobKeyTakenError\";\n }\n}\n\n/**\n * Хранилище задач картинок: память, SQLite, PostgreSQL (договор один). Открытая задача на место — одна: `insert` и\n * `update`, после которых их стало бы две, бросают `ImageJobKeyTakenError` и ничего не пишут.\n */\nexport interface ImageJobStore {\n insert(job: ImageJobRecord): Promise<void>;\n get(id: string): Promise<ImageJobRecord | null>;\n /** По порядку создания. */\n list(filter: ImageJobFilter): Promise<ImageJobRecord[]>;\n /** Условная запись: версия в базе равна `expectedVersion` — правка и версия +1; иначе `null`, запись прежняя. */\n update(id: string, patch: ImageJobPatch, expectedVersion: number): Promise<ImageJobRecord | null>;\n /** Задачи с работой (`active`), чья аренда свободна или истекла к `now`. */\n listResumable(now: string): Promise<ImageJobRecord[]>;\n /** Удаляет задачи без работы, законченные раньше `before`; ответ — сколько удалено. */\n deleteFinishedBefore(before: string): Promise<number>;\n close?(): Promise<void>;\n}\n\n/** Ошибка базы или её причина (`cause`, драйвер под drizzle), подходящая под `test`. */\nexport function findErrorCause(error: unknown, test: (cause: Record<string, unknown>) => boolean): boolean {\n for (let current = error, depth = 0; current !== null && typeof current === \"object\" && depth < 5; current = (current as { cause?: unknown }).cause, depth++) {\n if (test(current as Record<string, unknown>)) return true;\n }\n return false;\n}\n\n/** Колонки поиска; остальное хранилища держат одним JSON. */\nexport const IMAGE_JOB_COLUMNS = [\"id\", \"key\", \"ownerId\", \"chatId\", \"group\", \"status\", \"active\", \"leaseOwner\", \"leaseUntil\", \"version\", \"createdAt\", \"updatedAt\", \"finishedAt\"] as const;\n\n/** Запись с правкой: `undefined` в правке стирает поле. */\nexport function applyImageJobPatch(job: ImageJobRecord, patch: ImageJobPatch): ImageJobRecord {\n const next = { ...job } as Record<string, unknown>;\n for (const [field, value] of Object.entries(patch)) {\n if (value === undefined) delete next[field];\n else next[field] = value;\n }\n return next as unknown as ImageJobRecord;\n}\n\n/** Часть записи, которая хранится JSON: всё, кроме колонок поиска. */\nexport function imageJobBody(job: ImageJobRecord): Record<string, unknown> {\n const body = { ...job } as Record<string, unknown>;\n for (const column of IMAGE_JOB_COLUMNS) delete body[column];\n return body;\n}\n\nconst matches = (job: ImageJobRecord, filter: ImageJobFilter) =>\n (filter.ownerId === undefined || job.ownerId === filter.ownerId) &&\n (filter.chatId === undefined || job.chatId === filter.chatId) &&\n (filter.key === undefined || job.key === filter.key) &&\n (filter.group === undefined || job.group === filter.group);\n\n/** Хранилище в памяти: по умолчанию и для тестов, живёт до перезапуска. */\nexport function memoryImageJobStore(): ImageJobStore {\n const jobs = new Map<string, ImageJobRecord>();\n const copy = (job: ImageJobRecord) => structuredClone(job);\n const checkPlace = (job: ImageJobRecord) => {\n if (!isOpenImageJob(job)) return;\n for (const other of jobs.values()) {\n if (other.id !== job.id && other.ownerId === job.ownerId && other.key === job.key && isOpenImageJob(other)) throw new ImageJobKeyTakenError(job.ownerId, job.key);\n }\n };\n return {\n async insert(job) {\n if (jobs.has(job.id)) throw new Error(`Задача картинки ${job.id} уже есть`);\n checkPlace(job);\n jobs.set(job.id, copy(job));\n },\n async get(id) {\n const job = jobs.get(id);\n return job ? copy(job) : null;\n },\n async list(filter) {\n return [...jobs.values()].filter((job) => matches(job, filter)).sort((a, b) => a.createdAt.localeCompare(b.createdAt)).map(copy);\n },\n async update(id, patch, expectedVersion) {\n const job = jobs.get(id);\n if (!job || job.version !== expectedVersion) return null;\n const next = { ...applyImageJobPatch(job, structuredClone(patch)), version: job.version + 1 };\n checkPlace(next);\n jobs.set(id, next);\n return copy(next);\n },\n async listResumable(now) {\n return [...jobs.values()].filter((job) => job.active && (job.leaseUntil === undefined || job.leaseUntil < now)).map(copy);\n },\n async deleteFinishedBefore(before) {\n let deleted = 0;\n for (const job of jobs.values()) {\n if (!job.active && job.status !== \"generating\" && job.finishedAt !== undefined && job.finishedAt < before) {\n jobs.delete(job.id);\n deleted++;\n }\n }\n return deleted;\n },\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -2,14 +2,28 @@
|
|
|
2
2
|
var ImageGenerationError = class extends Error {
|
|
3
3
|
reason;
|
|
4
4
|
refusedBeforeWork;
|
|
5
|
+
kind;
|
|
6
|
+
providerCode;
|
|
5
7
|
constructor(reason, options) {
|
|
6
8
|
super(reason);
|
|
7
9
|
this.name = "ImageGenerationError";
|
|
8
10
|
this.reason = reason;
|
|
9
11
|
this.refusedBeforeWork = options.refusedBeforeWork;
|
|
12
|
+
this.kind = options.kind ?? "unknown";
|
|
13
|
+
this.providerCode = options.providerCode;
|
|
10
14
|
}
|
|
11
15
|
};
|
|
12
16
|
|
|
17
|
+
// src/image-failure.ts
|
|
18
|
+
function providerFailureKind(status, code, message) {
|
|
19
|
+
if (/safety_violations\s*=\s*\[[^\]]+\]|rejected by the safety system|content_policy_violation|content policy violation/i.test(message)) return "content";
|
|
20
|
+
if (status === 402 || code === "insufficientCredits") return "limits";
|
|
21
|
+
if (status === 408 || status === 429 || status !== void 0 && status >= 500 || ["timeoutProvider", "providerRateLimitExceeded", "serviceOverloaded", "inferenceRateLimitExceeded"].includes(code ?? "")) return "transient";
|
|
22
|
+
if (status !== void 0 && status >= 400 && status < 500 || code === "invalidApiKey") return "configuration";
|
|
23
|
+
return "unknown";
|
|
24
|
+
}
|
|
25
|
+
var canRetryImageFailure = (kind) => kind === void 0 || kind === "transient" || kind === "unknown";
|
|
26
|
+
|
|
13
27
|
// src/hide-secrets.ts
|
|
14
28
|
var MIN_SECRET_LENGTH = 4;
|
|
15
29
|
function hideSecrets(text, connection) {
|
|
@@ -112,13 +126,13 @@ async function submit(request, connection) {
|
|
|
112
126
|
answer = await readAnswer(response);
|
|
113
127
|
} catch (error) {
|
|
114
128
|
if (request.abortSignal?.aborted) throw request.abortSignal.reason ?? error;
|
|
115
|
-
throw new ImageGenerationError("\u041F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D", { refusedBeforeWork: false });
|
|
129
|
+
throw new ImageGenerationError("\u041F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D", { refusedBeforeWork: false, kind: "transient" });
|
|
116
130
|
}
|
|
117
131
|
const code = answerCode(response, answer);
|
|
118
132
|
const taskId = answer?.data?.taskId;
|
|
119
133
|
if (code === 200 && typeof taskId === "string" && taskId.length > 0) return taskId;
|
|
120
|
-
if (code >= 400 && code < 500 && code !== 408) throw new ImageGenerationError(reasonOf(code, answer, connection), { refusedBeforeWork: true });
|
|
121
|
-
throw new ImageGenerationError(`\u041F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u043E\u0448\u0438\u0431\u043A\u043E\u0439: ${reasonOf(code, answer, connection)}`, { refusedBeforeWork: false });
|
|
134
|
+
if (code >= 400 && code < 500 && code !== 408) throw new ImageGenerationError(reasonOf(code, answer, connection), { refusedBeforeWork: true, kind: providerFailureKind(code, void 0, typeof answer?.msg === "string" ? answer.msg : "") });
|
|
135
|
+
throw new ImageGenerationError(`\u041F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u043E\u0448\u0438\u0431\u043A\u043E\u0439: ${reasonOf(code, answer, connection)}`, { refusedBeforeWork: false, kind: providerFailureKind(code, void 0, typeof answer?.msg === "string" ? answer.msg : "") });
|
|
122
136
|
}
|
|
123
137
|
async function check(taskId, connection, abortSignal) {
|
|
124
138
|
const response = await connection.fetch(`${connection.baseURL ?? KIE_URL}/jobs/recordInfo?taskId=${encodeURIComponent(taskId)}`, {
|
|
@@ -129,7 +143,7 @@ async function check(taskId, connection, abortSignal) {
|
|
|
129
143
|
const code = answerCode(response, answer);
|
|
130
144
|
if (code === UNKNOWN_TASK_CODE && !answer?.data) return { status: "failed", reason: "\u0417\u0430\u0434\u0430\u0447\u0430 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u0430 \u0443 \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440\u0430" };
|
|
131
145
|
if (code >= 500 || code === 408 || code === 429 || !answer) throw new Error(`KIE \u043E\u0442\u0432\u0435\u0442\u0438\u043B ${code}`);
|
|
132
|
-
if (code !== 200) throw new ImageGenerationError(reasonOf(code, answer, connection), { refusedBeforeWork: false });
|
|
146
|
+
if (code !== 200) throw new ImageGenerationError(reasonOf(code, answer, connection), { refusedBeforeWork: false, kind: providerFailureKind(code, void 0, typeof answer?.msg === "string" ? answer.msg : "") });
|
|
133
147
|
const data = answer.data ?? {};
|
|
134
148
|
const costUsd = costOf(data.creditsConsumed);
|
|
135
149
|
if (data.state === "success") {
|
|
@@ -140,7 +154,7 @@ async function check(taskId, connection, abortSignal) {
|
|
|
140
154
|
}
|
|
141
155
|
if (data.state === "fail") {
|
|
142
156
|
const reason = [data.failCode, data.failMsg].filter((part) => (typeof part === "string" || typeof part === "number") && String(part).length > 0).join(": ");
|
|
143
|
-
return { status: "failed", reason: hideSecrets(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", connection).slice(0, MAX_REASON_LENGTH), ...costUsd !== void 0 && { costUsd } };
|
|
157
|
+
return { status: "failed", failureKind: providerFailureKind(void 0, typeof data.failCode === "string" ? data.failCode : void 0, reason), ...data.failCode !== void 0 && { providerCode: hideSecrets(String(data.failCode), connection).slice(0, 100) }, reason: hideSecrets(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", connection).slice(0, MAX_REASON_LENGTH), ...costUsd !== void 0 && { costUsd } };
|
|
144
158
|
}
|
|
145
159
|
return { status: "pending" };
|
|
146
160
|
}
|
|
@@ -159,7 +173,7 @@ var kieProvider = {
|
|
|
159
173
|
...request.abortSignal && { abortSignal: request.abortSignal }
|
|
160
174
|
});
|
|
161
175
|
if (outcome.status === "ready") return outcome.results;
|
|
162
|
-
if (outcome.status === "failed") throw new ImageGenerationError(outcome.reason, { refusedBeforeWork: false });
|
|
176
|
+
if (outcome.status === "failed") throw new ImageGenerationError(outcome.reason, { refusedBeforeWork: false, kind: outcome.failureKind, providerCode: outcome.providerCode });
|
|
163
177
|
throw new ImageGenerationError(`\u041F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440 \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u0437\u0430 ${Math.round(request.timeoutMs / 1e3)} \u0441`, { refusedBeforeWork: false });
|
|
164
178
|
}
|
|
165
179
|
};
|
|
@@ -220,20 +234,23 @@ var runwareProvider = {
|
|
|
220
234
|
} catch (error) {
|
|
221
235
|
if (request.abortSignal?.aborted) throw request.abortSignal.reason ?? error;
|
|
222
236
|
if (timeout.signal.aborted) {
|
|
223
|
-
throw new ImageGenerationError(`\u041F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440 \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u0437\u0430 ${Math.round(request.timeoutMs / 1e3)} \u0441`, { refusedBeforeWork: false });
|
|
237
|
+
throw new ImageGenerationError(`\u041F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440 \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u0437\u0430 ${Math.round(request.timeoutMs / 1e3)} \u0441`, { refusedBeforeWork: false, kind: "transient" });
|
|
224
238
|
}
|
|
225
|
-
throw new ImageGenerationError("\u041F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D", { refusedBeforeWork: false });
|
|
239
|
+
throw new ImageGenerationError("\u041F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D", { refusedBeforeWork: false, kind: "transient" });
|
|
226
240
|
} finally {
|
|
227
241
|
clearTimeout(timer);
|
|
228
242
|
}
|
|
229
243
|
const reason = errorsText(answer?.errors, connection);
|
|
244
|
+
const firstCode = answer?.errors?.find((error) => typeof error.code === "string")?.code;
|
|
245
|
+
const providerCode = typeof firstCode === "string" ? hideSecrets(firstCode, connection).slice(0, 100) : void 0;
|
|
246
|
+
const classification = { kind: providerFailureKind(response.status, providerCode, (answer?.errors ?? []).map((error) => typeof error.message === "string" ? error.message : "").join("; ")), ...providerCode && { providerCode } };
|
|
230
247
|
if (response.status >= 400 && response.status < 500 && response.status !== 408) {
|
|
231
|
-
throw new ImageGenerationError(reason ?? `\u041F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440 \u043E\u0442\u043A\u0430\u0437\u0430\u043B: ${response.status}`, { refusedBeforeWork: true });
|
|
248
|
+
throw new ImageGenerationError(reason ?? `\u041F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440 \u043E\u0442\u043A\u0430\u0437\u0430\u043B: ${response.status}`, { refusedBeforeWork: true, ...classification });
|
|
232
249
|
}
|
|
233
|
-
if (!response.ok) throw new ImageGenerationError(`\u041F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u043E\u0448\u0438\u0431\u043A\u043E\u0439 ${response.status}${reason ? `: ${reason}` : ""}`, { refusedBeforeWork: false });
|
|
250
|
+
if (!response.ok) throw new ImageGenerationError(`\u041F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u043E\u0448\u0438\u0431\u043A\u043E\u0439 ${response.status}${reason ? `: ${reason}` : ""}`, { refusedBeforeWork: false, ...classification });
|
|
234
251
|
const image = answer?.data?.[0];
|
|
235
252
|
if (!image || typeof image.imageURL !== "string") {
|
|
236
|
-
throw new ImageGenerationError(reason ?? "\u041F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440 \u043D\u0435 \u0432\u0435\u0440\u043D\u0443\u043B \u043A\u0430\u0440\u0442\u0438\u043D\u043A\u0443", { refusedBeforeWork: false });
|
|
253
|
+
throw new ImageGenerationError(reason ?? "\u041F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440 \u043D\u0435 \u0432\u0435\u0440\u043D\u0443\u043B \u043A\u0430\u0440\u0442\u0438\u043D\u043A\u0443", { refusedBeforeWork: false, ...classification });
|
|
237
254
|
}
|
|
238
255
|
const cost = typeof image.cost === "number" && Number.isFinite(image.cost) && image.cost >= 0 ? image.cost : void 0;
|
|
239
256
|
return [{ url: image.imageURL, ...cost !== void 0 && { costUsd: cost } }];
|
|
@@ -383,6 +400,8 @@ function extendImageCatalog(base, change) {
|
|
|
383
400
|
}
|
|
384
401
|
|
|
385
402
|
export {
|
|
403
|
+
providerFailureKind,
|
|
404
|
+
canRetryImageFailure,
|
|
386
405
|
ImageGenerationError,
|
|
387
406
|
hideSecrets,
|
|
388
407
|
waitForImageTask,
|
|
@@ -394,4 +413,4 @@ export {
|
|
|
394
413
|
standardImageCatalog,
|
|
395
414
|
extendImageCatalog
|
|
396
415
|
};
|
|
397
|
-
//# sourceMappingURL=chunk-
|
|
416
|
+
//# sourceMappingURL=chunk-W74LUWVQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/image-generation-error.ts","../src/image-failure.ts","../src/hide-secrets.ts","../src/image-task-polling.ts","../src/kie-provider.ts","../src/runware-provider.ts","../src/image-catalog.ts"],"sourcesContent":["import type { ImageFailureKind } from \"./image-failure\";\n\n/**\n * Сбой одной задачи картинки. `reason` — короткий текст для модели (без ключа и заголовков), `refusedBeforeWork` —\n * провайдер отказал до работы (неверный ключ, нет денег, неверные параметры): расхода нет.\n */\nexport class ImageGenerationError extends Error {\n readonly reason: string;\n readonly refusedBeforeWork: boolean;\n\n readonly kind: ImageFailureKind;\n readonly providerCode?: string;\n\n constructor(reason: string, options: { refusedBeforeWork: boolean; kind?: ImageFailureKind; providerCode?: string }) {\n super(reason);\n this.name = \"ImageGenerationError\";\n this.reason = reason;\n this.refusedBeforeWork = options.refusedBeforeWork;\n this.kind = options.kind ?? \"unknown\";\n this.providerCode = options.providerCode;\n }\n}\n","/** Причина конечного сбоя. Неизвестное не считается временным сбоем. */\nexport type ImageFailureKind = \"transient\" | \"content\" | \"limits\" | \"configuration\" | \"delivery\" | \"unknown\";\n\nexport interface ImageFailure {\n kind: ImageFailureKind;\n /** Короткая причина без ключей и заголовков подключения. */\n reason: string;\n providerCode?: string;\n}\n\n/** Классифицирует только ответ провайдера, никогда не исходный prompt. */\nexport function providerFailureKind(status: number | undefined, code: string | undefined, message: string): ImageFailureKind {\n // Явные признаки safety-отказа из provider error; одно упоминание темы в prompt сюда не попадает.\n if (/safety_violations\\s*=\\s*\\[[^\\]]+\\]|rejected by the safety system|content_policy_violation|content policy violation/i.test(message)) return \"content\";\n if (status === 402 || code === \"insufficientCredits\") return \"limits\";\n if (status === 408 || status === 429 || (status !== undefined && status >= 500) || [\"timeoutProvider\", \"providerRateLimitExceeded\", \"serviceOverloaded\", \"inferenceRateLimitExceeded\"].includes(code ?? \"\")) return \"transient\";\n if ((status !== undefined && status >= 400 && status < 500) || code === \"invalidApiKey\") return \"configuration\";\n return \"unknown\";\n}\n\nexport const canRetryImageFailure = (kind: ImageFailureKind | undefined) => kind === undefined || kind === \"transient\" || kind === \"unknown\";\n","/** Значения короче этого не прячутся: «1» или «on» встречаются в любом тексте. */\nconst MIN_SECRET_LENGTH = 4;\n\n/** Текст без ключа провайдера и значений заголовков подключения (прокси): для ошибок, итога вызова и журнала. */\nexport function hideSecrets(text: string, connection: { apiKey?: string; headers?: Record<string, string> }): string {\n const secrets = [connection.apiKey, ...Object.values(connection.headers ?? {})]\n .filter((value): value is string => typeof value === \"string\" && value.length >= MIN_SECRET_LENGTH)\n .sort((a, b) => b.length - a.length);\n return secrets.reduce((result, secret) => result.split(secret).join(\"[скрыто]\"), text);\n}\n","import type { ImageTaskState } from \"./image-catalog\";\nimport { ImageGenerationError } from \"./image-generation-error\";\n\n/** Первая проверка задачи — через это время: быстрее картинка не бывает готова. */\nconst FIRST_CHECK_MS = 2_000;\n/** Каждый следующий интервал во столько раз длиннее… */\nconst INTERVAL_GROWTH = 1.5;\n/** …но не длиннее этого: готовую картинку не ждём лишнего. */\nconst MAX_INTERVAL_MS = 10_000;\n\nexport interface WaitForImageTaskInput {\n check(abortSignal: AbortSignal | undefined): Promise<ImageTaskState>;\n /** Срок по `Date.now()`: дальше опроса нет, итог — `timeout`. */\n deadline: number;\n /** «Стоп»: ожидание обрывается сразу, наружу — причина сигнала. */\n abortSignal?: AbortSignal;\n /** Первая проверка сразу, а не через интервал: задача отправлена давно (повтор, фоновый опрос после перерыва). */\n checkNow?: boolean;\n}\n\nexport type WaitedImageTask = Exclude<ImageTaskState, { status: \"pending\" }> | { status: \"timeout\" };\n\n/** Пауза до `ms`, которую «Стоп» обрывает. Таймер не держит процесс: ждать фоновую задачу при выходе незачем. */\nfunction pause(ms: number, signal: AbortSignal | undefined): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) return reject(signal.reason);\n const onAbort = () => {\n clearTimeout(timer);\n reject(signal!.reason);\n };\n const timer = setTimeout(() => {\n signal?.removeEventListener(\"abort\", onAbort);\n resolve();\n }, ms);\n (timer as { unref?: () => void }).unref?.();\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n });\n}\n\n/**\n * Опрос задачи в очереди провайдера: 2 с, затем интервал растёт в полтора раза до 10 с. Последняя проверка — ровно в\n * срок. Временный сбой проверки (сеть, 5xx) не прерывает опрос; `ImageGenerationError` — прерывает.\n */\nexport async function waitForImageTask(input: WaitForImageTaskInput): Promise<WaitedImageTask> {\n let interval = FIRST_CHECK_MS;\n let first = true;\n for (;;) {\n if (!(first && input.checkNow)) {\n const left = input.deadline - Date.now();\n if (left <= 0) return { status: \"timeout\" };\n await pause(Math.min(interval, left), input.abortSignal);\n interval = Math.min(interval * INTERVAL_GROWTH, MAX_INTERVAL_MS);\n }\n first = false;\n try {\n const state = await input.check(input.abortSignal);\n if (state.status !== \"pending\") return state;\n } catch (error) {\n if (input.abortSignal?.aborted) throw input.abortSignal.reason ?? error;\n if (error instanceof ImageGenerationError) throw error;\n }\n if (Date.now() >= input.deadline) return { status: \"timeout\" };\n }\n}\n","import type { ImageGenerationRequest, ImageGenerationResult, ImageProviderConnection, ImageProviderDescription, ImageTaskState } from \"./image-catalog\";\nimport { providerFailureKind } from \"./image-failure\";\nimport { ImageGenerationError } from \"./image-generation-error\";\nimport { hideSecrets } from \"./hide-secrets\";\nimport { waitForImageTask } from \"./image-task-polling\";\n\nconst KIE_URL = \"https://api.kie.ai/api/v1\";\n/** Цена кредита KIE (пакеты от $5 за 1000 кредитов). */\nconst CREDIT_USD = 0.005;\n/** Причина для модели — не длиннее этого, знаков. */\nconst MAX_REASON_LENGTH = 300;\n/** Ответ KIE на неизвестную задачу: `{ code: 422, msg: \"recordInfo is null\" }`. */\nconst UNKNOWN_TASK_CODE = 422;\n\ninterface KieAnswer {\n code?: unknown;\n msg?: unknown;\n data?: {\n taskId?: unknown;\n state?: unknown;\n resultJson?: unknown;\n failCode?: unknown;\n failMsg?: unknown;\n creditsConsumed?: unknown;\n } | null;\n}\n\nconst isStatus = (code: number) => Number.isInteger(code) && code >= 100 && code < 600;\n\nasync function readAnswer(response: Response): Promise<KieAnswer | undefined> {\n try {\n return (await response.json()) as KieAnswer;\n } catch {\n return undefined;\n }\n}\n\n/** Код ответа: KIE часто кладёт его в тело при HTTP 200. */\nfunction answerCode(response: Response, answer: KieAnswer | undefined): number {\n const code = typeof answer?.code === \"number\" ? answer.code : response.status;\n return response.ok && isStatus(code) ? code : response.status;\n}\n\nconst reasonOf = (code: number, answer: KieAnswer | undefined, connection: ImageProviderConnection) =>\n hideSecrets(`KIE ${code}${typeof answer?.msg === \"string\" && answer.msg ? `: ${answer.msg}` : \"\"}`, connection).slice(0, MAX_REASON_LENGTH);\n\nconst costOf = (credits: unknown) => (typeof credits === \"number\" && Number.isFinite(credits) && credits >= 0 ? credits * CREDIT_USD : undefined);\n\nfunction headersOf(connection: ImageProviderConnection): Record<string, string> {\n return { ...connection.headers, Authorization: `Bearer ${connection.apiKey}`, \"Content-Type\": \"application/json\" };\n}\n\n/** Тело `createTask`: модель и `input` с полями профиля. Модели правки получают исходник, а пропорцию берут у него. */\nfunction taskBody(request: ImageGenerationRequest) {\n return {\n model: request.providerModelId,\n input: {\n prompt: request.prompt,\n ...(request.imageUrl === undefined ? { aspect_ratio: request.aspectRatio ?? `${request.width}:${request.height}` } : { image_url: request.imageUrl }),\n ...request.providerSettings,\n },\n };\n}\n\n/** Адреса из `resultJson` (JSON-строка `{\"resultUrls\":[…]}`). */\nfunction resultUrls(resultJson: unknown): string[] {\n if (typeof resultJson !== \"string\") return [];\n try {\n const urls = (JSON.parse(resultJson) as { resultUrls?: unknown }).resultUrls;\n return Array.isArray(urls) ? urls.filter((url): url is string => typeof url === \"string\" && url.length > 0) : [];\n } catch {\n return [];\n }\n}\n\nasync function submit(request: ImageGenerationRequest, connection: ImageProviderConnection): Promise<string> {\n let response: Response;\n let answer: KieAnswer | undefined;\n try {\n response = await connection.fetch(`${connection.baseURL ?? KIE_URL}/jobs/createTask`, {\n method: \"POST\",\n headers: headersOf(connection),\n body: JSON.stringify(taskBody(request)),\n ...(request.abortSignal && { signal: request.abortSignal }),\n });\n answer = await readAnswer(response);\n } catch (error) {\n if (request.abortSignal?.aborted) throw request.abortSignal.reason ?? error;\n throw new ImageGenerationError(\"Провайдер недоступен\", { refusedBeforeWork: false, kind: \"transient\" });\n }\n const code = answerCode(response, answer);\n const taskId = answer?.data?.taskId;\n if (code === 200 && typeof taskId === \"string\" && taskId.length > 0) return taskId;\n if (code >= 400 && code < 500 && code !== 408) throw new ImageGenerationError(reasonOf(code, answer, connection), { refusedBeforeWork: true, kind: providerFailureKind(code, undefined, typeof answer?.msg === \"string\" ? answer.msg : \"\") });\n throw new ImageGenerationError(`Провайдер ответил ошибкой: ${reasonOf(code, answer, connection)}`, { refusedBeforeWork: false, kind: providerFailureKind(code, undefined, typeof answer?.msg === \"string\" ? answer.msg : \"\") });\n}\n\nasync function check(taskId: string, connection: ImageProviderConnection, abortSignal?: AbortSignal): Promise<ImageTaskState> {\n const response = await connection.fetch(`${connection.baseURL ?? KIE_URL}/jobs/recordInfo?taskId=${encodeURIComponent(taskId)}`, {\n headers: headersOf(connection),\n ...(abortSignal && { signal: abortSignal }),\n });\n const answer = await readAnswer(response);\n const code = answerCode(response, answer);\n if (code === UNKNOWN_TASK_CODE && !answer?.data) return { status: \"failed\", reason: \"Задача не найдена у провайдера\" };\n // Временный сбой: опрос повторит.\n if (code >= 500 || code === 408 || code === 429 || !answer) throw new Error(`KIE ответил ${code}`);\n if (code !== 200) throw new ImageGenerationError(reasonOf(code, answer, connection), { refusedBeforeWork: false, kind: providerFailureKind(code, undefined, typeof answer?.msg === \"string\" ? answer.msg : \"\") });\n\n const data = answer.data ?? {};\n const costUsd = costOf(data.creditsConsumed);\n if (data.state === \"success\") {\n const urls = resultUrls(data.resultJson);\n if (urls.length === 0) return { status: \"failed\", reason: \"Провайдер не вернул картинку\", ...(costUsd !== undefined && { costUsd }) };\n // Цена — за задачу целиком: на первом адресе.\n const results: ImageGenerationResult[] = urls.map((url, index) => ({ url, ...(index === 0 && costUsd !== undefined && { costUsd }) }));\n return { status: \"ready\", results };\n }\n if (data.state === \"fail\") {\n const reason = [data.failCode, data.failMsg].filter((part) => (typeof part === \"string\" || typeof part === \"number\") && String(part).length > 0).join(\": \");\n return { status: \"failed\", failureKind: providerFailureKind(undefined, typeof data.failCode === \"string\" ? data.failCode : undefined, reason), ...(data.failCode !== undefined && { providerCode: hideSecrets(String(data.failCode), connection).slice(0, 100) }), reason: hideSecrets(reason || \"Провайдер не смог создать картинку\", connection).slice(0, MAX_REASON_LENGTH), ...(costUsd !== undefined && { costUsd }) };\n }\n return { status: \"pending\" };\n}\n\n/**\n * KIE.ai: задача в очереди (`createTask`) и проверка её состояния (`recordInfo`), Bearer-ключ `KIE_API_KEY`. Цена —\n * `creditsConsumed` × $0.005. Отказ 4xx при отправке — до работы, расхода нет; 5xx и обрыв — задача могла уйти.\n */\nexport const kieProvider: ImageProviderDescription = {\n id: \"kie\",\n displayName: \"KIE.ai\",\n apiKeyEnv: \"KIE_API_KEY\",\n baseURL: KIE_URL,\n queue: { submit, check },\n async generate(request, connection) {\n const deadline = Date.now() + request.timeoutMs;\n const taskId = await submit(request, connection);\n const outcome = await waitForImageTask({\n check: (abortSignal) => check(taskId, connection, abortSignal),\n deadline,\n ...(request.abortSignal && { abortSignal: request.abortSignal }),\n });\n if (outcome.status === \"ready\") return outcome.results;\n if (outcome.status === \"failed\") throw new ImageGenerationError(outcome.reason, { refusedBeforeWork: false, kind: outcome.failureKind, providerCode: outcome.providerCode });\n throw new ImageGenerationError(`Провайдер не ответил за ${Math.round(request.timeoutMs / 1000)} с`, { refusedBeforeWork: false });\n },\n};\n","import type { ImageGenerationRequest, ImageProviderConnection, ImageProviderDescription } from \"./image-catalog\";\nimport { providerFailureKind } from \"./image-failure\";\nimport { ImageGenerationError } from \"./image-generation-error\";\nimport { hideSecrets } from \"./hide-secrets\";\n\nconst RUNWARE_URL = \"https://api.runware.ai/v1\";\n/** Причина для модели — не длиннее этого, знаков. */\nconst MAX_REASON_LENGTH = 300;\n/** Формат файла у Runware; без формата — JPG, как было. */\nconst outputFormats = { png: \"PNG\", jpeg: \"JPG\", webp: \"WEBP\" } as const;\n\ninterface RunwareError {\n code?: unknown;\n message?: unknown;\n}\ninterface RunwareAnswer {\n data?: Array<{ imageURL?: unknown; cost?: unknown }>;\n errors?: RunwareError[];\n}\n\n/** Задача `imageInference` по contracts/images.md: одна картинка, адрес вместо байтов, цена в ответе. */\nfunction taskBody(request: ImageGenerationRequest) {\n return [\n {\n taskType: \"imageInference\",\n taskUUID: request.taskUUID,\n model: request.providerModelId,\n positivePrompt: request.prompt,\n width: request.width,\n height: request.height,\n numberResults: 1,\n outputType: \"URL\",\n outputFormat: outputFormats[request.outputFormat ?? \"jpeg\"],\n deliveryMethod: \"sync\",\n includeCost: true,\n ...request.providerSettings,\n },\n ];\n}\n\n/** «код: текст» первых ошибок Runware, без ключа и заголовков подключения. */\nfunction errorsText(errors: readonly RunwareError[] | undefined, connection: ImageProviderConnection): string | undefined {\n const lines = (errors ?? [])\n .map((error) => [error.code, error.message].filter((part) => typeof part === \"string\" && part.length > 0).join(\": \"))\n .filter((line) => line.length > 0);\n if (lines.length === 0) return undefined;\n return hideSecrets(lines.join(\"; \"), connection).slice(0, MAX_REASON_LENGTH);\n}\n\nasync function readAnswer(response: Response): Promise<RunwareAnswer | undefined> {\n try {\n return (await response.json()) as RunwareAnswer;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Runware: `POST` задачи с Bearer-ключом и заголовками подключения. Отказ 4xx — до работы, расхода нет; 5xx, пустой\n * ответ, обрыв и срок — задача могла выполниться. «Стоп» пробрасывается как есть.\n */\nexport const runwareProvider: ImageProviderDescription = {\n id: \"runware\",\n displayName: \"Runware\",\n apiKeyEnv: \"RUNWARE_API_KEY\",\n baseURL: RUNWARE_URL,\n async generate(request, connection) {\n const timeout = new AbortController();\n const timer = setTimeout(() => timeout.abort(new DOMException(\"timeout\", \"TimeoutError\")), request.timeoutMs);\n const signal = request.abortSignal ? AbortSignal.any([request.abortSignal, timeout.signal]) : timeout.signal;\n let response: Response;\n let answer: RunwareAnswer | undefined;\n try {\n response = await connection.fetch(connection.baseURL ?? RUNWARE_URL, {\n method: \"POST\",\n headers: { ...connection.headers, Authorization: `Bearer ${connection.apiKey}`, \"Content-Type\": \"application/json\" },\n body: JSON.stringify(taskBody(request)),\n signal,\n });\n answer = await readAnswer(response);\n } catch (error) {\n if (request.abortSignal?.aborted) throw request.abortSignal.reason ?? error;\n if (timeout.signal.aborted) {\n throw new ImageGenerationError(`Провайдер не ответил за ${Math.round(request.timeoutMs / 1000)} с`, { refusedBeforeWork: false, kind: \"transient\" });\n }\n throw new ImageGenerationError(\"Провайдер недоступен\", { refusedBeforeWork: false, kind: \"transient\" });\n } finally {\n clearTimeout(timer);\n }\n\n const reason = errorsText(answer?.errors, connection);\n const firstCode = answer?.errors?.find((error) => typeof error.code === \"string\")?.code;\n const providerCode = typeof firstCode === \"string\" ? hideSecrets(firstCode, connection).slice(0, 100) : undefined;\n const classification = { kind: providerFailureKind(response.status, providerCode, (answer?.errors ?? []).map((error) => typeof error.message === \"string\" ? error.message : \"\").join(\"; \")), ...(providerCode && { providerCode }) };\n if (response.status >= 400 && response.status < 500 && response.status !== 408) {\n throw new ImageGenerationError(reason ?? `Провайдер отказал: ${response.status}`, { refusedBeforeWork: true, ...classification });\n }\n if (!response.ok) throw new ImageGenerationError(`Провайдер ответил ошибкой ${response.status}${reason ? `: ${reason}` : \"\"}`, { refusedBeforeWork: false, ...classification });\n const image = answer?.data?.[0];\n if (!image || typeof image.imageURL !== \"string\") {\n throw new ImageGenerationError(reason ?? \"Провайдер не вернул картинку\", { refusedBeforeWork: false, ...classification });\n }\n const cost = typeof image.cost === \"number\" && Number.isFinite(image.cost) && image.cost >= 0 ? image.cost : undefined;\n return [{ url: image.imageURL, ...(cost !== undefined && { costUsd: cost }) }];\n },\n};\n","import type { ImageFailureKind } from \"./image-failure\";\nimport { kieProvider } from \"./kie-provider\";\nimport { runwareProvider } from \"./runware-provider\";\n\n/** Пропорции, которые модель может заказать у `generate_images`. */\nexport type AspectRatio = \"1:1\" | \"3:2\" | \"2:3\" | \"16:9\" | \"9:16\";\n\nexport const aspectRatios: readonly AspectRatio[] = [\"1:1\", \"3:2\", \"2:3\", \"16:9\", \"9:16\"];\n\n/** Качество GPT Image у Runware. */\nexport type ImageQuality = \"low\" | \"medium\" | \"high\" | \"auto\";\n\nexport const imageQualities: readonly ImageQuality[] = [\"low\", \"medium\", \"high\", \"auto\"];\n\n/** Размер картинки: `default` — размер модели по умолчанию (`aspectRatios`), `1k` — длинная сторона около 1024. */\nexport type ImageSize = \"default\" | \"1k\";\n\nexport const imageSizes: readonly ImageSize[] = [\"default\", \"1k\"];\n\n/** Стороны картинки для каждой пропорции. */\nexport type ImageSizesByAspectRatio = Record<AspectRatio, { width: number; height: number }>;\n\n/** Размер модели, кроме размера по умолчанию: стороны и цена картинки в нём. */\nexport interface ImageSizeOption {\n aspectRatios: ImageSizesByAspectRatio;\n /** Цена картинки этого размера в качестве по умолчанию; нет — `pricePerImageUsd` модели. */\n pricePerImageUsd?: number;\n}\n\n/** Формат файла картинки. */\nexport type ImageFileFormat = \"png\" | \"jpeg\" | \"webp\";\n\n/** Заказ одной картинки провайдеру. */\nexport interface ImageGenerationRequest {\n /** Id задачи у провайдера: свой у каждой картинки. */\n taskUUID: string;\n /** Id модели у этого провайдера (`ImageModelProfile.providerModelIds[provider.id]`). */\n providerModelId: string;\n prompt: string;\n width: number;\n height: number;\n /** Пропорция для провайдеров, которые принимают её строкой (KIE): `ImageModelProfile.aspectRatioValues` или своя. */\n aspectRatio?: string;\n /** Исходная картинка для моделей правки (разделение на слои): адрес готовой картинки предыдущего шага. */\n imageUrl?: string;\n /** Формат файла; не указан — формат провайдера по умолчанию (у Runware JPG). Прозрачному фону нужен `png`. */\n outputFormat?: ImageFileFormat;\n /** Настройки модели у провайдера из профиля (`ImageModelProfile.providerSettings`) с качеством и фоном приложения. */\n providerSettings?: Record<string, unknown>;\n /** «Стоп» хода. */\n abortSignal?: AbortSignal;\n /** Срок одной генерации. */\n timeoutMs: number;\n}\n\n/** Как достучаться до провайдера: ключ из окружения сервера, адрес и заголовки подключения (прокси). */\nexport interface ImageProviderConnection {\n apiKey: string;\n baseURL?: string;\n headers?: Record<string, string>;\n fetch: typeof globalThis.fetch;\n}\n\n/** Результат одной задачи: адрес готовой картинки и цена, если провайдер её сообщил. */\nexport interface ImageGenerationResult {\n url: string;\n costUsd?: number;\n}\n\n/** Состояние задачи в очереди провайдера. `costUsd` провала — если провайдер сообщил, сколько списал. */\nexport type ImageTaskState =\n | { status: \"pending\" }\n | { status: \"ready\"; results: ImageGenerationResult[] }\n | { status: \"failed\"; reason: string; costUsd?: number; failureKind?: ImageFailureKind; providerCode?: string };\n\n/**\n * Очередь провайдера (KIE и подобные): задача отправляется и проверяется отдельно. Так генерация переживает срок\n * вызова: опрос продолжается в фоне, а повтор того же вызова сначала проверяет старую задачу, а не платит за новую.\n * Отказ отправки — `ImageGenerationError` (как у `generate`). Сбой проверки: `ImageGenerationError` — проверять дальше\n * бессмысленно (неверный ключ), любая другая ошибка — временная, опрос повторит.\n */\nexport interface ImageTaskQueue {\n /** Отправить задачу; ответ — id задачи у провайдера. */\n submit(request: ImageGenerationRequest, connection: ImageProviderConnection): Promise<string>;\n check(taskId: string, connection: ImageProviderConnection, abortSignal?: AbortSignal): Promise<ImageTaskState>;\n}\n\n/**\n * Провайдер картинок (Runware и подобные). Сбой задачи — `ImageGenerationError`: его причина уходит модели, а\n * `refusedBeforeWork` говорит, что провайдер отказал до работы и расхода нет. Любая другая ошибка — «провайдер\n * недоступен», текст её наружу не идёт (в нём могут оказаться ключ или адрес).\n */\nexport interface ImageProviderDescription {\n id: string;\n displayName: string;\n /** Имя переменной окружения с ключом: `RUNWARE_API_KEY`. */\n apiKeyEnv: string;\n /** Адрес по умолчанию: `https://api.runware.ai/v1`. */\n baseURL?: string;\n headers?: Record<string, string>;\n /** Задача целиком: отправка и ожидание результата не дольше `request.timeoutMs`. */\n generate(request: ImageGenerationRequest, connection: ImageProviderConnection): Promise<ImageGenerationResult[]>;\n /** Есть у провайдеров с очередью: тогда `createImageGeneration` ведёт задачу сам (фоновый опрос, безопасный повтор). */\n queue?: ImageTaskQueue;\n}\n\n/** Профиль модели картинок. */\nexport interface ImageModelProfile {\n /** `gpt-image-2`. */\n id: string;\n displayName: string;\n /** Id модели у провайдера: `{ runware: \"openai:gpt-image@2\" }`. */\n providerModelIds: Record<string, string>;\n /** Размер по умолчанию для каждой пропорции. */\n aspectRatios: ImageSizesByAspectRatio;\n /** Другие размеры, которые приложение выбирает настройкой `size`. */\n sizes?: Partial<Record<Exclude<ImageSize, \"default\">, ImageSizeOption>>;\n /** Поля задачи для этой модели у провайдера: у Runware `providerSettings` или `settings` с качеством, у KIE — поля `input`. */\n providerSettings?: Record<string, unknown>;\n /**\n * Где в `providerSettings` лежат качество и фон: у Runware GPT Image 2 — `[\"providerSettings\", \"openai\"]`, у 2.5 —\n * `[\"settings\"]`. Нет — модель не принимает `quality`, и настройка с ним — ошибка.\n */\n settingsPath?: readonly string[];\n /** Модель умеет прозрачный фон (`background: \"transparent\"` по `settingsPath`). */\n transparentBackground?: boolean;\n /** Пропорция у провайдера, если она не совпадает с заказанной: z-image не знает 3:2 — берётся 4:3. */\n aspectRatioValues?: Partial<Record<AspectRatio, string>>;\n /** Модель правит готовую картинку (разделение на слои): ей нужен `imageUrl`, сама по себе она не рисует. */\n needsSourceImage?: boolean;\n /** Первый адрес ответа — основа (фон без предмета), а не результат: сохраняется следующий. */\n firstResultIsBase?: boolean;\n /**\n * Цена картинки размера по умолчанию в качестве по умолчанию. По ней (с размером и качеством) заказ бронируется в\n * лимитах, и она же записывается, если провайдер не сообщил цену или человек нажал «Стоп». С запасом: лучше записать лишнее.\n */\n pricePerImageUsd: number;\n /** Во сколько раз дороже картинка в другом качестве, чем в качестве по умолчанию; нет — так же. */\n qualityPriceFactors?: Partial<Record<ImageQuality, number>>;\n}\n\n/** Каталог моделей и провайдеров картинок; правится `extendImageCatalog`, как каталог моделей ядра. */\nexport interface ImageCatalog {\n providers: ImageProviderDescription[];\n models: ImageModelProfile[];\n}\n\n/** Подключение провайдера через прокси, как у провайдеров моделей ядра. */\nexport interface ImageProviderConnectionChange {\n baseURL?: string;\n headers?: Record<string, string>;\n}\n\nexport interface ImageCatalogChange {\n removeModels?: string[];\n addModels?: ImageModelProfile[];\n addProviders?: ImageProviderDescription[];\n connectProviders?: Record<string, ImageProviderConnectionChange>;\n}\n\n/**\n * Наименьший размер GPT Image у Runware: стороны кратны 16, площадь не меньше 655 360 пикселей,\n * пропорция точнее 1 %.\n */\nconst gptImageSizes: ImageSizesByAspectRatio = {\n \"1:1\": { width: 816, height: 816 },\n \"3:2\": { width: 1008, height: 672 },\n \"2:3\": { width: 672, height: 1008 },\n \"16:9\": { width: 1088, height: 608 },\n \"9:16\": { width: 608, height: 1088 },\n};\n\n/**\n * GPT Image в 1K: длинная сторона около 1024 при тех же условиях Runware. Кроме 1:1 совпадает с наименьшим размером:\n * 1024×576 для 16:9 Runware отвергает (меньше 655 360 пикселей).\n */\nconst gptImage1kSizes: ImageSizesByAspectRatio = { ...gptImageSizes, \"1:1\": { width: 1024, height: 1024 } };\n\n/**\n * Seedream 5.0 в разрешении 1K: площадь от 0,92 Мп. Проверено живым запросом 3 октября 2026: Runware принимает `width` и `height`\n * из этой таблицы без отдельного поля разрешения (1024×1024, цена $0.0193 в ответе), поэтому профиль без `providerSettings`.\n */\nconst seedreamSizes: ImageSizesByAspectRatio = {\n \"1:1\": { width: 1024, height: 1024 },\n \"3:2\": { width: 1248, height: 832 },\n \"2:3\": { width: 832, height: 1248 },\n \"16:9\": { width: 1280, height: 720 },\n \"9:16\": { width: 720, height: 1280 },\n};\n\n/**\n * z-image у KIE: размер задаётся пропорцией, 1:1 — 1024×1024 (живой запрос 5 октября 2026). Остальные стороны —\n * примерные для заглушки на экране: настоящий размер берётся из скачанного PNG.\n */\nconst zImageSizes: ImageSizesByAspectRatio = {\n \"1:1\": { width: 1024, height: 1024 },\n \"3:2\": { width: 1152, height: 864 },\n \"2:3\": { width: 864, height: 1152 },\n \"16:9\": { width: 1280, height: 720 },\n \"9:16\": { width: 720, height: 1280 },\n};\n\nconst lowQualityGpt2 = { providerSettings: { openai: { quality: \"low\" } } };\nconst lowQualityGpt25 = { settings: { quality: \"low\" } };\n/**\n * Цена GPT Image растёт с качеством примерно как у OpenAI: medium вчетверо дороже low, high — в 16 раз. `auto` может\n * выбрать high — бронируется как high.\n */\nconst gptQualityPriceFactors = { medium: 4, high: 16, auto: 16 };\n/**\n * Цены — замер Runware 5 октября 2026 (цена из ответа, low): 1024×1024 — $0.00602 у 2 и 2.5 Flare, $0.00599 у 2.5 Sunburst,\n * с прозрачным фоном так же. Наименьший размер — по площади (816² ≈ 0,64 от 1024²), с запасом.\n */\nconst gptPrices = { pricePerImageUsd: 0.005, qualityPriceFactors: gptQualityPriceFactors, sizes: { \"1k\": { aspectRatios: gptImage1kSizes, pricePerImageUsd: 0.007 } } };\n/** GPT Image 2 у Runware не умеет прозрачный фон: на `background: \"transparent\"` — `invalidOpenAiBackground` (только opaque и auto). */\nconst gpt2Settings = { providerSettings: lowQualityGpt2, settingsPath: [\"providerSettings\", \"openai\"], ...gptPrices };\nconst gpt25Settings = { providerSettings: lowQualityGpt25, settingsPath: [\"settings\"], transparentBackground: true, ...gptPrices };\n/** Seedream и z-image рисуют в 1K и так: размер 1K у них — размер по умолчанию. */\nconst sizes1k = (aspectRatios: ImageSizesByAspectRatio) => ({ sizes: { \"1k\": { aspectRatios } } });\n/** Seedream у KIE: 1K и PNG (разделению на слои PNG нужен и для основы). */\nconst kieSeedreamSettings = { size: \"1K\", output_format: \"png\" };\n\n/**\n * Runware и пять моделей (research.md, раздел 9); каждая в наименьшем разрешении и низшем качестве. KIE — три модели:\n * z-image (дешёвая картинка без текста), Seedream 5.0 Flash (держит надписи) и разделение готовой картинки на слои\n * (предмет без фона). Цены KIE — живые запросы 5 октября 2026, с запасом.\n */\nexport const standardImageCatalog: ImageCatalog = {\n providers: [runwareProvider, kieProvider],\n models: [\n {\n id: \"gpt-image-2\",\n displayName: \"GPT Image 2\",\n providerModelIds: { runware: \"openai:gpt-image@2\" },\n aspectRatios: gptImageSizes,\n ...gpt2Settings,\n },\n {\n id: \"gpt-image-2.5-flare\",\n displayName: \"GPT Image 2.5 Flare\",\n providerModelIds: { runware: \"openai:gpt-image@2.5-flare\" },\n aspectRatios: gptImageSizes,\n ...gpt25Settings,\n },\n {\n id: \"gpt-image-2.5-sunburst\",\n displayName: \"GPT Image 2.5 Sunburst\",\n providerModelIds: { runware: \"openai:gpt-image@2.5-sunburst\" },\n aspectRatios: gptImageSizes,\n ...gpt25Settings,\n },\n {\n id: \"seedream-5-flash\",\n displayName: \"Seedream 5.0 Flash\",\n providerModelIds: { runware: \"bytedance:seedream@5.0-flash\" },\n aspectRatios: seedreamSizes,\n ...sizes1k(seedreamSizes),\n pricePerImageUsd: 0.02,\n },\n {\n id: \"seedream-5-lite\",\n displayName: \"Seedream 5.0 Lite\",\n providerModelIds: { runware: \"bytedance:seedream@5.0-lite\" },\n aspectRatios: seedreamSizes,\n ...sizes1k(seedreamSizes),\n pricePerImageUsd: 0.04,\n },\n {\n id: \"z-image\",\n displayName: \"Z-Image\",\n providerModelIds: { kie: \"z-image\" },\n aspectRatios: zImageSizes,\n ...sizes1k(zImageSizes),\n aspectRatioValues: { \"3:2\": \"4:3\", \"2:3\": \"3:4\" },\n // 0,8 кредита = $0.004.\n pricePerImageUsd: 0.005,\n },\n {\n id: \"seedream-5-flash-kie\",\n displayName: \"Seedream 5.0 Flash (KIE)\",\n providerModelIds: { kie: \"seedream/5-flash-text-to-image\" },\n aspectRatios: seedreamSizes,\n ...sizes1k(seedreamSizes),\n providerSettings: kieSeedreamSettings,\n // 3,24 кредита = $0.0162 за 1K.\n pricePerImageUsd: 0.02,\n },\n {\n id: \"seedream-5-flash-layers\",\n displayName: \"Seedream 5.0 Flash: разделение на слои\",\n providerModelIds: { kie: \"seedream/5-flash-layer-decomposition\" },\n aspectRatios: seedreamSizes,\n ...sizes1k(seedreamSizes),\n providerSettings: kieSeedreamSettings,\n needsSourceImage: true,\n firstResultIsBase: true,\n // Основа и один слой — 6,48 кредита ($0.032); без подсказки слоёв больше и дороже ($0.065).\n pricePerImageUsd: 0.07,\n },\n ],\n};\n\n/** Каталог с правками приложения; исходный каталог не меняется. Несогласованная правка — ошибка настройки. */\nexport function extendImageCatalog(base: ImageCatalog, change: ImageCatalogChange): ImageCatalog {\n for (const id of change.removeModels ?? []) {\n if (!base.models.some((model) => model.id === id)) throw new Error(`Убрать нечего: модели картинок «${id}» нет в каталоге`);\n }\n const removed = new Set(change.removeModels ?? []);\n const added = change.addModels ?? [];\n const addedIds = new Set(added.map((model) => model.id));\n const models = [...base.models.filter((model) => !removed.has(model.id) && !addedIds.has(model.id)), ...added];\n\n const addedProviders = change.addProviders ?? [];\n const addedProviderIds = new Set(addedProviders.map((provider) => provider.id));\n const providers = [...base.providers.filter((provider) => !addedProviderIds.has(provider.id)), ...addedProviders];\n const connections = change.connectProviders ?? {};\n for (const providerId of Object.keys(connections)) {\n if (!providers.some((provider) => provider.id === providerId)) {\n throw new Error(`Подключить через прокси нечего: провайдера картинок «${providerId}» нет в каталоге`);\n }\n }\n const connected = providers.map((provider) => {\n const connection = connections[provider.id];\n if (!connection) return provider;\n return {\n ...provider,\n ...(connection.baseURL !== undefined && { baseURL: connection.baseURL }),\n ...(connection.headers !== undefined && { headers: { ...connection.headers } }),\n };\n });\n\n const providerIds = new Set(connected.map((provider) => provider.id));\n for (const model of added) {\n const unknown = Object.keys(model.providerModelIds).filter((providerId) => !providerIds.has(providerId));\n if (unknown.length > 0) throw new Error(`Модель картинок «${model.id}»: провайдера ${unknown.map((id) => `«${id}»`).join(\", \")} нет в каталоге`);\n }\n return { providers: connected, models };\n}\n"],"mappings":"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","names":["MAX_REASON_LENGTH","taskBody","readAnswer","aspectRatios"]}
|