@ykurilov/agent-chat-core 0.68.0 → 0.69.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +12 -0
- package/dist/{chunk-WMP5AKDU.js → chunk-7G63YBL5.js} +512 -22
- package/dist/chunk-7G63YBL5.js.map +1 -0
- package/dist/index.js +15 -1
- package/dist/index.js.map +1 -1
- package/dist/storage-contract.js +1 -1
- package/dist/types/access/client-visibility.d.ts +8 -1
- package/dist/types/agent-run/answer-saver.d.ts +3 -1
- package/dist/types/agent-run/run-agent-turn.d.ts +6 -0
- package/dist/types/chat-service.d.ts +7 -0
- package/dist/types/conversation/types.d.ts +7 -1
- package/dist/types/index.d.ts +4 -0
- package/dist/types/thought-titles/thought-title-model.d.ts +27 -0
- package/dist/types/thought-titles/thought-title-parts.d.ts +20 -0
- package/dist/types/thought-titles/thought-title-text.d.ts +26 -0
- package/dist/types/thought-titles/thought-titles-setting.d.ts +83 -0
- package/dist/types/thought-titles/turn-thought-titles.d.ts +56 -0
- package/package.json +1 -1
- package/dist/chunk-WMP5AKDU.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -77,6 +77,9 @@ import {
|
|
|
77
77
|
SUBJECT_KEY_MAX_LENGTH,
|
|
78
78
|
SUGGESTIONS_SHOWN,
|
|
79
79
|
SpendingLimitReachedError,
|
|
80
|
+
THOUGHT_TITLE_INSTRUCTIONS,
|
|
81
|
+
THOUGHT_TITLE_METADATA_KEY,
|
|
82
|
+
THOUGHT_TITLE_USAGE_SOURCE,
|
|
80
83
|
TOOL_TIMEOUT_HINT,
|
|
81
84
|
TooManyTurnsError,
|
|
82
85
|
UnsupportedFormatVersionError,
|
|
@@ -103,6 +106,7 @@ import {
|
|
|
103
106
|
chatDataOfInput,
|
|
104
107
|
chatHasExternalContent,
|
|
105
108
|
checkServiceBlockFormats,
|
|
109
|
+
cleanThoughtTitle,
|
|
106
110
|
costOfUsage,
|
|
107
111
|
createAppStateWriter,
|
|
108
112
|
createChatEvents,
|
|
@@ -146,6 +150,7 @@ import {
|
|
|
146
150
|
normalizeChatTitle,
|
|
147
151
|
noticePartForModel,
|
|
148
152
|
noticeText,
|
|
153
|
+
providerHeadingOf,
|
|
149
154
|
publicAppState,
|
|
150
155
|
publicAppStateParts,
|
|
151
156
|
repairToolArguments,
|
|
@@ -163,6 +168,8 @@ import {
|
|
|
163
168
|
splitVoiceTags,
|
|
164
169
|
standardCatalog,
|
|
165
170
|
stripVoiceTags,
|
|
171
|
+
thoughtTitlePrompt,
|
|
172
|
+
thoughtTitlesOfParts,
|
|
166
173
|
toolImageFallbackText,
|
|
167
174
|
touchedAt,
|
|
168
175
|
turnOriginOf,
|
|
@@ -175,7 +182,7 @@ import {
|
|
|
175
182
|
withoutFileParts,
|
|
176
183
|
withoutGlueFragments,
|
|
177
184
|
withoutResetSteps
|
|
178
|
-
} from "./chunk-
|
|
185
|
+
} from "./chunk-7G63YBL5.js";
|
|
179
186
|
import {
|
|
180
187
|
APP_EVENT_DATA_MAX_BYTES,
|
|
181
188
|
APP_EVENT_TYPE_MAX_LENGTH,
|
|
@@ -947,6 +954,9 @@ export {
|
|
|
947
954
|
SUBJECT_KEY_MAX_LENGTH,
|
|
948
955
|
SUGGESTIONS_SHOWN,
|
|
949
956
|
SpendingLimitReachedError,
|
|
957
|
+
THOUGHT_TITLE_INSTRUCTIONS,
|
|
958
|
+
THOUGHT_TITLE_METADATA_KEY,
|
|
959
|
+
THOUGHT_TITLE_USAGE_SOURCE,
|
|
950
960
|
TOOL_TIMEOUT_HINT,
|
|
951
961
|
TURN_REFUSAL_MESSAGE_MAX_LENGTH,
|
|
952
962
|
TooManyTurnsError,
|
|
@@ -977,6 +987,7 @@ export {
|
|
|
977
987
|
chatDataOfInput,
|
|
978
988
|
chatHasExternalContent,
|
|
979
989
|
checkServiceBlockFormats,
|
|
990
|
+
cleanThoughtTitle,
|
|
980
991
|
costOfUsage,
|
|
981
992
|
createAppStateWriter,
|
|
982
993
|
createChatEvents,
|
|
@@ -1029,6 +1040,7 @@ export {
|
|
|
1029
1040
|
noticeText,
|
|
1030
1041
|
originRefusalHint,
|
|
1031
1042
|
pauseAfterFailure,
|
|
1043
|
+
providerHeadingOf,
|
|
1032
1044
|
publicAppState,
|
|
1033
1045
|
publicAppStateParts,
|
|
1034
1046
|
repairToolArguments,
|
|
@@ -1047,6 +1059,8 @@ export {
|
|
|
1047
1059
|
splitVoiceTags,
|
|
1048
1060
|
standardCatalog,
|
|
1049
1061
|
stripVoiceTags,
|
|
1062
|
+
thoughtTitlePrompt,
|
|
1063
|
+
thoughtTitlesOfParts,
|
|
1050
1064
|
toolImageFallbackText,
|
|
1051
1065
|
touchedAt,
|
|
1052
1066
|
turnOriginOf,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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"]}
|
|
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"]}
|
package/dist/storage-contract.js
CHANGED
|
@@ -38,4 +38,11 @@ export declare const SHOW_ON_SCREEN_KEY = "showOnScreen";
|
|
|
38
38
|
* Видимость для прав зрителя. При `toolDetails: false` видны вызовы с пометкой `showOnScreen`, инструменты SDK, которым
|
|
39
39
|
* нужен экран (`sdkScreenTool`: виджеты, браузерные), и вызовы, спросившие подтверждения.
|
|
40
40
|
*/
|
|
41
|
-
export declare function clientVisibilityOf(capabilities: Pick<ChatCapabilities, "reasoning" | "toolDetails" | "modelDetails" | "errorDetails">, sdkScreenTool: (toolName: string) => boolean
|
|
41
|
+
export declare function clientVisibilityOf(capabilities: Pick<ChatCapabilities, "reasoning" | "toolDetails" | "modelDetails" | "errorDetails">, sdkScreenTool: (toolName: string) => boolean,
|
|
42
|
+
/**
|
|
43
|
+
* Служба делает заголовки раздумий (`thoughtTitles`): без права `reasoning` вместо раздумья уходит пустое место (часть без
|
|
44
|
+
* текста и метаданных — по нему экран ставит заголовок на своё место), а заголовки — в метаданных. Текст — никогда.
|
|
45
|
+
*/
|
|
46
|
+
{ thoughtTitles }?: {
|
|
47
|
+
thoughtTitles?: boolean;
|
|
48
|
+
}): ClientVisibility;
|
|
@@ -18,7 +18,9 @@ usage: () => TokenUsage | null,
|
|
|
18
18
|
/** Запись узнала, что ответ уже закрыт другой копией сервера: записей больше нет, ход должен остановиться. */
|
|
19
19
|
onTakenOver: () => void,
|
|
20
20
|
/** Продолжение: сообщение, к которому дописываются куски. */
|
|
21
|
-
existing?: AgentChatMessage
|
|
21
|
+
existing?: AgentChatMessage,
|
|
22
|
+
/** Правка частей перед каждой записью (заголовки раздумий); сборка из кусков её не видит. */
|
|
23
|
+
decorateParts?: (parts: AgentChatMessage["parts"]) => AgentChatMessage["parts"]): {
|
|
22
24
|
observe(chunk: UIMessageChunk): void;
|
|
23
25
|
/** Ждёт, пока начатый вызов попадёт в хранилище; записи больше не будет (ход кончен, ответ закрыт) — сразу. */
|
|
24
26
|
whenCallSaved(toolCallId: string): Promise<void>;
|
|
@@ -13,6 +13,7 @@ import type { CacheMissWatch } from "../usage/cache-miss-watch.js";
|
|
|
13
13
|
import type { PendingBrowserCalls } from "./browser-tools.js";
|
|
14
14
|
import type { TurnSpendingRules } from "../spending/spending-limits.js";
|
|
15
15
|
import type { ModeNotes } from "./mode-notes.js";
|
|
16
|
+
import type { TurnThoughtTitlesSource } from "../thought-titles/turn-thought-titles.js";
|
|
16
17
|
import type { ServiceBlockFormat } from "./forged-blocks.js";
|
|
17
18
|
import type { TurnReplyChecks } from "./reply-checks.js";
|
|
18
19
|
import type { ProviderFallbackSetting } from "./next-attempt.js";
|
|
@@ -175,6 +176,11 @@ export interface AgentTurnInput {
|
|
|
175
176
|
reply?: string;
|
|
176
177
|
/** Метка расхода хода в журнале. Нет — `answer`; агент пишет первым — `first_message` (`deliverNotices`). */
|
|
177
178
|
usageSource?: UsageSource;
|
|
179
|
+
/**
|
|
180
|
+
* Заголовки раздумий (`createChatService({ thoughtTitles })`): язык и пределы хода, пересказ уже привязан к владельцу,
|
|
181
|
+
* чату и ответу. Нет — заголовков нет, поток и запрос побайтно прежние.
|
|
182
|
+
*/
|
|
183
|
+
thoughtTitles?: TurnThoughtTitlesSource;
|
|
178
184
|
/**
|
|
179
185
|
* Проверки законченного ответа перед записью (`createChatService({ replyChecks })`): правят последнюю текстовую часть.
|
|
180
186
|
* Готовый ответ (`reply`) не проверяется. Веб-чат печатает как обычно и заменяет шаг исправленным (`replace`) или
|
|
@@ -11,6 +11,7 @@ import type { StepContext } from "./agent-run/step-context.js";
|
|
|
11
11
|
import type { ContextBlocksSource } from "./frozen-context/context-blocks.js";
|
|
12
12
|
import type { ChatEvent, ChatEvents } from "./chat-events/chat-events.js";
|
|
13
13
|
import type { BackgroundTasks } from "./background/background-tasks.js";
|
|
14
|
+
import type { ThoughtTitlesSetting } from "./thought-titles/thought-titles-setting.js";
|
|
14
15
|
import type { AppendStaffMessageInput, DeliverNoticesInput, DeliverNoticesResult } from "./notices/agent-notice.js";
|
|
15
16
|
import type { TurnObserver } from "./agent-run/turn-observer.js";
|
|
16
17
|
import type { ProviderFallbackSetting } from "./agent-run/next-attempt.js";
|
|
@@ -377,6 +378,12 @@ export interface ChatServiceOptions {
|
|
|
377
378
|
* `"hold"` — не печатать до проверки; мессенджеры получают только проверенный текст. Нет — ход побайтно прежний.
|
|
378
379
|
*/
|
|
379
380
|
replyChecks?: ReplyChecksSetting;
|
|
381
|
+
/**
|
|
382
|
+
* Заголовки раздумий вместо «Думал 3 сек»: заголовки провайдера (сводка рассуждений GPT через OpenRouter) или пересказ
|
|
383
|
+
* дешёвой моделью `model`. Делаются в фоне, ход не ждут; лежат в частях рассуждения, в запрос основной модели не идут.
|
|
384
|
+
* Расход — видом `thought_title`. Нет — ход и запрос побайтно прежние. См. `ThoughtTitlesSetting`.
|
|
385
|
+
*/
|
|
386
|
+
thoughtTitles?: ThoughtTitlesSetting;
|
|
380
387
|
/**
|
|
381
388
|
* Наблюдатель хода (песочница): каждое обращение к модели (ответ, сжатие, память, повторы) и каждый вызов серверного
|
|
382
389
|
* инструмента — событием. Вызывается синхронно и не ждётся; бросок уходит в `logError`. Нет — запрос побайтно прежний.
|
|
@@ -113,6 +113,11 @@ export interface ChatMessageMetadata {
|
|
|
113
113
|
providerId?: string;
|
|
114
114
|
usage?: TokenUsage;
|
|
115
115
|
reasoningDurationsMs?: Record<string, number>;
|
|
116
|
+
/**
|
|
117
|
+
* Заголовки раздумий по порядковому номеру части рассуждения («0», «1»…), как `reasoningDurationsMs`
|
|
118
|
+
* (`createChatService({ thoughtTitles })`). Хранятся в самих частях рассуждения; нет — заголовков нет.
|
|
119
|
+
*/
|
|
120
|
+
thoughtTitles?: Record<string, string>;
|
|
116
121
|
status?: AnswerStatus;
|
|
117
122
|
error?: ChatError;
|
|
118
123
|
/** Есть, если озвучку ответа оборвало перебивание. */
|
|
@@ -203,8 +208,9 @@ export interface StoredBranchSelection {
|
|
|
203
208
|
* `modelId` — модель распознавания. `voice_polish` — чистка надиктованного текста моделью.
|
|
204
209
|
* `mcp_tool` — вызов инструмента сервера MCP (`@ykurilov/agent-chat-mcp`), если сервер сообщил стоимость: `modelId` —
|
|
205
210
|
* имя инструмента `<префикс>__<имя>`, `providerId` — `mcp`, токенов нет.
|
|
211
|
+
* `thought_title` — заголовок раздумья моделью заголовков (`createChatService({ thoughtTitles })`), с id ответа.
|
|
206
212
|
*/
|
|
207
|
-
export declare const BUILT_IN_USAGE_SOURCES: readonly ["answer", "answer_correction", "compaction", "memory", "speech", "sounds", "web_tools", "images", "helper", "background", "judge", "first_message", "voice_input", "voice_polish", "mcp_tool"];
|
|
213
|
+
export declare const BUILT_IN_USAGE_SOURCES: readonly ["answer", "answer_correction", "compaction", "memory", "speech", "sounds", "web_tools", "images", "helper", "background", "judge", "first_message", "voice_input", "voice_polish", "mcp_tool", "thought_title"];
|
|
208
214
|
/** Штатные виды расхода SDK. */
|
|
209
215
|
export type BuiltInUsageSource = (typeof BUILT_IN_USAGE_SOURCES)[number];
|
|
210
216
|
/**
|
package/dist/types/index.d.ts
CHANGED
|
@@ -56,6 +56,10 @@ export type { StateChangedHandler, StateChangedInput, StateChangeReason } from "
|
|
|
56
56
|
export type { AbandonedAnswers, AfterTurnHandler, AppEvent, BeforeTurnDecision, BeforeTurnHandler, StaffActor, TurnAction, TurnNotStartedHandler, TurnNotStartedReason, TurnHold, TurnRefusal, TurnReply, TurnResult, TurnStopReason, } from "./turn-hooks/turn-hooks.js";
|
|
57
57
|
export { DEFAULT_REPLY_CHECKS_WEB, resolveReplyChecks } from "./agent-run/reply-check-types.js";
|
|
58
58
|
export type { KnownTexts, KnownTextsContext, ReplyCheck, ReplyCheckInput, ReplyCheckResult, ReplyChecksSetting, ReplyChecksWebMode } from "./agent-run/reply-check-types.js";
|
|
59
|
+
export type { ThoughtTitlesSetting, ThoughtTitleLanguageInput } from "./thought-titles/thought-titles-setting.js";
|
|
60
|
+
export { THOUGHT_TITLE_USAGE_SOURCE, THOUGHT_TITLE_INSTRUCTIONS, thoughtTitlePrompt } from "./thought-titles/thought-title-model.js";
|
|
61
|
+
export { THOUGHT_TITLE_METADATA_KEY, thoughtTitlesOfParts } from "./thought-titles/thought-title-parts.js";
|
|
62
|
+
export { cleanThoughtTitle, providerHeadingOf } from "./thought-titles/thought-title-text.js";
|
|
59
63
|
export { accessRefusedResponse, allowedUser } from "./access/resolved-user.js";
|
|
60
64
|
export type { AccessRefusal, ResolvedUser } from "./access/resolved-user.js";
|
|
61
65
|
export { ActionForbiddenError, CHAT_CAPABILITIES, resolveChatCapabilities } from "./access/chat-capabilities.js";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ModelChoice } from "../models/model-choice.js";
|
|
2
|
+
import type { OneOffCallsContext } from "../usage/ask-model.js";
|
|
3
|
+
/** Вид расхода модели заголовков в журнале. */
|
|
4
|
+
export declare const THOUGHT_TITLE_USAGE_SOURCE = "thought_title";
|
|
5
|
+
/**
|
|
6
|
+
* Инструкция постоянная: меняется только хвост сообщения (текст и язык), начало запроса у провайдера попадает в кэш.
|
|
7
|
+
* Текст раздумья — данные: модель не выполняет из него просьб.
|
|
8
|
+
*/
|
|
9
|
+
export declare const THOUGHT_TITLE_INSTRUCTIONS: string;
|
|
10
|
+
/** Название языка для инструкции: «ru» → «Russian»; незнакомый код — как есть. */
|
|
11
|
+
export declare function languageName(language: string): string;
|
|
12
|
+
/** Сообщение модели заголовков: раздумье в теге (без чужих тегов) и язык заголовка. */
|
|
13
|
+
export declare function thoughtTitlePrompt(text: string, language: string): string;
|
|
14
|
+
export interface ThoughtTitleCall {
|
|
15
|
+
ownerId: string;
|
|
16
|
+
chatId: string;
|
|
17
|
+
messageId: string;
|
|
18
|
+
text: string;
|
|
19
|
+
language: string;
|
|
20
|
+
/** «Стоп» хода: отменяет запрос. */
|
|
21
|
+
signal?: AbortSignal;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Пересказ раздумья моделью заголовков: один запрос без инструментов и истории, срок `timeoutMs`. Лимит расходов —
|
|
25
|
+
* до вызова (исчерпан — `undefined`, без журнала), расход — видом `thought_title` у владельца чата с id ответа.
|
|
26
|
+
*/
|
|
27
|
+
export declare function createThoughtTitleModel(context: OneOffCallsContext, model: ModelChoice, timeoutMs: number): (call: ThoughtTitleCall) => Promise<string | undefined>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AgentChatMessage } from "../conversation/types.js";
|
|
2
|
+
import type { ThoughtTitles } from "./turn-thought-titles.js";
|
|
3
|
+
type MessagePart = AgentChatMessage["parts"][number];
|
|
4
|
+
/**
|
|
5
|
+
* Ключ SDK в метаданных части рассуждения: там лежит заголовок раздумья (`thoughtTitle`). Новых полей хранилищам не
|
|
6
|
+
* нужно, а перед запросом к модели ключ снимается (`withoutThoughtTitles`): запрос побайтно тот же.
|
|
7
|
+
*/
|
|
8
|
+
export declare const THOUGHT_TITLE_METADATA_KEY = "agentChat";
|
|
9
|
+
/** Сколько раздумий сообщения уже пересказывала модель заголовков: `maxPerTurn` — на ответ целиком, с продолжениями. */
|
|
10
|
+
export declare function askedThoughtTitlesOfParts(parts: readonly MessagePart[]): number;
|
|
11
|
+
/** Заголовки из частей сообщения по порядковому номеру рассуждения. */
|
|
12
|
+
export declare function thoughtTitlesOfParts(parts: readonly MessagePart[]): ThoughtTitles;
|
|
13
|
+
/**
|
|
14
|
+
* Части с заголовками в метаданных рассуждений и отметкой «модель уже спрашивали» (`asked`); ничего не изменилось — тот
|
|
15
|
+
* же массив.
|
|
16
|
+
*/
|
|
17
|
+
export declare function withThoughtTitles<Parts extends readonly MessagePart[]>(parts: Parts, titles: ThoughtTitles, asked?: ReadonlySet<string>): Parts;
|
|
18
|
+
/** История без заголовков раздумий: ключ SDK снят с метаданных рассуждений, пустые метаданные убраны. */
|
|
19
|
+
export declare function withoutThoughtTitles(messages: readonly AgentChatMessage[]): readonly AgentChatMessage[];
|
|
20
|
+
export {};
|