@ykurilov/agent-chat-core 0.57.0 → 0.58.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/dist/index.js CHANGED
@@ -168,7 +168,7 @@ import {
168
168
  withoutFileParts,
169
169
  withoutGlueFragments,
170
170
  withoutResetSteps
171
- } from "./chunk-BS2HDKZB.js";
171
+ } from "./chunk-KAG3QTZC.js";
172
172
  import {
173
173
  APP_EVENT_DATA_MAX_BYTES,
174
174
  APP_EVENT_TYPE_MAX_LENGTH,
@@ -200,6 +200,53 @@ function revertRefusal(error) {
200
200
  return error.kind === "duplicate" ? "duplicate" : "limit_reached";
201
201
  }
202
202
 
203
+ // src/storage/postgres-migrations.ts
204
+ var AGENT_CHAT_SCHEMA = "agent_chat";
205
+ var AGENT_CHAT_SCHEMA_LOCK_KEY = 7311900441;
206
+ var DEFAULT_RETRY_DELAYS_MS = [25, 50, 100, 200];
207
+ var DUPLICATE_CODES = /* @__PURE__ */ new Set(["23505", "42P06", "42P07", "42710"]);
208
+ async function migratePostgresSchema(options) {
209
+ const delays = options.retryDelaysMs ?? DEFAULT_RETRY_DELAYS_MS;
210
+ for (let attempt = 0; ; attempt++) {
211
+ try {
212
+ return await migrateOnce(options);
213
+ } catch (error) {
214
+ if (attempt >= delays.length || !isDuplicateError(error)) throw error;
215
+ await new Promise((resolve) => setTimeout(resolve, delays[attempt]));
216
+ }
217
+ }
218
+ }
219
+ async function migrateOnce(options) {
220
+ const connection = await options.connect();
221
+ try {
222
+ if (!await schemaExists(connection)) {
223
+ await underLock(connection, AGENT_CHAT_SCHEMA_LOCK_KEY, () => connection.query(`create schema if not exists "${AGENT_CHAT_SCHEMA}"`));
224
+ }
225
+ await underLock(connection, options.lockKey, () => options.migrate(connection));
226
+ } finally {
227
+ connection.release();
228
+ }
229
+ }
230
+ async function schemaExists(connection) {
231
+ const result = await connection.query("select 1 from pg_namespace where nspname = $1", [AGENT_CHAT_SCHEMA]);
232
+ return result.rows.length > 0;
233
+ }
234
+ async function underLock(connection, key, work) {
235
+ await connection.query("select pg_advisory_lock($1)", [key]);
236
+ try {
237
+ await work();
238
+ } finally {
239
+ await connection.query("select pg_advisory_unlock($1)", [key]);
240
+ }
241
+ }
242
+ function isDuplicateError(error) {
243
+ for (let current = error, depth = 0; current && depth < 5; current = current.cause, depth++) {
244
+ const code = current.code;
245
+ if (typeof code === "string" && DUPLICATE_CODES.has(code)) return true;
246
+ }
247
+ return false;
248
+ }
249
+
203
250
  // src/request-origin/request-origin.ts
204
251
  function originRefusalHint(refusal) {
205
252
  if (refusal.reason === "host_mismatch") {
@@ -961,6 +1008,7 @@ export {
961
1008
  loadCurrentBranch,
962
1009
  loadCurrentBranchLinks,
963
1010
  mergeChatData,
1011
+ migratePostgresSchema,
964
1012
  modelAcceptsImages,
965
1013
  nextSiblingTime,
966
1014
  normalizeChatClosure,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/memory/memory-optimization.ts","../src/request-origin/request-origin.ts","../src/storage/in-memory-storage.ts"],"sourcesContent":["import type { MemoryError, MemoryNote, MemoryNoteInput } from \"./long-term-memory\";\n\n/**\n * Состояние запуска оптимизации дублей: идёт → слито / нечего сливать / не удалось; слитое можно вернуть (`reverted`).\n * Запуск `running` у человека не больше одного.\n */\nexport type MemoryOptimizationStatus = \"running\" | \"applied\" | \"no_changes\" | \"failed\" | \"reverted\";\n\n/** Одна склейка: записи `originalIds` скрыты и подчинены новой записи `resultId`. */\nexport interface MemoryMergeGroup {\n resultId: string;\n originalIds: string[];\n /** Версия результата на момент слияния: пока она та же, результат не менялся и слияние можно вернуть. */\n resultVersion: number;\n}\n\n/** Запуск оптимизации. Владелец (`ownerId`) — свойство хранилища и наружу не выдаётся. */\nexport interface MemoryOptimizationRun {\n id: string;\n /** Время в формате ISO 8601. Часы даёт вызывающий: от них считаются срок между запусками и пауза после сбоя. */\n startedAt: string;\n finishedAt: string | null;\n status: MemoryOptimizationStatus;\n /** Склейки запуска; у не слитых запусков пусто. */\n groups: MemoryMergeGroup[];\n /** Причина сбоя одним словом-кодом; у остальных `null`. */\n error: string | null;\n /** Запись расхода модели, если вызывающий её знает. */\n usageRecordId: string | null;\n}\n\n/** Правила запуска: их проверяет хранилище в той же операции, что и берёт запуск, чтобы два процесса не начали его вдвоём. */\nexport interface MemoryOptimizationRules {\n /** Сколько должно пройти после законченного запуска (слито, нечего сливать, отменено) до следующего. */\n minIntervalMs: number;\n /**\n * Сколько должно пройти после неудачного запуска до повтора. Число — одна пауза на все неудачи; список — паузы после\n * 1-й, 2-й, … неудачи подряд (последняя держится дальше). Успешный, пустой или отменённый запуск начинает счёт заново.\n */\n retryAfterFailureMs: number | readonly number[];\n /** Сколько запуск `running` считается живым; после этого срока он становится неудачным (`failed`, ошибка `lease_expired`). */\n leaseMs: number;\n}\n\n/** Чем закончился запуск без слияния. */\nexport interface MemoryOptimizationOutcome {\n status: \"no_changes\" | \"failed\";\n finishedAt: string;\n error?: string | null;\n usageRecordId?: string | null;\n}\n\n/** Что слить: новая запись-результат вместо оригиналов; версии оригиналов — те, что видел планировщик. */\nexport interface MemoryMergeInput {\n runId: string;\n finishedAt: string;\n usageRecordId?: string | null;\n merges: { result: MemoryNoteInput; originals: { id: string; version: number }[] }[];\n}\n\n/** Скрытая запись слияния: оригинал, подчинённый результату. Список, блок памяти и поиск таких записей не видят. */\nexport interface MergedMemoryNote extends MemoryNote {\n mergedInto: string;\n}\n\nexport type MemoryMergeApplyResult = MemoryOptimizationRun | \"stale_version\" | \"run_not_active\";\n/** `limit_reached` и `duplicate` — возврат вышел бы за пределы памяти или вернул бы повтор записи, сделанной за это время. */\nexport type MemoryMergeRevertResult = \"ok\" | \"not_found\" | \"conflict\" | \"limit_reached\" | \"duplicate\";\n\n/** Запуск в том виде, что нужен для решения «можно ли начать новый»; время — миллисекунды. */\nexport interface OptimizationRunTiming {\n status: MemoryOptimizationStatus;\n startedAtMs: number;\n /** Конец запуска; у идущего его нет. */\n endedAtMs: number | null;\n}\n\n/** Пауза после неудачи с номером `failuresInRow` (с 1) подряд. */\nexport function pauseAfterFailure(rules: Pick<MemoryOptimizationRules, \"retryAfterFailureMs\">, failuresInRow: number): number {\n const { retryAfterFailureMs } = rules;\n if (typeof retryAfterFailureMs === \"number\") return retryAfterFailureMs;\n return retryAfterFailureMs[Math.min(failuresInRow, retryAfterFailureMs.length) - 1] ?? 0;\n}\n\n/**\n * Мешает ли история запусков начать новый: идёт другой, или не вышла пауза после последнего. Пауза после неудачи\n * растёт с числом неудач подряд. Все три хранилища решают этим одним правилом.\n */\nexport function isOptimizationBlocked(runs: readonly OptimizationRunTiming[], rules: MemoryOptimizationRules, nowMs: number): boolean {\n const ordered = [...runs].sort((left, right) => left.startedAtMs - right.startedAtMs);\n let failuresInRow = 0;\n for (const run of ordered) {\n if (run.status === \"running\") return true;\n failuresInRow = run.status === \"failed\" ? failuresInRow + 1 : 0;\n const pause = run.status === \"failed\" ? pauseAfterFailure(rules, failuresInRow) : rules.minIntervalMs;\n if (nowMs - (run.endedAtMs ?? run.startedAtMs) < pause) return true;\n }\n return false;\n}\n\n/** Ответ отката слияния на ошибку проверки: повтор записи — `duplicate`, всё остальное (число, объём) — `limit_reached`. */\nexport function revertRefusal(error: MemoryError): \"limit_reached\" | \"duplicate\" {\n return error.kind === \"duplicate\" ? \"duplicate\" : \"limit_reached\";\n}\n","/**\n * Защита от запросов с чужих страниц. Браузер сам прикладывает к запросу куки человека, даже если запрос\n * отправила чужая страница (обычная HTML-форма, WebSocket). Отличить свою страницу от чужой помогают заголовки,\n * которые ставит только браузер: `Sec-Fetch-Site` и `Origin`. Порядок проверки как в Go 1.25 (CrossOriginProtection).\n */\n\nexport interface RequestOriginCheckOptions {\n /**\n * Чужие страницы, которым можно слать запросы с куками человека, например `[\"https://school.getcourse.ru\"]`\n * (схема, хост, порт). Шаблон `https://*.example.com` пускает все поддомены `example.com`, но не сам домен\n * и не `evil-example.com`. Свой сайт в список вносить не нужно.\n */\n allowedOrigins?: readonly string[] | undefined;\n /** Отказ: вызывается один раз на каждый новый адрес страницы, чтобы журнал не забивался. */\n onRefused?: (refusal: OriginRefusal) => void;\n}\n\n/**\n * Почему запрос отклонён. `cross_site` — браузер сам сказал, что страница чужая. `host_mismatch` — браузер старый\n * или адрес не https, и Origin не совпал ни с одним именем сервера (`hosts`): так бывает за прокси, который подменил Host.\n */\nexport type OriginRefusal = { origin: string; reason: \"cross_site\" } | { origin: string; reason: \"host_mismatch\"; hosts: string[] };\n\n/** Подсказка для журнала: что поправить, если отклонена своя страница. */\nexport function originRefusalHint(refusal: OriginRefusal): string {\n if (refusal.reason === \"host_mismatch\") {\n return (\n `отклонён запрос со страницы ${refusal.origin}: Origin не совпал с Host (${refusal.hosts.join(\", \")}). ` +\n \"Если сервер за прокси, за прокси передайте Host/X-Forwarded-Host (nginx: proxy_set_header Host $host; \" +\n \"proxy_set_header X-Forwarded-Host $host;). Если это чужая для сервера, но ваша страница, добавьте её адрес в allowedOrigins.\"\n );\n }\n return `отклонён запрос с чужой страницы ${refusal.origin}. Если это ваша страница, добавьте её адрес в allowedOrigins.`;\n}\n\n/** `true` — запрос со своей страницы, со страницы из списка или не из браузера. Метод запроса не смотрит. */\nexport type RequestOriginCheck = (request: Request) => boolean;\n\n/** Чтения (GET, HEAD, OPTIONS) не меняют данных: их ответ браузер чужой странице всё равно не покажет. */\nexport function isSafeMethod(method: string): boolean {\n const upper = method.toUpperCase();\n return upper === \"GET\" || upper === \"HEAD\" || upper === \"OPTIONS\";\n}\n\n/** Сколько разных отклонённых адресов запоминается для журнала. */\nconst MAX_REPORTED_ORIGINS = 100;\nconst MAX_REPORTED_LENGTH = 200;\n\nexport function createRequestOriginCheck(options: RequestOriginCheckOptions = {}): RequestOriginCheck {\n const isAllowed = allowedOriginsMatcher(options.allowedOrigins ?? []);\n const reported = new Set<string>();\n const refuse = (refusal: OriginRefusal): false => {\n const shown = refusal.origin.slice(0, MAX_REPORTED_LENGTH);\n if (options.onRefused && !reported.has(shown) && reported.size < MAX_REPORTED_ORIGINS) {\n reported.add(shown);\n options.onRefused({ ...refusal, origin: shown });\n }\n return false;\n };\n return (request) => {\n const origin = request.headers.get(\"origin\");\n if (origin !== null && isAllowed(origin)) return true;\n const site = request.headers.get(\"sec-fetch-site\");\n if (site !== null) {\n if (site === \"same-origin\" || site === \"none\") return true;\n return refuse({ origin: origin ?? `(без Origin, Sec-Fetch-Site: ${site})`, reason: \"cross_site\" });\n }\n // Нет ни того ни другого — не браузер (сервер, curl): куки человека чужая страница так не приложит.\n if (origin === null) return true;\n // Старый браузер без Sec-Fetch-Site: Origin должен совпасть с адресом самого сервера.\n const host = originHost(origin);\n const hosts = hostsOf(request);\n if (host !== null && hosts.has(host)) return true;\n return refuse({ origin, reason: \"host_mismatch\", hosts: [...hosts] });\n };\n}\n\n/** Метка вместо `*`: адрес с ней разбирает URL, а в настоящем имени хоста она не встречается. */\nconst WILDCARD_LABEL = \"wildcard-label-0\";\n\n/** Точные адреса и шаблоны поддоменов из `allowedOrigins`; неверная запись — ошибка сразу. */\nfunction allowedOriginsMatcher(entries: readonly string[]): (origin: string) => boolean {\n const exact = new Set<string>();\n const subdomainsOf: Array<{ protocol: string; port: string; suffix: string }> = [];\n for (const entry of entries) {\n const wildcard = /^(https?:\\/\\/)\\*\\.([^*]+)$/.exec(entry);\n const url = parsePageAddress(wildcard ? `${wildcard[1]}${WILDCARD_LABEL}.${wildcard[2]}` : entry);\n if (url === null || (!wildcard && entry.includes(\"*\"))) throw wrongEntry(entry);\n if (!wildcard) {\n exact.add(url.origin);\n continue;\n }\n const suffix = url.hostname.slice(WILDCARD_LABEL.length);\n // `*.com` пустил бы весь интернет: после звёздочки нужен домен хотя бы из двух частей (или localhost).\n // Точка в конце (`*.com.`) — тот же `*.com`. Общие суффиксы вроде `*.co.uk` так не отличить: вписывают свой домен.\n const tooBroad = suffix.endsWith(\".\") || (!suffix.slice(1).includes(\".\") && suffix !== \".localhost\");\n if (!url.hostname.startsWith(`${WILDCARD_LABEL}.`) || tooBroad) throw wrongEntry(entry);\n subdomainsOf.push({ protocol: url.protocol, port: url.port, suffix });\n }\n return (origin) => {\n const url = parseOrigin(origin);\n if (url === null) return false;\n if (exact.has(url.origin)) return true;\n return subdomainsOf.some(\n (pattern) =>\n url.protocol === pattern.protocol && url.port === pattern.port && url.hostname.endsWith(pattern.suffix) && url.hostname.length > pattern.suffix.length,\n );\n };\n}\n\n/** Адрес страницы: http(s), без пути, запроса, якоря и имени пользователя. Иначе `null`. */\nfunction parsePageAddress(entry: string): URL | null {\n try {\n const url = new URL(entry);\n const bare = url.pathname === \"/\" && !url.search && !url.hash && !url.username;\n return (url.protocol === \"http:\" || url.protocol === \"https:\") && bare ? url : null;\n } catch {\n return null;\n }\n}\n\nfunction wrongEntry(entry: string): Error {\n return new Error(\n `allowedOrigins: «${entry}» не адрес страницы; нужен вид https://app.example, http://localhost:5173 или https://*.example.com ` +\n \"(в шаблоне — свой домен: общие суффиксы вроде *.co.uk проверка не распознаёт)\",\n );\n}\n\nfunction parseOrigin(origin: string): URL | null {\n try {\n return new URL(origin);\n } catch {\n return null;\n }\n}\n\n/** Хост с портом из Origin; `null` и не адрес — `null`. */\nfunction originHost(origin: string): string | null {\n try {\n const url = new URL(origin);\n return url.protocol === \"http:\" || url.protocol === \"https:\" ? url.host : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Под какими именами сервер знает себя: `Host` и `X-Forwarded-Host` от прокси. Адрес запроса — только без `Host`:\n * при подъёме WebSocket в Node (`@hono/node-ws`) он собран как `http://localhost/…` и порта в нём нет.\n */\nfunction hostsOf(request: Request): Set<string> {\n const host = request.headers.get(\"host\");\n const hosts = new Set<string>([host ? host.trim().toLowerCase() : new URL(request.url).host]);\n for (const forwarded of request.headers.get(\"x-forwarded-host\")?.split(\",\") ?? []) {\n if (forwarded.trim()) hosts.add(forwarded.trim().toLowerCase());\n }\n return hosts;\n}\n","import type { ModelChoice } from \"../models/model-choice\";\nimport type { MemoryNote, MemoryNoteInput } from \"../memory/long-term-memory\";\nimport { isOptimizationBlocked, revertRefusal } from \"../memory/memory-optimization\";\nimport type { MemoryMergeGroup, MemoryOptimizationRun, MergedMemoryNote } from \"../memory/memory-optimization\";\nimport type { Chat, StoredBranchSelection, StoredCompaction, StoredMessage, UsageRecord, UsageSum } from \"../conversation/types\";\nimport {\n aliveAtByLocalClock,\n assertKnownFormatVersion,\n ChatAlreadyExistsError,\n DuplicateChatSubjectError,\n DuplicateMessageError,\n MessageAlreadyFinishedError,\n MessageNotAwaitingError,\n sameMessageParts,\n silenceBoundaryByLocalClock,\n} from \"./chat-storage\";\nimport { assertChatDataFilter, chatDataFields, chatDataMatches, chatDataOfInput, mergeChatData } from \"./chat-data\";\nimport type { ChatListOptions, ChatStorage, ChatSubjectFilter, SpendingLimitStateRecord, StoredPendingBurst } from \"./chat-storage\";\n\n/** Хранилище в памяти процесса — для тестов. Копирует данные на входе и выходе, как база. */\ntype StoredNote = MemoryNote & { mergedInto?: string };\n\n/** Сумма записей расхода по ключу (`group` — поля ключа: пара «модель × провайдер», у суммы по чатам — ещё чат). */\nfunction sumRecords<K extends Pick<UsageSum, \"modelId\" | \"providerId\">>(records: readonly UsageRecord[], group: (record: UsageRecord) => K): Array<K & UsageSum> {\n const sums = new Map<string, K & UsageSum>();\n for (const record of records) {\n const fields = group(record);\n const key = Object.values(fields).join(\"\\u0000\");\n const sum = sums.get(key) ?? { ...fields, inputTokens: 0, cachedInputTokens: 0, cacheWriteTokens: 0, outputTokens: 0, reasoningTokens: 0, costUsd: 0 };\n sum.inputTokens += record.inputTokens;\n sum.cachedInputTokens += record.cachedInputTokens;\n sum.cacheWriteTokens += record.cacheWriteTokens;\n sum.outputTokens += record.outputTokens;\n sum.reasoningTokens += record.reasoningTokens;\n sum.costUsd += record.costUsd;\n sums.set(key, sum);\n }\n return [...sums.values()];\n}\n\nexport function createInMemoryChatStorage(): ChatStorage {\n const chats = new Map<string, Chat>();\n const messages = new Map<string, StoredMessage>();\n /** Время последнего знака жизни идущих ответов по id: при чтении не отдаётся. */\n const aliveAt = new Map<string, string>();\n /** Аренды хода по чатам: кто держит и до какого времени (мс по часам этой машины). */\n const turnLeases = new Map<string, { holder: string; until: number }>();\n /** Ждущие пачки по чатам. */\n const pendingBursts = new Map<string, StoredPendingBurst>();\n const compactions: StoredCompaction[] = [];\n const branchSelections: StoredBranchSelection[] = [];\n const usageRecords: UsageRecord[] = [];\n const userModelChoices = new Map<string, ModelChoice>();\n const voiceChoices = new Map<string, string>();\n const spendingLimitStates = new Map<string, SpendingLimitStateRecord>();\n /** Долгая память: у каждого человека свои записи; у скрытых оригиналов слияния есть `mergedInto`. */\n const memoryNotes = new Map<string, Map<string, StoredNote>>();\n const optimizationRuns = new Map<string, MemoryOptimizationRun[]>();\n\n function streamingMessage(messageId: string): StoredMessage {\n const message = messages.get(messageId);\n if (!message) throw new Error(`Сообщения ${messageId} нет`);\n if (message.status !== \"streaming\") throw new MessageAlreadyFinishedError(messageId);\n return message;\n }\n\n /** Сообщение для чтения: копия, `voiceCut` — `null`, если не задан. */\n function readable(message: StoredMessage): StoredMessage {\n const copy = structuredClone(message);\n return { ...copy, voiceCut: copy.voiceCut ?? null };\n }\n\n function ownerNotes(ownerId: string): Map<string, StoredNote> {\n let notes = memoryNotes.get(ownerId);\n if (!notes) {\n notes = new Map();\n memoryNotes.set(ownerId, notes);\n }\n return notes;\n }\n\n function ownerRuns(ownerId: string): MemoryOptimizationRun[] {\n let runs = optimizationRuns.get(ownerId);\n if (!runs) {\n runs = [];\n optimizationRuns.set(ownerId, runs);\n }\n return runs;\n }\n\n /** Порядок списка по договору: `updatedAt`, затем `createdAt`, затем `id` — новее и больше первыми. */\n function newestFirst<T extends MemoryNote>(notes: T[]): T[] {\n return notes.sort(\n (left, right) =>\n right.updatedAt.localeCompare(left.updatedAt) ||\n right.createdAt.localeCompare(left.createdAt) ||\n (right.id > left.id ? 1 : right.id < left.id ? -1 : 0),\n );\n }\n\n /** Обычная запись без служебного признака скрытия. */\n function publicNote({ mergedInto: _hidden, ...note }: StoredNote): MemoryNote {\n return note;\n }\n\n /** Как база с NOT NULL: разделы — список строк, текст — строка. */\n function assertWellFormedNote(note: MemoryNoteInput): void {\n if (!Array.isArray(note?.categories) || !note.categories.every((category) => typeof category === \"string\")) {\n throw new TypeError(\"Разделы записи памяти — список строк\");\n }\n if (typeof note.content !== \"string\") throw new TypeError(\"Текст записи памяти — строка\");\n }\n\n /** Записи, которые видит человек: без скрытых оригиналов, копиями. */\n function visibleNotes(ownerId: string): MemoryNote[] {\n const visible = [...ownerNotes(ownerId).values()].filter((note) => !note.mergedInto);\n return structuredClone(newestFirst(visible).map(publicNote));\n }\n\n function insertNotes(ownerId: string, inputs: MemoryNoteInput[]): MemoryNote[] {\n // Сначала проверяем весь пакет: негодная запись не оставляет в памяти и остальных.\n inputs.forEach(assertWellFormedNote);\n const now = new Date().toISOString();\n const created = inputs.map(\n ({ categories, content }): MemoryNote => ({\n id: crypto.randomUUID(),\n categories: [...categories],\n content,\n version: 1,\n createdAt: now,\n updatedAt: now,\n }),\n );\n const notes = ownerNotes(ownerId);\n for (const note of created) notes.set(note.id, note);\n return structuredClone(created);\n }\n\n function updateNote(ownerId: string, id: string, expectedVersion: number, input: MemoryNoteInput): MemoryNote | \"not_found\" | \"stale_version\" {\n assertWellFormedNote(input);\n const notes = ownerNotes(ownerId);\n const note = notes.get(id);\n if (!note || note.mergedInto) return \"not_found\";\n if (note.version !== expectedVersion) return \"stale_version\";\n const updated: MemoryNote = {\n ...note,\n categories: [...input.categories],\n content: input.content,\n version: note.version + 1,\n updatedAt: new Date().toISOString(),\n };\n notes.set(id, updated);\n return structuredClone(updated);\n }\n\n /** Как уникальный индекс (владелец, ключ): чат без ключа не мешает никому. */\n function assertSubjectFree(chat: Pick<Chat, \"ownerId\" | \"subjectKey\">): void {\n if (chat.subjectKey === undefined) return;\n for (const existing of chats.values()) {\n if (existing.ownerId === chat.ownerId && existing.subjectKey === chat.subjectKey) {\n throw new DuplicateChatSubjectError(chat.ownerId, chat.subjectKey);\n }\n }\n }\n\n function sortedMessages(chatId: string): StoredMessage[] {\n return [...messages.values()]\n .filter((message) => message.chatId === chatId)\n .sort((left, right) => left.createdAt.localeCompare(right.createdAt));\n }\n\n return {\n async createChat({ createdAt, ...input }) {\n const at = createdAt ?? new Date().toISOString();\n const chat: Chat = withChatData(withoutUndefined({ ...structuredClone(input), createdAt: at, updatedAt: at }));\n assertSubjectFree(chat);\n chats.set(chat.id, chat);\n return structuredClone(chat);\n },\n\n async findChatBySubject(ownerId, subjectKey) {\n const chat = [...chats.values()].find((existing) => existing.ownerId === ownerId && existing.subjectKey === subjectKey);\n return chat ? structuredClone(chat) : null;\n },\n\n async importChat(input, imported) {\n // Сначала все проверки: при ошибке ничего не записано.\n if (chats.has(input.id)) throw new ChatAlreadyExistsError(input.id);\n const chat: Chat = withChatData(withoutUndefined(structuredClone(input)));\n assertSubjectFree(chat);\n const ids = new Set<string>();\n for (const message of imported) {\n if (messages.has(message.id) || ids.has(message.id)) throw new DuplicateMessageError(message.id);\n ids.add(message.id);\n }\n chats.set(chat.id, chat);\n for (const message of imported) {\n const { alive: _alive, ...stored } = message;\n messages.set(message.id, structuredClone(stored));\n }\n },\n\n async setAfterTurnState(messageId, state, options) {\n const message = messages.get(messageId);\n if (!message || (state !== \"pending\" && message.afterTurn !== \"pending\")) return false;\n const parts = options?.appendParts ? [...message.parts, ...structuredClone(options.appendParts)] : message.parts;\n messages.set(messageId, { ...message, parts, afterTurn: state });\n // Отметка ожидания хука — в той же колонке, что знак жизни: у законченного ответа она больше ни для чего.\n if (state === \"pending\") aliveAt.set(messageId, aliveAtByLocalClock(\"now\"));\n return true;\n },\n\n async listPendingAfterTurn(filter) {\n const boundary = filter?.pendingForMs === undefined ? null : silenceBoundaryByLocalClock(filter.pendingForMs);\n return [...messages.values()]\n .filter((message) => message.afterTurn === \"pending\")\n .filter((message) => boundary === null || (aliveAt.get(message.id) ?? message.createdAt) < boundary)\n .map((message) => message.id);\n },\n\n async getChat(chatId) {\n const chat = chats.get(chatId);\n return chat ? structuredClone(chat) : null;\n },\n\n async updateChatModel(chatId, model) {\n const chat = chats.get(chatId);\n if (!chat) return;\n const { reasoning: _dropped, ...rest } = chat;\n chats.set(chatId, { ...rest, ...model });\n },\n\n async listChats(ownerId, options?: ChatListOptions) {\n if (options?.data) assertChatDataFilter(options.data);\n const owned = [...chats.values()].filter(\n (chat) => chat.ownerId === ownerId && matchesSubjects(chat, options?.subjects) && (!options?.data || chatDataMatches(chat.data, options.data)),\n );\n owned.sort(\n (left, right) =>\n right.updatedAt.localeCompare(left.updatedAt) ||\n right.createdAt.localeCompare(left.createdAt) ||\n (right.id > left.id ? 1 : right.id < left.id ? -1 : 0),\n );\n return structuredClone(owned);\n },\n\n async updateChatData(chatId, patch, { maxBytes }) {\n const chat = chats.get(chatId);\n if (!chat) return null;\n // Слияние и проверка размера до записи: при ошибке данные прежние.\n const merged = mergeChatData(chat.data, patch, maxBytes);\n const { data: _data, subjectData: _subjectData, ...rest } = chat;\n const updated: Chat = { ...rest, ...chatDataFields(merged) };\n chats.set(chatId, updated);\n return structuredClone(updated);\n },\n\n async renameChat(chatId, title) {\n const chat = chats.get(chatId);\n if (!chat) return;\n const { title: _dropped, ...rest } = chat;\n chats.set(chatId, title === null ? rest : { ...rest, title });\n },\n\n async setChatClosed(chatId, closed) {\n const chat = chats.get(chatId);\n if (!chat) return;\n const { closed: _dropped, ...rest } = chat;\n chats.set(chatId, closed === null ? rest : { ...rest, closed: { code: closed.code, message: closed.message } });\n },\n\n async deleteChat(chatId) {\n if (!chats.delete(chatId)) return;\n turnLeases.delete(chatId);\n pendingBursts.delete(chatId);\n for (const message of [...messages.values()]) {\n if (message.chatId !== chatId) continue;\n messages.delete(message.id);\n aliveAt.delete(message.id);\n }\n // Записи сжатия — на месте, с обратным ходом, чтобы индексы не сдвигались.\n for (let index = compactions.length - 1; index >= 0; index -= 1) {\n if (compactions[index]!.chatId === chatId) compactions.splice(index, 1);\n }\n for (let index = branchSelections.length - 1; index >= 0; index -= 1) {\n if (branchSelections[index]!.chatId === chatId) branchSelections.splice(index, 1);\n }\n },\n\n async getMessage(messageId) {\n const message = messages.get(messageId);\n if (!message) return null;\n assertKnownFormatVersion(message);\n return readable(message);\n },\n\n async listMessages(chatId) {\n const found = sortedMessages(chatId);\n found.forEach(assertKnownFormatVersion);\n return found.map(readable);\n },\n\n async listMessageLinks(chatId) {\n return sortedMessages(chatId).map(({ id, parentId, createdAt }) => ({ id, parentId, createdAt }));\n },\n\n async listMessagePartsOfTypes(chatId, types) {\n return sortedMessages(chatId).map(({ id, parentId, createdAt, parts }) => ({\n id,\n parentId,\n createdAt,\n parts: structuredClone(parts.filter((part) => types.includes(part.type))),\n }));\n },\n\n async insertMessage(message) {\n if (messages.has(message.id)) throw new DuplicateMessageError(message.id);\n // Как внешний ключ в базе: сообщение без чата не пишется.\n const chat = chats.get(message.chatId);\n if (!chat) throw new Error(`Чата ${message.chatId} нет`);\n const { alive, ...stored } = message;\n messages.set(message.id, structuredClone(stored));\n if (alive) aliveAt.set(message.id, aliveAtByLocalClock(alive));\n chats.set(chat.id, { ...chat, updatedAt: message.createdAt });\n },\n\n async updateStreamingMessage(messageId, patch) {\n const message = streamingMessage(messageId);\n const { alive, ...rest } = structuredClone(patch);\n messages.set(messageId, { ...message, ...rest });\n if (alive) aliveAt.set(messageId, aliveAtByLocalClock(alive));\n },\n\n async markAnswerAlive(messageId) {\n streamingMessage(messageId);\n aliveAt.set(messageId, aliveAtByLocalClock(\"now\"));\n },\n\n async finishMessage(messageId, final) {\n const message = streamingMessage(messageId);\n messages.set(messageId, { ...message, ...structuredClone(final) });\n if (final.afterTurn === \"pending\") aliveAt.set(messageId, aliveAtByLocalClock(\"now\"));\n },\n\n async updateAwaitingMessage(messageId, patch) {\n const message = messages.get(messageId);\n if (!message) throw new Error(`Сообщения ${messageId} нет`);\n if (message.status !== \"awaiting_approval\") throw new MessageNotAwaitingError(messageId);\n if (patch.expectedParts && !sameMessageParts(message.parts, patch.expectedParts)) throw new MessageNotAwaitingError(messageId);\n messages.set(messageId, {\n ...message,\n parts: structuredClone(patch.parts),\n ...(patch.status && { status: patch.status }),\n });\n if (patch.alive) aliveAt.set(messageId, aliveAtByLocalClock(patch.alive));\n },\n\n async listStreamingMessageIds() {\n return [...messages.values()].filter((message) => message.status === \"streaming\").map((message) => message.id);\n },\n\n async listSilentAnswerIds({ silentForMs, chatId }) {\n const boundary = silenceBoundaryByLocalClock(silentForMs);\n return [...messages.values()]\n .filter((message) => message.status === \"streaming\" && (chatId === undefined || message.chatId === chatId))\n .filter((message) => (aliveAt.get(message.id) ?? message.createdAt) < boundary)\n .map((message) => message.id);\n },\n\n async claimChatTurn(chatId, holder, leaseMs) {\n const now = Date.now();\n const lease = turnLeases.get(chatId);\n if (lease && lease.holder !== holder && lease.until > now) return false;\n turnLeases.set(chatId, { holder, until: now + leaseMs });\n return true;\n },\n\n async releaseChatTurn(chatId, holder) {\n if (turnLeases.get(chatId)?.holder === holder) turnLeases.delete(chatId);\n },\n\n async clearChatTurnLeases() {\n turnLeases.clear();\n },\n\n async savePendingBurst(burst) {\n pendingBursts.set(burst.chatId, structuredClone(burst));\n },\n\n async takeAbandonedBursts({ abandonedBefore, holder, heldUntil, limit, exceptHolder }) {\n const abandoned = [...pendingBursts.values()]\n .filter((burst) => Date.parse(burst.heldUntil) <= Date.parse(abandonedBefore) && burst.holder !== exceptHolder)\n .sort((first, second) => Date.parse(first.dueAt) - Date.parse(second.dueAt))\n .slice(0, limit);\n return abandoned.map((burst) => {\n const taken = { ...burst, holder, heldUntil };\n pendingBursts.set(burst.chatId, taken);\n return structuredClone(taken);\n });\n },\n\n async renewPendingBurst({ chatId, messageId, holder, heldUntil }) {\n const burst = pendingBursts.get(chatId);\n if (burst?.messageId !== messageId || burst.holder !== holder) return false;\n pendingBursts.set(chatId, { ...burst, heldUntil });\n return true;\n },\n\n async removePendingBurst(chatId, messageId) {\n if (pendingBursts.get(chatId)?.messageId === messageId) pendingBursts.delete(chatId);\n },\n\n async insertCompaction(record) {\n if (compactions.some((existing) => existing.id === record.id)) throw new Error(`Запись сжатия ${record.id} уже есть`);\n compactions.push(structuredClone(record));\n },\n\n async listCompactions(chatId) {\n return structuredClone(compactions.filter((record) => record.chatId === chatId));\n },\n\n async insertBranchSelection(record) {\n if (branchSelections.some((existing) => existing.id === record.id)) throw new Error(`Выбор ветки ${record.id} уже есть`);\n branchSelections.push(structuredClone(record));\n },\n\n async listBranchSelections(chatId) {\n return structuredClone(branchSelections.filter((record) => record.chatId === chatId));\n },\n\n async insertUsageRecord(record) {\n if (usageRecords.some((existing) => existing.id === record.id)) throw new Error(`Запись расхода ${record.id} уже есть`);\n usageRecords.push(structuredClone(record));\n },\n\n async hasUsageRecord(id) {\n return usageRecords.some((record) => record.id === id);\n },\n\n async sumUsage({ ownerId, chatId, messageId, source, since }) {\n return sumRecords(\n usageRecords.filter(\n (record) =>\n (ownerId === undefined || record.ownerId === ownerId) &&\n (chatId === undefined || record.chatId === chatId) &&\n (messageId === undefined || record.messageId === messageId) &&\n (source === undefined || record.source === source) &&\n (since === undefined || record.createdAt >= since),\n ),\n (record) => ({ modelId: record.modelId, providerId: record.providerId }),\n );\n },\n\n async sumUsageByChat({ chatIds, ownerId, source, since }) {\n const wanted = new Set(chatIds);\n return sumRecords(\n usageRecords.filter(\n (record) =>\n wanted.has(record.chatId) &&\n (ownerId === undefined || record.ownerId === ownerId) &&\n (source === undefined || record.source === source) &&\n (since === undefined || record.createdAt >= since),\n ),\n (record) => ({ chatId: record.chatId, modelId: record.modelId, providerId: record.providerId }),\n );\n },\n\n async setVoiceCut({ ownerId, chatId, messageId, voiceCut }) {\n const message = messages.get(messageId);\n if (!message || message.chatId !== chatId || chats.get(chatId)?.ownerId !== ownerId) return;\n messages.set(messageId, { ...message, voiceCut: structuredClone(voiceCut) });\n },\n\n async getSpendingLimitState(key) {\n const record = spendingLimitStates.get(key);\n return record ? { ...record } : null;\n },\n\n async saveSpendingLimitState(key, value, expectedVersion) {\n const current = spendingLimitStates.get(key);\n if ((current?.version ?? null) !== expectedVersion) return false;\n spendingLimitStates.set(key, { value, version: (current?.version ?? 0) + 1 });\n return true;\n },\n\n async deleteSpendingLimitStates(keys) {\n for (const key of keys) spendingLimitStates.delete(key);\n },\n\n async getVoiceChoice(ownerId) {\n return voiceChoices.get(ownerId) ?? null;\n },\n\n async setVoiceChoice(ownerId, voiceId) {\n voiceChoices.set(ownerId, voiceId);\n },\n\n async getUserModelChoice(ownerId) {\n const choice = userModelChoices.get(ownerId);\n return choice ? structuredClone(choice) : null;\n },\n\n async saveUserModelChoice(ownerId, choice) {\n userModelChoices.set(ownerId, structuredClone(choice));\n },\n\n async listMemoryNotes(ownerId) {\n return visibleNotes(ownerId);\n },\n\n async insertMemoryNotes(ownerId, inputs) {\n return insertNotes(ownerId, inputs);\n },\n\n // Всё выполняется без ожидания, поэтому между проверкой и вставкой никто не успеет вклиниться.\n async insertMemoryNotesChecked(ownerId, inputs, guard) {\n const error = guard(visibleNotes(ownerId));\n return error ?? insertNotes(ownerId, inputs);\n },\n\n async updateMemoryNote(ownerId, id, expectedVersion, input) {\n return updateNote(ownerId, id, expectedVersion, input);\n },\n\n async updateMemoryNoteChecked(ownerId, id, expectedVersion, input, guard) {\n return guard(visibleNotes(ownerId)) ?? updateNote(ownerId, id, expectedVersion, input);\n },\n\n async deleteMemoryNote(ownerId, id, expectedVersion) {\n const notes = ownerNotes(ownerId);\n const note = notes.get(id);\n if (!note || note.mergedInto) return \"not_found\";\n if (note.version !== expectedVersion) return \"stale_version\";\n notes.delete(id);\n for (const [hiddenId, hidden] of notes) if (hidden.mergedInto === id) notes.delete(hiddenId);\n return \"ok\";\n },\n\n async clearMemoryNotes(ownerId) {\n const count = [...ownerNotes(ownerId).values()].filter((note) => !note.mergedInto).length;\n memoryNotes.delete(ownerId);\n optimizationRuns.delete(ownerId);\n return count;\n },\n\n async claimMemoryOptimizationRun(ownerId, rules, now) {\n const runs = ownerRuns(ownerId);\n const nowMs = Date.parse(now);\n for (const run of runs) {\n const leaseEnd = Date.parse(run.startedAt) + rules.leaseMs;\n if (run.status === \"running\" && nowMs >= leaseEnd) Object.assign(run, { status: \"failed\", error: \"lease_expired\", finishedAt: new Date(leaseEnd).toISOString() });\n }\n const blocked = isOptimizationBlocked(\n runs.map((run) => ({ status: run.status, startedAtMs: Date.parse(run.startedAt), endedAtMs: run.finishedAt ? Date.parse(run.finishedAt) : null })),\n rules,\n nowMs,\n );\n if (blocked) return null;\n const run: MemoryOptimizationRun = { id: crypto.randomUUID(), startedAt: now, finishedAt: null, status: \"running\", groups: [], error: null, usageRecordId: null };\n runs.push(run);\n return structuredClone(run);\n },\n\n async finishMemoryOptimizationRun(ownerId, runId, outcome) {\n const run = ownerRuns(ownerId).find((candidate) => candidate.id === runId);\n if (run?.status !== \"running\") return \"not_found\";\n Object.assign(run, { status: outcome.status, finishedAt: outcome.finishedAt, error: outcome.error ?? null, usageRecordId: outcome.usageRecordId ?? null });\n return \"ok\";\n },\n\n async applyMemoryMerge(ownerId, merge) {\n const { merges } = merge;\n if (merges.length === 0) throw new TypeError(\"Слияние без склеек\");\n const seen = new Set<string>();\n for (const { result, originals } of merges) {\n assertWellFormedNote(result);\n if (originals.length === 0) throw new TypeError(\"Склейка без оригиналов\");\n for (const { id } of originals) {\n if (seen.has(id)) throw new TypeError(`Запись ${id} названа в двух склейках`);\n seen.add(id);\n }\n }\n const run = ownerRuns(ownerId).find((candidate) => candidate.id === merge.runId);\n if (run?.status !== \"running\") return \"run_not_active\";\n const notes = ownerNotes(ownerId);\n for (const { originals } of merges) {\n for (const { id, version } of originals) {\n const original = notes.get(id);\n if (!original || original.mergedInto || original.version !== version) return \"stale_version\";\n }\n }\n const now = new Date().toISOString();\n const groups: MemoryMergeGroup[] = merges.map(({ result, originals }) => {\n const created: StoredNote = { id: crypto.randomUUID(), categories: [...result.categories], content: result.content, version: 1, createdAt: now, updatedAt: now };\n notes.set(created.id, created);\n for (const { id } of originals) notes.get(id)!.mergedInto = created.id;\n return { resultId: created.id, originalIds: originals.map(({ id }) => id), resultVersion: 1 };\n });\n Object.assign(run, { status: \"applied\", finishedAt: merge.finishedAt, groups, usageRecordId: merge.usageRecordId ?? null });\n return structuredClone(run);\n },\n\n async revertMemoryMerge(ownerId, runId, guard) {\n const run = ownerRuns(ownerId).find((candidate) => candidate.id === runId);\n if (run?.status !== \"applied\") return \"not_found\";\n const notes = ownerNotes(ownerId);\n const intact = run.groups.every(\n ({ resultId, originalIds, resultVersion }) =>\n notes.get(resultId)?.version === resultVersion && !notes.get(resultId)?.mergedInto && originalIds.every((id) => notes.get(id)?.mergedInto === resultId),\n );\n if (!intact) return \"conflict\";\n if (guard) {\n const resultIds = new Set(run.groups.map(({ resultId }) => resultId));\n const restoredIds = new Set(run.groups.flatMap(({ originalIds }) => originalIds));\n const inPlay = [...notes.values()];\n const remaining = newestFirst(inPlay.filter((note) => !note.mergedInto && !resultIds.has(note.id))).map(publicNote);\n const restored = newestFirst(inPlay.filter((note) => restoredIds.has(note.id))).map(publicNote);\n const error = guard(structuredClone(remaining), structuredClone(restored));\n if (error) return revertRefusal(error);\n }\n for (const { resultId, originalIds } of run.groups) {\n notes.delete(resultId);\n for (const id of originalIds) delete notes.get(id)!.mergedInto;\n }\n run.status = \"reverted\";\n return \"ok\";\n },\n\n async listMemoryOptimizationRuns(ownerId, filter) {\n const runs = ownerRuns(ownerId)\n .map((run, index) => ({ run, index }))\n .filter(({ run }) => !filter?.status || filter.status.includes(run.status))\n .sort((left, right) => right.run.startedAt.localeCompare(left.run.startedAt) || right.index - left.index)\n .map(({ run }) => run);\n return structuredClone(filter?.limit === undefined ? runs : runs.slice(0, filter.limit));\n },\n\n async listMergedMemoryNotes(ownerId) {\n const hidden = [...ownerNotes(ownerId).values()].filter((note): note is StoredNote & { mergedInto: string } => Boolean(note.mergedInto));\n return structuredClone(newestFirst(hidden) as MergedMemoryNote[]);\n },\n };\n}\n\n/** Подходит ли чат под фильтр `listChats` по ключу объекта. */\nfunction matchesSubjects(chat: Chat, subjects: ChatSubjectFilter = \"exclude\"): boolean {\n if (subjects === \"include\") return true;\n return subjects === \"only\" ? chat.subjectKey !== undefined : chat.subjectKey === undefined;\n}\n\n/** `data` и `subjectData` — один объект; данных нет — полей нет. */\nfunction withChatData<T extends Pick<Chat, \"data\" | \"subjectData\">>(chat: T): T {\n const { data: _data, subjectData: _subjectData, ...rest } = chat;\n return { ...rest, ...chatDataFields(chatDataOfInput(chat)) } as T;\n}\n\n/** Как база: поле без значения не хранится. */\nfunction withoutUndefined<T extends object>(value: T): T {\n return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined)) as T;\n}\n"],"mappings":"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","names":["run"]}
1
+ {"version":3,"sources":["../src/memory/memory-optimization.ts","../src/storage/postgres-migrations.ts","../src/request-origin/request-origin.ts","../src/storage/in-memory-storage.ts"],"sourcesContent":["import type { MemoryError, MemoryNote, MemoryNoteInput } from \"./long-term-memory\";\n\n/**\n * Состояние запуска оптимизации дублей: идёт → слито / нечего сливать / не удалось; слитое можно вернуть (`reverted`).\n * Запуск `running` у человека не больше одного.\n */\nexport type MemoryOptimizationStatus = \"running\" | \"applied\" | \"no_changes\" | \"failed\" | \"reverted\";\n\n/** Одна склейка: записи `originalIds` скрыты и подчинены новой записи `resultId`. */\nexport interface MemoryMergeGroup {\n resultId: string;\n originalIds: string[];\n /** Версия результата на момент слияния: пока она та же, результат не менялся и слияние можно вернуть. */\n resultVersion: number;\n}\n\n/** Запуск оптимизации. Владелец (`ownerId`) — свойство хранилища и наружу не выдаётся. */\nexport interface MemoryOptimizationRun {\n id: string;\n /** Время в формате ISO 8601. Часы даёт вызывающий: от них считаются срок между запусками и пауза после сбоя. */\n startedAt: string;\n finishedAt: string | null;\n status: MemoryOptimizationStatus;\n /** Склейки запуска; у не слитых запусков пусто. */\n groups: MemoryMergeGroup[];\n /** Причина сбоя одним словом-кодом; у остальных `null`. */\n error: string | null;\n /** Запись расхода модели, если вызывающий её знает. */\n usageRecordId: string | null;\n}\n\n/** Правила запуска: их проверяет хранилище в той же операции, что и берёт запуск, чтобы два процесса не начали его вдвоём. */\nexport interface MemoryOptimizationRules {\n /** Сколько должно пройти после законченного запуска (слито, нечего сливать, отменено) до следующего. */\n minIntervalMs: number;\n /**\n * Сколько должно пройти после неудачного запуска до повтора. Число — одна пауза на все неудачи; список — паузы после\n * 1-й, 2-й, … неудачи подряд (последняя держится дальше). Успешный, пустой или отменённый запуск начинает счёт заново.\n */\n retryAfterFailureMs: number | readonly number[];\n /** Сколько запуск `running` считается живым; после этого срока он становится неудачным (`failed`, ошибка `lease_expired`). */\n leaseMs: number;\n}\n\n/** Чем закончился запуск без слияния. */\nexport interface MemoryOptimizationOutcome {\n status: \"no_changes\" | \"failed\";\n finishedAt: string;\n error?: string | null;\n usageRecordId?: string | null;\n}\n\n/** Что слить: новая запись-результат вместо оригиналов; версии оригиналов — те, что видел планировщик. */\nexport interface MemoryMergeInput {\n runId: string;\n finishedAt: string;\n usageRecordId?: string | null;\n merges: { result: MemoryNoteInput; originals: { id: string; version: number }[] }[];\n}\n\n/** Скрытая запись слияния: оригинал, подчинённый результату. Список, блок памяти и поиск таких записей не видят. */\nexport interface MergedMemoryNote extends MemoryNote {\n mergedInto: string;\n}\n\nexport type MemoryMergeApplyResult = MemoryOptimizationRun | \"stale_version\" | \"run_not_active\";\n/** `limit_reached` и `duplicate` — возврат вышел бы за пределы памяти или вернул бы повтор записи, сделанной за это время. */\nexport type MemoryMergeRevertResult = \"ok\" | \"not_found\" | \"conflict\" | \"limit_reached\" | \"duplicate\";\n\n/** Запуск в том виде, что нужен для решения «можно ли начать новый»; время — миллисекунды. */\nexport interface OptimizationRunTiming {\n status: MemoryOptimizationStatus;\n startedAtMs: number;\n /** Конец запуска; у идущего его нет. */\n endedAtMs: number | null;\n}\n\n/** Пауза после неудачи с номером `failuresInRow` (с 1) подряд. */\nexport function pauseAfterFailure(rules: Pick<MemoryOptimizationRules, \"retryAfterFailureMs\">, failuresInRow: number): number {\n const { retryAfterFailureMs } = rules;\n if (typeof retryAfterFailureMs === \"number\") return retryAfterFailureMs;\n return retryAfterFailureMs[Math.min(failuresInRow, retryAfterFailureMs.length) - 1] ?? 0;\n}\n\n/**\n * Мешает ли история запусков начать новый: идёт другой, или не вышла пауза после последнего. Пауза после неудачи\n * растёт с числом неудач подряд. Все три хранилища решают этим одним правилом.\n */\nexport function isOptimizationBlocked(runs: readonly OptimizationRunTiming[], rules: MemoryOptimizationRules, nowMs: number): boolean {\n const ordered = [...runs].sort((left, right) => left.startedAtMs - right.startedAtMs);\n let failuresInRow = 0;\n for (const run of ordered) {\n if (run.status === \"running\") return true;\n failuresInRow = run.status === \"failed\" ? failuresInRow + 1 : 0;\n const pause = run.status === \"failed\" ? pauseAfterFailure(rules, failuresInRow) : rules.minIntervalMs;\n if (nowMs - (run.endedAtMs ?? run.startedAtMs) < pause) return true;\n }\n return false;\n}\n\n/** Ответ отката слияния на ошибку проверки: повтор записи — `duplicate`, всё остальное (число, объём) — `limit_reached`. */\nexport function revertRefusal(error: MemoryError): \"limit_reached\" | \"duplicate\" {\n return error.kind === \"duplicate\" ? \"duplicate\" : \"limit_reached\";\n}\n","/** Соединение PostgreSQL, которое нужно помощнику миграций. Подходит `PoolClient` из `pg`: core от `pg` не зависит. */\nexport interface PostgresMigrationConnection {\n query(text: string, values?: unknown[]): Promise<{ rows: unknown[] }>;\n release(): void;\n}\n\nexport interface PostgresMigrationOptions<Connection extends PostgresMigrationConnection> {\n /** Новое соединение из пула, например `() => pool.connect()`; на каждую попытку — своё. */\n connect(): Promise<Connection>;\n /** Своя блокировка пакета: одновременно мигрирует только одна копия этого пакета. */\n lockKey: number;\n /** Миграции пакета на этом соединении, например `migrate(drizzle(connection), …)`. */\n migrate(connection: Connection): Promise<void>;\n /** Паузы перед повторами, если всё же столкнулись с соседом. */\n retryDelaysMs?: readonly number[];\n}\n\n/** Схема всех пакетов SDK. */\nexport const AGENT_CHAT_SCHEMA = \"agent_chat\";\n\n/** Общая блокировка на создание схемы: одна на все пакеты, держится только на `create schema`. */\nexport const AGENT_CHAT_SCHEMA_LOCK_KEY = 7_311_900_441;\n\nconst DEFAULT_RETRY_DELAYS_MS = [25, 50, 100, 200];\n\n/** Коды PostgreSQL «такое уже есть»: сосед создал то же самое одновременно с нами. */\nconst DUPLICATE_CODES = new Set([\"23505\", \"42P06\", \"42P07\", \"42710\"]);\n\n/**\n * Создаёт схему `agent_chat` и применяет миграции пакета так, чтобы пакеты могли стартовать одновременно на пустой базе.\n * Схема создаётся под общей блокировкой, затем она отпускается и берётся своя блокировка пакета: блокировки не\n * вложены, поэтому взаимной блокировки нет, а разные пакеты ждут друг друга только на создании схемы.\n * Столкнулись всё же («уже есть») — повтор с начала на новом соединении.\n */\nexport async function migratePostgresSchema<Connection extends PostgresMigrationConnection>(options: PostgresMigrationOptions<Connection>): Promise<void> {\n const delays = options.retryDelaysMs ?? DEFAULT_RETRY_DELAYS_MS;\n for (let attempt = 0; ; attempt++) {\n try {\n return await migrateOnce(options);\n } catch (error) {\n if (attempt >= delays.length || !isDuplicateError(error)) throw error;\n await new Promise((resolve) => setTimeout(resolve, delays[attempt]));\n }\n }\n}\n\nasync function migrateOnce<Connection extends PostgresMigrationConnection>(options: PostgresMigrationOptions<Connection>): Promise<void> {\n const connection = await options.connect();\n try {\n if (!(await schemaExists(connection))) {\n await underLock(connection, AGENT_CHAT_SCHEMA_LOCK_KEY, () => connection.query(`create schema if not exists \"${AGENT_CHAT_SCHEMA}\"`));\n }\n await underLock(connection, options.lockKey, () => options.migrate(connection));\n } finally {\n connection.release();\n }\n}\n\nasync function schemaExists(connection: PostgresMigrationConnection): Promise<boolean> {\n const result = await connection.query(\"select 1 from pg_namespace where nspname = $1\", [AGENT_CHAT_SCHEMA]);\n return result.rows.length > 0;\n}\n\nasync function underLock(connection: PostgresMigrationConnection, key: number, work: () => Promise<unknown>): Promise<void> {\n await connection.query(\"select pg_advisory_lock($1)\", [key]);\n try {\n await work();\n } finally {\n await connection.query(\"select pg_advisory_unlock($1)\", [key]);\n }\n}\n\n/** Ошибка «уже есть» — у самой ошибки или в цепочке причин (drizzle заворачивает ошибку `pg` в свою). */\nfunction isDuplicateError(error: unknown): boolean {\n for (let current = error, depth = 0; current && depth < 5; current = (current as { cause?: unknown }).cause, depth++) {\n const code = (current as { code?: unknown }).code;\n if (typeof code === \"string\" && DUPLICATE_CODES.has(code)) return true;\n }\n return false;\n}\n","/**\n * Защита от запросов с чужих страниц. Браузер сам прикладывает к запросу куки человека, даже если запрос\n * отправила чужая страница (обычная HTML-форма, WebSocket). Отличить свою страницу от чужой помогают заголовки,\n * которые ставит только браузер: `Sec-Fetch-Site` и `Origin`. Порядок проверки как в Go 1.25 (CrossOriginProtection).\n */\n\nexport interface RequestOriginCheckOptions {\n /**\n * Чужие страницы, которым можно слать запросы с куками человека, например `[\"https://school.getcourse.ru\"]`\n * (схема, хост, порт). Шаблон `https://*.example.com` пускает все поддомены `example.com`, но не сам домен\n * и не `evil-example.com`. Свой сайт в список вносить не нужно.\n */\n allowedOrigins?: readonly string[] | undefined;\n /** Отказ: вызывается один раз на каждый новый адрес страницы, чтобы журнал не забивался. */\n onRefused?: (refusal: OriginRefusal) => void;\n}\n\n/**\n * Почему запрос отклонён. `cross_site` — браузер сам сказал, что страница чужая. `host_mismatch` — браузер старый\n * или адрес не https, и Origin не совпал ни с одним именем сервера (`hosts`): так бывает за прокси, который подменил Host.\n */\nexport type OriginRefusal = { origin: string; reason: \"cross_site\" } | { origin: string; reason: \"host_mismatch\"; hosts: string[] };\n\n/** Подсказка для журнала: что поправить, если отклонена своя страница. */\nexport function originRefusalHint(refusal: OriginRefusal): string {\n if (refusal.reason === \"host_mismatch\") {\n return (\n `отклонён запрос со страницы ${refusal.origin}: Origin не совпал с Host (${refusal.hosts.join(\", \")}). ` +\n \"Если сервер за прокси, за прокси передайте Host/X-Forwarded-Host (nginx: proxy_set_header Host $host; \" +\n \"proxy_set_header X-Forwarded-Host $host;). Если это чужая для сервера, но ваша страница, добавьте её адрес в allowedOrigins.\"\n );\n }\n return `отклонён запрос с чужой страницы ${refusal.origin}. Если это ваша страница, добавьте её адрес в allowedOrigins.`;\n}\n\n/** `true` — запрос со своей страницы, со страницы из списка или не из браузера. Метод запроса не смотрит. */\nexport type RequestOriginCheck = (request: Request) => boolean;\n\n/** Чтения (GET, HEAD, OPTIONS) не меняют данных: их ответ браузер чужой странице всё равно не покажет. */\nexport function isSafeMethod(method: string): boolean {\n const upper = method.toUpperCase();\n return upper === \"GET\" || upper === \"HEAD\" || upper === \"OPTIONS\";\n}\n\n/** Сколько разных отклонённых адресов запоминается для журнала. */\nconst MAX_REPORTED_ORIGINS = 100;\nconst MAX_REPORTED_LENGTH = 200;\n\nexport function createRequestOriginCheck(options: RequestOriginCheckOptions = {}): RequestOriginCheck {\n const isAllowed = allowedOriginsMatcher(options.allowedOrigins ?? []);\n const reported = new Set<string>();\n const refuse = (refusal: OriginRefusal): false => {\n const shown = refusal.origin.slice(0, MAX_REPORTED_LENGTH);\n if (options.onRefused && !reported.has(shown) && reported.size < MAX_REPORTED_ORIGINS) {\n reported.add(shown);\n options.onRefused({ ...refusal, origin: shown });\n }\n return false;\n };\n return (request) => {\n const origin = request.headers.get(\"origin\");\n if (origin !== null && isAllowed(origin)) return true;\n const site = request.headers.get(\"sec-fetch-site\");\n if (site !== null) {\n if (site === \"same-origin\" || site === \"none\") return true;\n return refuse({ origin: origin ?? `(без Origin, Sec-Fetch-Site: ${site})`, reason: \"cross_site\" });\n }\n // Нет ни того ни другого — не браузер (сервер, curl): куки человека чужая страница так не приложит.\n if (origin === null) return true;\n // Старый браузер без Sec-Fetch-Site: Origin должен совпасть с адресом самого сервера.\n const host = originHost(origin);\n const hosts = hostsOf(request);\n if (host !== null && hosts.has(host)) return true;\n return refuse({ origin, reason: \"host_mismatch\", hosts: [...hosts] });\n };\n}\n\n/** Метка вместо `*`: адрес с ней разбирает URL, а в настоящем имени хоста она не встречается. */\nconst WILDCARD_LABEL = \"wildcard-label-0\";\n\n/** Точные адреса и шаблоны поддоменов из `allowedOrigins`; неверная запись — ошибка сразу. */\nfunction allowedOriginsMatcher(entries: readonly string[]): (origin: string) => boolean {\n const exact = new Set<string>();\n const subdomainsOf: Array<{ protocol: string; port: string; suffix: string }> = [];\n for (const entry of entries) {\n const wildcard = /^(https?:\\/\\/)\\*\\.([^*]+)$/.exec(entry);\n const url = parsePageAddress(wildcard ? `${wildcard[1]}${WILDCARD_LABEL}.${wildcard[2]}` : entry);\n if (url === null || (!wildcard && entry.includes(\"*\"))) throw wrongEntry(entry);\n if (!wildcard) {\n exact.add(url.origin);\n continue;\n }\n const suffix = url.hostname.slice(WILDCARD_LABEL.length);\n // `*.com` пустил бы весь интернет: после звёздочки нужен домен хотя бы из двух частей (или localhost).\n // Точка в конце (`*.com.`) — тот же `*.com`. Общие суффиксы вроде `*.co.uk` так не отличить: вписывают свой домен.\n const tooBroad = suffix.endsWith(\".\") || (!suffix.slice(1).includes(\".\") && suffix !== \".localhost\");\n if (!url.hostname.startsWith(`${WILDCARD_LABEL}.`) || tooBroad) throw wrongEntry(entry);\n subdomainsOf.push({ protocol: url.protocol, port: url.port, suffix });\n }\n return (origin) => {\n const url = parseOrigin(origin);\n if (url === null) return false;\n if (exact.has(url.origin)) return true;\n return subdomainsOf.some(\n (pattern) =>\n url.protocol === pattern.protocol && url.port === pattern.port && url.hostname.endsWith(pattern.suffix) && url.hostname.length > pattern.suffix.length,\n );\n };\n}\n\n/** Адрес страницы: http(s), без пути, запроса, якоря и имени пользователя. Иначе `null`. */\nfunction parsePageAddress(entry: string): URL | null {\n try {\n const url = new URL(entry);\n const bare = url.pathname === \"/\" && !url.search && !url.hash && !url.username;\n return (url.protocol === \"http:\" || url.protocol === \"https:\") && bare ? url : null;\n } catch {\n return null;\n }\n}\n\nfunction wrongEntry(entry: string): Error {\n return new Error(\n `allowedOrigins: «${entry}» не адрес страницы; нужен вид https://app.example, http://localhost:5173 или https://*.example.com ` +\n \"(в шаблоне — свой домен: общие суффиксы вроде *.co.uk проверка не распознаёт)\",\n );\n}\n\nfunction parseOrigin(origin: string): URL | null {\n try {\n return new URL(origin);\n } catch {\n return null;\n }\n}\n\n/** Хост с портом из Origin; `null` и не адрес — `null`. */\nfunction originHost(origin: string): string | null {\n try {\n const url = new URL(origin);\n return url.protocol === \"http:\" || url.protocol === \"https:\" ? url.host : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Под какими именами сервер знает себя: `Host` и `X-Forwarded-Host` от прокси. Адрес запроса — только без `Host`:\n * при подъёме WebSocket в Node (`@hono/node-ws`) он собран как `http://localhost/…` и порта в нём нет.\n */\nfunction hostsOf(request: Request): Set<string> {\n const host = request.headers.get(\"host\");\n const hosts = new Set<string>([host ? host.trim().toLowerCase() : new URL(request.url).host]);\n for (const forwarded of request.headers.get(\"x-forwarded-host\")?.split(\",\") ?? []) {\n if (forwarded.trim()) hosts.add(forwarded.trim().toLowerCase());\n }\n return hosts;\n}\n","import type { ModelChoice } from \"../models/model-choice\";\nimport type { MemoryNote, MemoryNoteInput } from \"../memory/long-term-memory\";\nimport { isOptimizationBlocked, revertRefusal } from \"../memory/memory-optimization\";\nimport type { MemoryMergeGroup, MemoryOptimizationRun, MergedMemoryNote } from \"../memory/memory-optimization\";\nimport type { Chat, StoredBranchSelection, StoredCompaction, StoredMessage, UsageRecord, UsageSum } from \"../conversation/types\";\nimport {\n aliveAtByLocalClock,\n assertKnownFormatVersion,\n ChatAlreadyExistsError,\n DuplicateChatSubjectError,\n DuplicateMessageError,\n MessageAlreadyFinishedError,\n MessageNotAwaitingError,\n sameMessageParts,\n silenceBoundaryByLocalClock,\n} from \"./chat-storage\";\nimport { assertChatDataFilter, chatDataFields, chatDataMatches, chatDataOfInput, mergeChatData } from \"./chat-data\";\nimport type { ChatListOptions, ChatStorage, ChatSubjectFilter, SpendingLimitStateRecord, StoredPendingBurst } from \"./chat-storage\";\n\n/** Хранилище в памяти процесса — для тестов. Копирует данные на входе и выходе, как база. */\ntype StoredNote = MemoryNote & { mergedInto?: string };\n\n/** Сумма записей расхода по ключу (`group` — поля ключа: пара «модель × провайдер», у суммы по чатам — ещё чат). */\nfunction sumRecords<K extends Pick<UsageSum, \"modelId\" | \"providerId\">>(records: readonly UsageRecord[], group: (record: UsageRecord) => K): Array<K & UsageSum> {\n const sums = new Map<string, K & UsageSum>();\n for (const record of records) {\n const fields = group(record);\n const key = Object.values(fields).join(\"\\u0000\");\n const sum = sums.get(key) ?? { ...fields, inputTokens: 0, cachedInputTokens: 0, cacheWriteTokens: 0, outputTokens: 0, reasoningTokens: 0, costUsd: 0 };\n sum.inputTokens += record.inputTokens;\n sum.cachedInputTokens += record.cachedInputTokens;\n sum.cacheWriteTokens += record.cacheWriteTokens;\n sum.outputTokens += record.outputTokens;\n sum.reasoningTokens += record.reasoningTokens;\n sum.costUsd += record.costUsd;\n sums.set(key, sum);\n }\n return [...sums.values()];\n}\n\nexport function createInMemoryChatStorage(): ChatStorage {\n const chats = new Map<string, Chat>();\n const messages = new Map<string, StoredMessage>();\n /** Время последнего знака жизни идущих ответов по id: при чтении не отдаётся. */\n const aliveAt = new Map<string, string>();\n /** Аренды хода по чатам: кто держит и до какого времени (мс по часам этой машины). */\n const turnLeases = new Map<string, { holder: string; until: number }>();\n /** Ждущие пачки по чатам. */\n const pendingBursts = new Map<string, StoredPendingBurst>();\n const compactions: StoredCompaction[] = [];\n const branchSelections: StoredBranchSelection[] = [];\n const usageRecords: UsageRecord[] = [];\n const userModelChoices = new Map<string, ModelChoice>();\n const voiceChoices = new Map<string, string>();\n const spendingLimitStates = new Map<string, SpendingLimitStateRecord>();\n /** Долгая память: у каждого человека свои записи; у скрытых оригиналов слияния есть `mergedInto`. */\n const memoryNotes = new Map<string, Map<string, StoredNote>>();\n const optimizationRuns = new Map<string, MemoryOptimizationRun[]>();\n\n function streamingMessage(messageId: string): StoredMessage {\n const message = messages.get(messageId);\n if (!message) throw new Error(`Сообщения ${messageId} нет`);\n if (message.status !== \"streaming\") throw new MessageAlreadyFinishedError(messageId);\n return message;\n }\n\n /** Сообщение для чтения: копия, `voiceCut` — `null`, если не задан. */\n function readable(message: StoredMessage): StoredMessage {\n const copy = structuredClone(message);\n return { ...copy, voiceCut: copy.voiceCut ?? null };\n }\n\n function ownerNotes(ownerId: string): Map<string, StoredNote> {\n let notes = memoryNotes.get(ownerId);\n if (!notes) {\n notes = new Map();\n memoryNotes.set(ownerId, notes);\n }\n return notes;\n }\n\n function ownerRuns(ownerId: string): MemoryOptimizationRun[] {\n let runs = optimizationRuns.get(ownerId);\n if (!runs) {\n runs = [];\n optimizationRuns.set(ownerId, runs);\n }\n return runs;\n }\n\n /** Порядок списка по договору: `updatedAt`, затем `createdAt`, затем `id` — новее и больше первыми. */\n function newestFirst<T extends MemoryNote>(notes: T[]): T[] {\n return notes.sort(\n (left, right) =>\n right.updatedAt.localeCompare(left.updatedAt) ||\n right.createdAt.localeCompare(left.createdAt) ||\n (right.id > left.id ? 1 : right.id < left.id ? -1 : 0),\n );\n }\n\n /** Обычная запись без служебного признака скрытия. */\n function publicNote({ mergedInto: _hidden, ...note }: StoredNote): MemoryNote {\n return note;\n }\n\n /** Как база с NOT NULL: разделы — список строк, текст — строка. */\n function assertWellFormedNote(note: MemoryNoteInput): void {\n if (!Array.isArray(note?.categories) || !note.categories.every((category) => typeof category === \"string\")) {\n throw new TypeError(\"Разделы записи памяти — список строк\");\n }\n if (typeof note.content !== \"string\") throw new TypeError(\"Текст записи памяти — строка\");\n }\n\n /** Записи, которые видит человек: без скрытых оригиналов, копиями. */\n function visibleNotes(ownerId: string): MemoryNote[] {\n const visible = [...ownerNotes(ownerId).values()].filter((note) => !note.mergedInto);\n return structuredClone(newestFirst(visible).map(publicNote));\n }\n\n function insertNotes(ownerId: string, inputs: MemoryNoteInput[]): MemoryNote[] {\n // Сначала проверяем весь пакет: негодная запись не оставляет в памяти и остальных.\n inputs.forEach(assertWellFormedNote);\n const now = new Date().toISOString();\n const created = inputs.map(\n ({ categories, content }): MemoryNote => ({\n id: crypto.randomUUID(),\n categories: [...categories],\n content,\n version: 1,\n createdAt: now,\n updatedAt: now,\n }),\n );\n const notes = ownerNotes(ownerId);\n for (const note of created) notes.set(note.id, note);\n return structuredClone(created);\n }\n\n function updateNote(ownerId: string, id: string, expectedVersion: number, input: MemoryNoteInput): MemoryNote | \"not_found\" | \"stale_version\" {\n assertWellFormedNote(input);\n const notes = ownerNotes(ownerId);\n const note = notes.get(id);\n if (!note || note.mergedInto) return \"not_found\";\n if (note.version !== expectedVersion) return \"stale_version\";\n const updated: MemoryNote = {\n ...note,\n categories: [...input.categories],\n content: input.content,\n version: note.version + 1,\n updatedAt: new Date().toISOString(),\n };\n notes.set(id, updated);\n return structuredClone(updated);\n }\n\n /** Как уникальный индекс (владелец, ключ): чат без ключа не мешает никому. */\n function assertSubjectFree(chat: Pick<Chat, \"ownerId\" | \"subjectKey\">): void {\n if (chat.subjectKey === undefined) return;\n for (const existing of chats.values()) {\n if (existing.ownerId === chat.ownerId && existing.subjectKey === chat.subjectKey) {\n throw new DuplicateChatSubjectError(chat.ownerId, chat.subjectKey);\n }\n }\n }\n\n function sortedMessages(chatId: string): StoredMessage[] {\n return [...messages.values()]\n .filter((message) => message.chatId === chatId)\n .sort((left, right) => left.createdAt.localeCompare(right.createdAt));\n }\n\n return {\n async createChat({ createdAt, ...input }) {\n const at = createdAt ?? new Date().toISOString();\n const chat: Chat = withChatData(withoutUndefined({ ...structuredClone(input), createdAt: at, updatedAt: at }));\n assertSubjectFree(chat);\n chats.set(chat.id, chat);\n return structuredClone(chat);\n },\n\n async findChatBySubject(ownerId, subjectKey) {\n const chat = [...chats.values()].find((existing) => existing.ownerId === ownerId && existing.subjectKey === subjectKey);\n return chat ? structuredClone(chat) : null;\n },\n\n async importChat(input, imported) {\n // Сначала все проверки: при ошибке ничего не записано.\n if (chats.has(input.id)) throw new ChatAlreadyExistsError(input.id);\n const chat: Chat = withChatData(withoutUndefined(structuredClone(input)));\n assertSubjectFree(chat);\n const ids = new Set<string>();\n for (const message of imported) {\n if (messages.has(message.id) || ids.has(message.id)) throw new DuplicateMessageError(message.id);\n ids.add(message.id);\n }\n chats.set(chat.id, chat);\n for (const message of imported) {\n const { alive: _alive, ...stored } = message;\n messages.set(message.id, structuredClone(stored));\n }\n },\n\n async setAfterTurnState(messageId, state, options) {\n const message = messages.get(messageId);\n if (!message || (state !== \"pending\" && message.afterTurn !== \"pending\")) return false;\n const parts = options?.appendParts ? [...message.parts, ...structuredClone(options.appendParts)] : message.parts;\n messages.set(messageId, { ...message, parts, afterTurn: state });\n // Отметка ожидания хука — в той же колонке, что знак жизни: у законченного ответа она больше ни для чего.\n if (state === \"pending\") aliveAt.set(messageId, aliveAtByLocalClock(\"now\"));\n return true;\n },\n\n async listPendingAfterTurn(filter) {\n const boundary = filter?.pendingForMs === undefined ? null : silenceBoundaryByLocalClock(filter.pendingForMs);\n return [...messages.values()]\n .filter((message) => message.afterTurn === \"pending\")\n .filter((message) => boundary === null || (aliveAt.get(message.id) ?? message.createdAt) < boundary)\n .map((message) => message.id);\n },\n\n async getChat(chatId) {\n const chat = chats.get(chatId);\n return chat ? structuredClone(chat) : null;\n },\n\n async updateChatModel(chatId, model) {\n const chat = chats.get(chatId);\n if (!chat) return;\n const { reasoning: _dropped, ...rest } = chat;\n chats.set(chatId, { ...rest, ...model });\n },\n\n async listChats(ownerId, options?: ChatListOptions) {\n if (options?.data) assertChatDataFilter(options.data);\n const owned = [...chats.values()].filter(\n (chat) => chat.ownerId === ownerId && matchesSubjects(chat, options?.subjects) && (!options?.data || chatDataMatches(chat.data, options.data)),\n );\n owned.sort(\n (left, right) =>\n right.updatedAt.localeCompare(left.updatedAt) ||\n right.createdAt.localeCompare(left.createdAt) ||\n (right.id > left.id ? 1 : right.id < left.id ? -1 : 0),\n );\n return structuredClone(owned);\n },\n\n async updateChatData(chatId, patch, { maxBytes }) {\n const chat = chats.get(chatId);\n if (!chat) return null;\n // Слияние и проверка размера до записи: при ошибке данные прежние.\n const merged = mergeChatData(chat.data, patch, maxBytes);\n const { data: _data, subjectData: _subjectData, ...rest } = chat;\n const updated: Chat = { ...rest, ...chatDataFields(merged) };\n chats.set(chatId, updated);\n return structuredClone(updated);\n },\n\n async renameChat(chatId, title) {\n const chat = chats.get(chatId);\n if (!chat) return;\n const { title: _dropped, ...rest } = chat;\n chats.set(chatId, title === null ? rest : { ...rest, title });\n },\n\n async setChatClosed(chatId, closed) {\n const chat = chats.get(chatId);\n if (!chat) return;\n const { closed: _dropped, ...rest } = chat;\n chats.set(chatId, closed === null ? rest : { ...rest, closed: { code: closed.code, message: closed.message } });\n },\n\n async deleteChat(chatId) {\n if (!chats.delete(chatId)) return;\n turnLeases.delete(chatId);\n pendingBursts.delete(chatId);\n for (const message of [...messages.values()]) {\n if (message.chatId !== chatId) continue;\n messages.delete(message.id);\n aliveAt.delete(message.id);\n }\n // Записи сжатия — на месте, с обратным ходом, чтобы индексы не сдвигались.\n for (let index = compactions.length - 1; index >= 0; index -= 1) {\n if (compactions[index]!.chatId === chatId) compactions.splice(index, 1);\n }\n for (let index = branchSelections.length - 1; index >= 0; index -= 1) {\n if (branchSelections[index]!.chatId === chatId) branchSelections.splice(index, 1);\n }\n },\n\n async getMessage(messageId) {\n const message = messages.get(messageId);\n if (!message) return null;\n assertKnownFormatVersion(message);\n return readable(message);\n },\n\n async listMessages(chatId) {\n const found = sortedMessages(chatId);\n found.forEach(assertKnownFormatVersion);\n return found.map(readable);\n },\n\n async listMessageLinks(chatId) {\n return sortedMessages(chatId).map(({ id, parentId, createdAt }) => ({ id, parentId, createdAt }));\n },\n\n async listMessagePartsOfTypes(chatId, types) {\n return sortedMessages(chatId).map(({ id, parentId, createdAt, parts }) => ({\n id,\n parentId,\n createdAt,\n parts: structuredClone(parts.filter((part) => types.includes(part.type))),\n }));\n },\n\n async insertMessage(message) {\n if (messages.has(message.id)) throw new DuplicateMessageError(message.id);\n // Как внешний ключ в базе: сообщение без чата не пишется.\n const chat = chats.get(message.chatId);\n if (!chat) throw new Error(`Чата ${message.chatId} нет`);\n const { alive, ...stored } = message;\n messages.set(message.id, structuredClone(stored));\n if (alive) aliveAt.set(message.id, aliveAtByLocalClock(alive));\n chats.set(chat.id, { ...chat, updatedAt: message.createdAt });\n },\n\n async updateStreamingMessage(messageId, patch) {\n const message = streamingMessage(messageId);\n const { alive, ...rest } = structuredClone(patch);\n messages.set(messageId, { ...message, ...rest });\n if (alive) aliveAt.set(messageId, aliveAtByLocalClock(alive));\n },\n\n async markAnswerAlive(messageId) {\n streamingMessage(messageId);\n aliveAt.set(messageId, aliveAtByLocalClock(\"now\"));\n },\n\n async finishMessage(messageId, final) {\n const message = streamingMessage(messageId);\n messages.set(messageId, { ...message, ...structuredClone(final) });\n if (final.afterTurn === \"pending\") aliveAt.set(messageId, aliveAtByLocalClock(\"now\"));\n },\n\n async updateAwaitingMessage(messageId, patch) {\n const message = messages.get(messageId);\n if (!message) throw new Error(`Сообщения ${messageId} нет`);\n if (message.status !== \"awaiting_approval\") throw new MessageNotAwaitingError(messageId);\n if (patch.expectedParts && !sameMessageParts(message.parts, patch.expectedParts)) throw new MessageNotAwaitingError(messageId);\n messages.set(messageId, {\n ...message,\n parts: structuredClone(patch.parts),\n ...(patch.status && { status: patch.status }),\n });\n if (patch.alive) aliveAt.set(messageId, aliveAtByLocalClock(patch.alive));\n },\n\n async listStreamingMessageIds() {\n return [...messages.values()].filter((message) => message.status === \"streaming\").map((message) => message.id);\n },\n\n async listSilentAnswerIds({ silentForMs, chatId }) {\n const boundary = silenceBoundaryByLocalClock(silentForMs);\n return [...messages.values()]\n .filter((message) => message.status === \"streaming\" && (chatId === undefined || message.chatId === chatId))\n .filter((message) => (aliveAt.get(message.id) ?? message.createdAt) < boundary)\n .map((message) => message.id);\n },\n\n async claimChatTurn(chatId, holder, leaseMs) {\n const now = Date.now();\n const lease = turnLeases.get(chatId);\n if (lease && lease.holder !== holder && lease.until > now) return false;\n turnLeases.set(chatId, { holder, until: now + leaseMs });\n return true;\n },\n\n async releaseChatTurn(chatId, holder) {\n if (turnLeases.get(chatId)?.holder === holder) turnLeases.delete(chatId);\n },\n\n async clearChatTurnLeases() {\n turnLeases.clear();\n },\n\n async savePendingBurst(burst) {\n pendingBursts.set(burst.chatId, structuredClone(burst));\n },\n\n async takeAbandonedBursts({ abandonedBefore, holder, heldUntil, limit, exceptHolder }) {\n const abandoned = [...pendingBursts.values()]\n .filter((burst) => Date.parse(burst.heldUntil) <= Date.parse(abandonedBefore) && burst.holder !== exceptHolder)\n .sort((first, second) => Date.parse(first.dueAt) - Date.parse(second.dueAt))\n .slice(0, limit);\n return abandoned.map((burst) => {\n const taken = { ...burst, holder, heldUntil };\n pendingBursts.set(burst.chatId, taken);\n return structuredClone(taken);\n });\n },\n\n async renewPendingBurst({ chatId, messageId, holder, heldUntil }) {\n const burst = pendingBursts.get(chatId);\n if (burst?.messageId !== messageId || burst.holder !== holder) return false;\n pendingBursts.set(chatId, { ...burst, heldUntil });\n return true;\n },\n\n async removePendingBurst(chatId, messageId) {\n if (pendingBursts.get(chatId)?.messageId === messageId) pendingBursts.delete(chatId);\n },\n\n async insertCompaction(record) {\n if (compactions.some((existing) => existing.id === record.id)) throw new Error(`Запись сжатия ${record.id} уже есть`);\n compactions.push(structuredClone(record));\n },\n\n async listCompactions(chatId) {\n return structuredClone(compactions.filter((record) => record.chatId === chatId));\n },\n\n async insertBranchSelection(record) {\n if (branchSelections.some((existing) => existing.id === record.id)) throw new Error(`Выбор ветки ${record.id} уже есть`);\n branchSelections.push(structuredClone(record));\n },\n\n async listBranchSelections(chatId) {\n return structuredClone(branchSelections.filter((record) => record.chatId === chatId));\n },\n\n async insertUsageRecord(record) {\n if (usageRecords.some((existing) => existing.id === record.id)) throw new Error(`Запись расхода ${record.id} уже есть`);\n usageRecords.push(structuredClone(record));\n },\n\n async hasUsageRecord(id) {\n return usageRecords.some((record) => record.id === id);\n },\n\n async sumUsage({ ownerId, chatId, messageId, source, since }) {\n return sumRecords(\n usageRecords.filter(\n (record) =>\n (ownerId === undefined || record.ownerId === ownerId) &&\n (chatId === undefined || record.chatId === chatId) &&\n (messageId === undefined || record.messageId === messageId) &&\n (source === undefined || record.source === source) &&\n (since === undefined || record.createdAt >= since),\n ),\n (record) => ({ modelId: record.modelId, providerId: record.providerId }),\n );\n },\n\n async sumUsageByChat({ chatIds, ownerId, source, since }) {\n const wanted = new Set(chatIds);\n return sumRecords(\n usageRecords.filter(\n (record) =>\n wanted.has(record.chatId) &&\n (ownerId === undefined || record.ownerId === ownerId) &&\n (source === undefined || record.source === source) &&\n (since === undefined || record.createdAt >= since),\n ),\n (record) => ({ chatId: record.chatId, modelId: record.modelId, providerId: record.providerId }),\n );\n },\n\n async setVoiceCut({ ownerId, chatId, messageId, voiceCut }) {\n const message = messages.get(messageId);\n if (!message || message.chatId !== chatId || chats.get(chatId)?.ownerId !== ownerId) return;\n messages.set(messageId, { ...message, voiceCut: structuredClone(voiceCut) });\n },\n\n async getSpendingLimitState(key) {\n const record = spendingLimitStates.get(key);\n return record ? { ...record } : null;\n },\n\n async saveSpendingLimitState(key, value, expectedVersion) {\n const current = spendingLimitStates.get(key);\n if ((current?.version ?? null) !== expectedVersion) return false;\n spendingLimitStates.set(key, { value, version: (current?.version ?? 0) + 1 });\n return true;\n },\n\n async deleteSpendingLimitStates(keys) {\n for (const key of keys) spendingLimitStates.delete(key);\n },\n\n async getVoiceChoice(ownerId) {\n return voiceChoices.get(ownerId) ?? null;\n },\n\n async setVoiceChoice(ownerId, voiceId) {\n voiceChoices.set(ownerId, voiceId);\n },\n\n async getUserModelChoice(ownerId) {\n const choice = userModelChoices.get(ownerId);\n return choice ? structuredClone(choice) : null;\n },\n\n async saveUserModelChoice(ownerId, choice) {\n userModelChoices.set(ownerId, structuredClone(choice));\n },\n\n async listMemoryNotes(ownerId) {\n return visibleNotes(ownerId);\n },\n\n async insertMemoryNotes(ownerId, inputs) {\n return insertNotes(ownerId, inputs);\n },\n\n // Всё выполняется без ожидания, поэтому между проверкой и вставкой никто не успеет вклиниться.\n async insertMemoryNotesChecked(ownerId, inputs, guard) {\n const error = guard(visibleNotes(ownerId));\n return error ?? insertNotes(ownerId, inputs);\n },\n\n async updateMemoryNote(ownerId, id, expectedVersion, input) {\n return updateNote(ownerId, id, expectedVersion, input);\n },\n\n async updateMemoryNoteChecked(ownerId, id, expectedVersion, input, guard) {\n return guard(visibleNotes(ownerId)) ?? updateNote(ownerId, id, expectedVersion, input);\n },\n\n async deleteMemoryNote(ownerId, id, expectedVersion) {\n const notes = ownerNotes(ownerId);\n const note = notes.get(id);\n if (!note || note.mergedInto) return \"not_found\";\n if (note.version !== expectedVersion) return \"stale_version\";\n notes.delete(id);\n for (const [hiddenId, hidden] of notes) if (hidden.mergedInto === id) notes.delete(hiddenId);\n return \"ok\";\n },\n\n async clearMemoryNotes(ownerId) {\n const count = [...ownerNotes(ownerId).values()].filter((note) => !note.mergedInto).length;\n memoryNotes.delete(ownerId);\n optimizationRuns.delete(ownerId);\n return count;\n },\n\n async claimMemoryOptimizationRun(ownerId, rules, now) {\n const runs = ownerRuns(ownerId);\n const nowMs = Date.parse(now);\n for (const run of runs) {\n const leaseEnd = Date.parse(run.startedAt) + rules.leaseMs;\n if (run.status === \"running\" && nowMs >= leaseEnd) Object.assign(run, { status: \"failed\", error: \"lease_expired\", finishedAt: new Date(leaseEnd).toISOString() });\n }\n const blocked = isOptimizationBlocked(\n runs.map((run) => ({ status: run.status, startedAtMs: Date.parse(run.startedAt), endedAtMs: run.finishedAt ? Date.parse(run.finishedAt) : null })),\n rules,\n nowMs,\n );\n if (blocked) return null;\n const run: MemoryOptimizationRun = { id: crypto.randomUUID(), startedAt: now, finishedAt: null, status: \"running\", groups: [], error: null, usageRecordId: null };\n runs.push(run);\n return structuredClone(run);\n },\n\n async finishMemoryOptimizationRun(ownerId, runId, outcome) {\n const run = ownerRuns(ownerId).find((candidate) => candidate.id === runId);\n if (run?.status !== \"running\") return \"not_found\";\n Object.assign(run, { status: outcome.status, finishedAt: outcome.finishedAt, error: outcome.error ?? null, usageRecordId: outcome.usageRecordId ?? null });\n return \"ok\";\n },\n\n async applyMemoryMerge(ownerId, merge) {\n const { merges } = merge;\n if (merges.length === 0) throw new TypeError(\"Слияние без склеек\");\n const seen = new Set<string>();\n for (const { result, originals } of merges) {\n assertWellFormedNote(result);\n if (originals.length === 0) throw new TypeError(\"Склейка без оригиналов\");\n for (const { id } of originals) {\n if (seen.has(id)) throw new TypeError(`Запись ${id} названа в двух склейках`);\n seen.add(id);\n }\n }\n const run = ownerRuns(ownerId).find((candidate) => candidate.id === merge.runId);\n if (run?.status !== \"running\") return \"run_not_active\";\n const notes = ownerNotes(ownerId);\n for (const { originals } of merges) {\n for (const { id, version } of originals) {\n const original = notes.get(id);\n if (!original || original.mergedInto || original.version !== version) return \"stale_version\";\n }\n }\n const now = new Date().toISOString();\n const groups: MemoryMergeGroup[] = merges.map(({ result, originals }) => {\n const created: StoredNote = { id: crypto.randomUUID(), categories: [...result.categories], content: result.content, version: 1, createdAt: now, updatedAt: now };\n notes.set(created.id, created);\n for (const { id } of originals) notes.get(id)!.mergedInto = created.id;\n return { resultId: created.id, originalIds: originals.map(({ id }) => id), resultVersion: 1 };\n });\n Object.assign(run, { status: \"applied\", finishedAt: merge.finishedAt, groups, usageRecordId: merge.usageRecordId ?? null });\n return structuredClone(run);\n },\n\n async revertMemoryMerge(ownerId, runId, guard) {\n const run = ownerRuns(ownerId).find((candidate) => candidate.id === runId);\n if (run?.status !== \"applied\") return \"not_found\";\n const notes = ownerNotes(ownerId);\n const intact = run.groups.every(\n ({ resultId, originalIds, resultVersion }) =>\n notes.get(resultId)?.version === resultVersion && !notes.get(resultId)?.mergedInto && originalIds.every((id) => notes.get(id)?.mergedInto === resultId),\n );\n if (!intact) return \"conflict\";\n if (guard) {\n const resultIds = new Set(run.groups.map(({ resultId }) => resultId));\n const restoredIds = new Set(run.groups.flatMap(({ originalIds }) => originalIds));\n const inPlay = [...notes.values()];\n const remaining = newestFirst(inPlay.filter((note) => !note.mergedInto && !resultIds.has(note.id))).map(publicNote);\n const restored = newestFirst(inPlay.filter((note) => restoredIds.has(note.id))).map(publicNote);\n const error = guard(structuredClone(remaining), structuredClone(restored));\n if (error) return revertRefusal(error);\n }\n for (const { resultId, originalIds } of run.groups) {\n notes.delete(resultId);\n for (const id of originalIds) delete notes.get(id)!.mergedInto;\n }\n run.status = \"reverted\";\n return \"ok\";\n },\n\n async listMemoryOptimizationRuns(ownerId, filter) {\n const runs = ownerRuns(ownerId)\n .map((run, index) => ({ run, index }))\n .filter(({ run }) => !filter?.status || filter.status.includes(run.status))\n .sort((left, right) => right.run.startedAt.localeCompare(left.run.startedAt) || right.index - left.index)\n .map(({ run }) => run);\n return structuredClone(filter?.limit === undefined ? runs : runs.slice(0, filter.limit));\n },\n\n async listMergedMemoryNotes(ownerId) {\n const hidden = [...ownerNotes(ownerId).values()].filter((note): note is StoredNote & { mergedInto: string } => Boolean(note.mergedInto));\n return structuredClone(newestFirst(hidden) as MergedMemoryNote[]);\n },\n };\n}\n\n/** Подходит ли чат под фильтр `listChats` по ключу объекта. */\nfunction matchesSubjects(chat: Chat, subjects: ChatSubjectFilter = \"exclude\"): boolean {\n if (subjects === \"include\") return true;\n return subjects === \"only\" ? chat.subjectKey !== undefined : chat.subjectKey === undefined;\n}\n\n/** `data` и `subjectData` — один объект; данных нет — полей нет. */\nfunction withChatData<T extends Pick<Chat, \"data\" | \"subjectData\">>(chat: T): T {\n const { data: _data, subjectData: _subjectData, ...rest } = chat;\n return { ...rest, ...chatDataFields(chatDataOfInput(chat)) } as T;\n}\n\n/** Как база: поле без значения не хранится. */\nfunction withoutUndefined<T extends object>(value: T): T {\n return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined)) as T;\n}\n"],"mappings":"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","names":["run"]}
@@ -13,7 +13,7 @@ import {
13
13
  extendCatalog,
14
14
  newStoredMessage,
15
15
  standardCatalog
16
- } from "./chunk-BS2HDKZB.js";
16
+ } from "./chunk-KAG3QTZC.js";
17
17
  import "./chunk-FOAIG4DY.js";
18
18
 
19
19
  // src/storage/storage-contract.ts
@@ -1,4 +1,6 @@
1
1
  import type { ModelCatalog } from "../models/model-catalog.js";
2
+ import type { ProviderOutageKind } from "../provider-errors/provider-outages.js";
3
+ import type { ChatErrorKind } from "../conversation/types.js";
2
4
  import type { AttemptResult } from "./turn-attempt.js";
3
5
  /**
4
6
  * Запасные провайдеры той же модели (FR-030): `false` — выключены; `byModel` — свой порядок для модели или `false` для
@@ -68,7 +70,10 @@ export declare function fallbackProviders(input: {
68
70
  setting?: ProviderFallbackSetting;
69
71
  }): string[];
70
72
  /**
71
- * Переход к запасному — только при перегрузке или лимите, до первого куска ответа и один раз на ход (FR-031, FR-032).
73
+ * Кончились деньги или не принят ключ — провайдер не ответит ни на какой запрос: сразу следующий из списка, в любом
74
+ * месте хода и сколько угодно раз (список только убывает). Такой переход не тратит единственный переход по перегрузке.
75
+ * Переход к запасному при перегрузке, лимите или отказе по доступу (403: регион, модель) — до первого куска ответа и
76
+ * один раз на ход (FR-031, FR-032).
72
77
  * Переполнение — сжатие: сначала истории чата, затем первых шагов ответа, каждое не больше раза на ход (FR-012).
73
78
  * После первого куска та же перегрузка или лимит — автопродолжение у того же провайдера, до двух на ход и не при
74
79
  * запросе подтверждения (FR-037, FR-042, FR-044). Запасной после первого куска не выбирается никогда.
@@ -102,5 +107,11 @@ export declare function decideNextAttempt(result: Extract<AttemptResult, {
102
107
  nextProviderId?: string | undefined;
103
108
  };
104
109
  }): NextAttempt;
110
+ /**
111
+ * Вид ошибки для человека, когда не ответил никто. Был отказ по деньгам или ключу (`firstOutage` — первый в ходе), а
112
+ * последний провайдер лишь занят или запрос велик — его вид: «попробуйте позже» или «короче» поможет. Иначе — исходная
113
+ * причина у основного: её и чинить владельцу.
114
+ */
115
+ export declare function errorKindForPerson(lastKind: ChatErrorKind, firstOutage: ProviderOutageKind | undefined): ChatErrorKind;
105
116
  /** Проверка настройки при создании службы: неверная форма, неизвестная модель или чужой провайдер — `TypeError`. */
106
117
  export declare function checkProviderFallback(setting: ProviderFallbackSetting | undefined, catalog: ModelCatalog): void;
@@ -5,6 +5,7 @@ import type { AgentChatMessage, ChatErrorKind, FinalAnswerStatus, TokenUsage } f
5
5
  import type { AgentHelper } from "../helpers/helper-types.js";
6
6
  import type { DataChange, ScreenPlace } from "../page-context/page-context.js";
7
7
  import type { ModelCatalog, ModelProfile } from "../models/model-catalog.js";
8
+ import type { ProviderOutages } from "../provider-errors/provider-outages.js";
8
9
  import type { LongTermMemory } from "../memory/long-term-memory.js";
9
10
  import type { ModelSilenceLimits } from "../provider-errors/model-silence.js";
10
11
  import type { ChatStorage } from "../storage/chat-storage.js";
@@ -279,6 +280,11 @@ export interface AgentTurnInput {
279
280
  toolResultLimit?: ResolvedToolResultLimit;
280
281
  /** Запасные провайдеры той же модели, уже проверенные. Нет — значение по умолчанию из `next-attempt`. */
281
282
  providerFallback?: ProviderFallbackSetting;
283
+ /**
284
+ * Память службы об отказах провайдеров по деньгам и ключу: основной на паузе — ход сразу у запасного. Нет — своя на
285
+ * ход: громкая строка в журнал есть, паузы между ходами нет.
286
+ */
287
+ providerOutages?: ProviderOutages;
282
288
  /** Сроки ответа серверных инструментов по имени, уже проверенные. Нет — 120 с всем. */
283
289
  toolTimeout?: ResolvedToolTimeout;
284
290
  /** Серверные инструменты, которые идут по одному (FR-049), уже проверенные. Нет — все вместе. */
@@ -385,6 +385,12 @@ export interface ChatServiceOptions {
385
385
  lockedToolOutputs?: readonly string[];
386
386
  /** События чата вне потока ответа. Нет — служба создаёт свои (`createChatServer` создаёт сам). */
387
387
  chatEvents?: ChatEvents;
388
+ /**
389
+ * Несколько служб чата на одном хранилище (своя инструкция на версию продукта и т. п.): какой службе отдать брошенную
390
+ * пачку (`answerAfterRestart`), найденную обходом этой службы (при старте, регулярно, `tickBursts`). Нет — отвечает
391
+ * сама эта служба. Задайте один и тот же `serviceFor` всем службам на этом хранилище: брошенные проверяет любая.
392
+ */
393
+ abandonedBursts?: AbandonedBurstsOptions;
388
394
  /** Фоновые задачи (пакет `@ykurilov/agent-chat-background`). Нет — запрос к модели побайтно прежний. */
389
395
  background?: BackgroundTasks;
390
396
  /**
@@ -577,6 +583,19 @@ export declare class WidgetNotPendingError extends Error {
577
583
  export declare class InvalidWidgetAnswerError extends Error {
578
584
  constructor(toolCallId: string);
579
585
  }
586
+ /** Кому отдать брошенную пачку (`ChatServiceOptions.abandonedBursts`). */
587
+ export interface AbandonedBurstsOptions {
588
+ /**
589
+ * Служба чата этой пачки: эта же, другая служба приложения или новая, созданная сейчас (`createChatService` или
590
+ * `createChatServer`). `null` — нужной службы нет: пачка никому не отдаётся и ждёт следующего обхода (через 30 с).
591
+ * Бросок — то же, со строкой в журнал. Зовётся на каждую взятую пачку, до хода; `chat` — чат из хранилища.
592
+ */
593
+ serviceFor(burst: {
594
+ ownerId: string;
595
+ chatId: string;
596
+ chat: Chat;
597
+ }): ChatService | null | undefined | Promise<ChatService | null | undefined>;
598
+ }
580
599
  /** Ответ человека на запрос подтверждения. */
581
600
  export type ApprovalAnswer = "allow" | "deny" | "always";
582
601
  /** Ответ человека на ждущий виджет. */
@@ -1,6 +1,7 @@
1
1
  import type { ModelMessage } from "ai";
2
2
  import type { UsageSource } from "../conversation/types.js";
3
3
  import type { ModelCatalog } from "../models/model-catalog.js";
4
+ import type { ProviderOutages } from "../provider-errors/provider-outages.js";
4
5
  import type { ModelSilenceLimits } from "../provider-errors/model-silence.js";
5
6
  import type { ChatStorage } from "../storage/chat-storage.js";
6
7
  import type { ProviderFallbackSetting } from "../agent-run/next-attempt.js";
@@ -28,6 +29,8 @@ export interface HelperRunContext {
28
29
  /** Очередь инструментов «по одному» чата: помощник делит её с основным агентом. */
29
30
  toolQueue?: ToolQueue;
30
31
  providerFallback?: ProviderFallbackSetting;
32
+ /** Память службы об отказах провайдеров по деньгам и ключу. Нет — своя на запуск. */
33
+ providerOutages?: ProviderOutages;
31
34
  modelSilence?: ModelSilenceLimits;
32
35
  turnObserver?: TurnObserver;
33
36
  /** Проверка лимита расходов перед каждым шагом; бросает `SpendingLimitReachedError`. Нет — без лимита. */
@@ -39,6 +39,8 @@ export type { OpenRouterMaxPrice, OpenRouterProviderOptions } from "./models/pro
39
39
  export { aliveAtByLocalClock, assertKnownFormatVersion, ChatAlreadyExistsError, DuplicateChatSubjectError, DuplicateMessageError, MessageAlreadyFinishedError, MessageNotAwaitingError, RELEASED_ANSWER_ALIVE_AT, sameMessageParts, silenceBoundaryByLocalClock, UnsupportedFormatVersionError, } from "./storage/chat-storage.js";
40
40
  export { assertChatData, assertChatDataFilter, CHAT_DATA_MAX_BYTES, ChatDataTooLargeError, chatDataFields, chatDataMatches, chatDataOfInput, mergeChatData } from "./storage/chat-data.js";
41
41
  export type { ChatDataFilter, ChatDataFilterValue } from "./storage/chat-data.js";
42
+ export { migratePostgresSchema } from "./storage/postgres-migrations.js";
43
+ export type { PostgresMigrationConnection, PostgresMigrationOptions } from "./storage/postgres-migrations.js";
42
44
  export type { AwaitingMessagePatch, ChatListOptions, ChatStorage, ChatSubjectFilter, FinishedMessagePatch, ImportedChat, MessageLink, MessagePartsLink, NewChatInput, SpendingLimitStateRecord, StoredPendingBurst, StreamingMessagePatch, } from "./storage/chat-storage.js";
43
45
  export { SpendingLimitReachedError } from "./spending/spending-limits.js";
44
46
  export { APP_EVENT_DATA_MAX_BYTES, APP_EVENT_TYPE_MAX_LENGTH, isTurnRefusalCode, normalizeTurnRefusal, TURN_REFUSAL_MESSAGE_MAX_LENGTH, TurnRefusedError, validateAppEvent, } from "./turn-hooks/turn-hooks.js";
@@ -69,7 +71,7 @@ export { createInMemoryChatStorage } from "./storage/in-memory-storage.js";
69
71
  export { ChatBusyError, createRunningAnswerRegistry } from "./running-answers/running-answer-registry.js";
70
72
  export type { RunningAnswerRegistry } from "./running-answers/running-answer-registry.js";
71
73
  export { ChatNotFoundError, createChatService, InvalidMessageError, InvalidModelChoiceError, NothingToRetryError, } from "./chat-service.js";
72
- export type { ActionOptions, AgentSettings, ChatBranchForks, ChatCompactions, ChatHistory, ChatListItem, ChatService, ChatInstructions, ChatInstructionsTurn, ChatState, CompactionMarker, CompactionOutcome, CompactionSettings, FlushOptions, CheckReplyInput, CheckReplyResult, IncomingUserMessage, ModelChoice, ModelChosenHandler, ModelMenu, RecoverInterruptedAnswersOptions, SendMessageOptions, SendBurstOptions, BurstMessageResult, SteerResult, } from "./chat-service.js";
74
+ export type { AbandonedBurstsOptions, ActionOptions, AgentSettings, ChatBranchForks, ChatCompactions, ChatHistory, ChatListItem, ChatService, ChatInstructions, ChatInstructionsTurn, ChatState, CompactionMarker, CompactionOutcome, CompactionSettings, FlushOptions, CheckReplyInput, CheckReplyResult, IncomingUserMessage, ModelChoice, ModelChosenHandler, ModelMenu, RecoverInterruptedAnswersOptions, SendMessageOptions, SendBurstOptions, BurstMessageResult, SteerResult, } from "./chat-service.js";
73
75
  export { STEER_PART_TYPE, isCorrection } from "./steering/turn-steering.js";
74
76
  export { BURST_HOLD_MS, BURST_PART_TYPE, BURST_SWEEP_LIMIT, DEFAULT_BURST_QUIET_MS, isBurstMessage } from "./bursts/message-bursts.js";
75
77
  export { GREETING_LEAD } from "./agent-run/greeting-lead.js";
@@ -6,3 +6,8 @@ import type { ProviderDescription } from "../models/model-catalog.js";
6
6
  * Порядок общий с тихими повторами: «не повторять» → переполнение → «повторять».
7
7
  */
8
8
  export declare function classifyProviderError(error: unknown, provider?: Pick<ProviderDescription, "errorHints">): ChatErrorKind;
9
+ /**
10
+ * Провайдер отказал по доступу (403 без слов о ключе и деньгах): регион, доступ к модели. Другой провайдер может
11
+ * ответить. Модерация — не такой отказ: дело в содержимом, и другому провайдеру оно не передаётся.
12
+ */
13
+ export declare function isAccessRefusal(error: unknown): boolean;
@@ -1,6 +1,6 @@
1
1
  /** Запрос не влез в окно модели: признаки в тексте или код 413. Признаки лимита запросов — не переполнение. */
2
2
  export declare function isContextOverflow(text: string, statusCode?: number): boolean;
3
- /** Квота, бюджет или оплата исчерпаны: повторять не нужно. */
4
- export declare function isNotRetryable(text: string): boolean;
3
+ /** Квота, бюджет или оплата исчерпаны: повторять не нужно. При коде 429 — только явные признаки пустого счёта. */
4
+ export declare function isNotRetryable(text: string, statusCode?: number): boolean;
5
5
  /** Текст похож на временный сбой провайдера или сети. */
6
6
  export declare function looksTransient(text: string): boolean;
@@ -0,0 +1,41 @@
1
+ import type { ChatErrorKind } from "../conversation/types.js";
2
+ /** Отказ, после которого провайдер не ответит ни на какой запрос: кончились деньги или не принят ключ. */
3
+ export type ProviderOutageKind = Extract<ChatErrorKind, "insufficient_funds" | "invalid_key">;
4
+ export declare function isProviderOutage(kind: ChatErrorKind): kind is ProviderOutageKind;
5
+ /** Сколько провайдер на паузе после отказа: новые ходы это время сразу идут к запасному. */
6
+ export declare const PROVIDER_OUTAGE_PAUSE_MS: number;
7
+ /** Громкая строка о провайдере — не чаще. */
8
+ export declare const PROVIDER_OUTAGE_ALERT_EVERY_MS: number;
9
+ /** Пауза кончилась: один ход пробует провайдера, остальные это время идут мимо. Проба без итога — через этот срок следующая. */
10
+ export declare const PROVIDER_OUTAGE_PROBE_MS = 60000;
11
+ /**
12
+ * Память службы об отказах провайдеров по деньгам и ключу. Пустой счёт за минуты не пополнится: ходы на паузе не
13
+ * стучат в него каждый раз, а после паузы его пробует один ход, не все разом. Кэш при этом не теряется — ответить
14
+ * основной всё равно не может, а запасной на паузе получает все запросы подряд, и его кэш тёплый.
15
+ */
16
+ export interface ProviderOutages {
17
+ /**
18
+ * Провайдер отказал: пауза на `PROVIDER_OUTAGE_PAUSE_MS` и, не чаще раза в `PROVIDER_OUTAGE_ALERT_EVERY_MS`, строка
19
+ * `[ВНИМАНИЕ провайдер]` в журнал. `fallbackProviderId` — кто отвечает вместо; нет — ходы падают, а
20
+ * `fallbacksPaused` говорит, что запасные есть, но тоже на паузе.
21
+ */
22
+ report(input: {
23
+ providerId: string;
24
+ kind: ProviderOutageKind;
25
+ fallbackProviderId?: string;
26
+ fallbacksPaused?: boolean;
27
+ }): void;
28
+ /** Вид отказа, пока провайдер на паузе или его пробует другой ход; иначе `undefined`. Ничего не меняет. */
29
+ pausedKind(providerId: string): ProviderOutageKind | undefined;
30
+ /**
31
+ * Ход хочет к провайдеру: `undefined` — можно, вид отказа — нельзя. Пауза кончилась — первый получает пробу
32
+ * на `PROVIDER_OUTAGE_PROBE_MS`, остальные до её итога — вид отказа.
33
+ */
34
+ admit(providerId: string): ProviderOutageKind | undefined;
35
+ /** Провайдер ответил: пауза снята. Счёт громких строк остаётся — «мигающий» провайдер не шумит. */
36
+ recovered(providerId: string): void;
37
+ }
38
+ export declare function createProviderOutages(options: {
39
+ now: () => number;
40
+ logError: (message: string) => void;
41
+ }): ProviderOutages;