@ykurilov/agent-chat-server 0.77.0 → 0.79.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/README.md +2 -0
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/types/chat-handler.d.ts +8 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/new-chat-greeting.d.ts +6 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# @ykurilov/agent-chat-server
|
|
2
2
|
|
|
3
|
+
## 0.79.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 88886de: Новые контракты приложения: HTTP `newChatGreeting` после `newChatData`, проверенное `createChat.title`, текущий вопрос `contextBlocks.userMessage` для ожидаемой асинхронной классификации, direct DeepSeek `low/high/max` и опциональные устойчивые номера фото.
|
|
8
|
+
|
|
9
|
+
`createAttachments({ imageNumbers: true })` сохраняет серверный номер вместе с принятой картинкой; `service.getChatImages(userId, chatId)` возвращает соответствие номер/attachmentId полной активной ветки. Номера не меняются при повторе файла, перезапуске, компакции и переключении ветки; скрытые ветки не отдают свои номера другим файлам. Старые файлы без меток не заполняются задним числом, без настройки запрос модели прежний. Одновременные процессы используют существующую распределённую аренду чата.
|
|
10
|
+
|
|
11
|
+
Приветствие вызывается только при HTTP-создании, не при GET/reload; повтор POST создаёт другой чат. `userMessage` равен null у служебного хода, approval/resume сохраняет исходный вопрос. Данные и название не попадают автоматически в системные инструкции. `importChat.chat.data` уже доступно в 0.77.0; тестовая модель — существующий AI SDK `ai/test`, новый тестовый пакет не нужен. Полные принятые contextBlocks после компакции передаются без обрезания, в прежнем порядке.
|
|
12
|
+
|
|
13
|
+
Как обновить: все SDK-пакеты одной версией; новые настройки включать явно. SQL-схемы не меняются. [Точные примеры и ограничения](https://github.com/ykurilov/agent-chat-sdk/blob/main/docs/app-chat-contracts.md).
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- Updated dependencies [88886de]
|
|
18
|
+
- @ykurilov/agent-chat-core@0.79.0
|
|
19
|
+
|
|
20
|
+
## 0.78.0
|
|
21
|
+
|
|
22
|
+
### Patch Changes
|
|
23
|
+
|
|
24
|
+
- @ykurilov/agent-chat-core@0.78.0
|
|
25
|
+
|
|
3
26
|
## 0.77.0
|
|
4
27
|
|
|
5
28
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -106,3 +106,5 @@ await chat.flush({ waitForAnswersMs: 20_000 }); // при штатной ост
|
|
|
106
106
|
Подключение целиком: [docs/getting-started.md](https://github.com/ykurilov/agent-chat-sdk/blob/main/docs/getting-started.md).
|
|
107
107
|
|
|
108
108
|
Журнал изменений и что делать при обновлении: [CHANGELOG.md](./CHANGELOG.md). Все пакеты SDK выходят вместе и с одной версией; обновление — `pnpm update "@ykurilov/agent-chat-*" --latest`.
|
|
109
|
+
|
|
110
|
+
Контракты интеграции приложения: [HTTP-приветствие, текущий вопрос, номера фото, метаданные и тесты](https://github.com/ykurilov/agent-chat-sdk/blob/main/docs/app-chat-contracts.md).
|
package/dist/index.js
CHANGED
|
@@ -327,6 +327,7 @@ var SERVER_ONLY_OPTIONS = {
|
|
|
327
327
|
chatList: true,
|
|
328
328
|
subjectChats: true,
|
|
329
329
|
newChatData: true,
|
|
330
|
+
newChatGreeting: true,
|
|
330
331
|
allowedOrigins: true,
|
|
331
332
|
heartbeatIntervalMs: true
|
|
332
333
|
};
|
|
@@ -478,7 +479,11 @@ function createChatServer(options) {
|
|
|
478
479
|
if (method === "GET" && options.chatList) return Response.json({ chats: await service.listChats(userId, chatListOptions(options.chatList)) });
|
|
479
480
|
if (method !== "POST") return errorResponse2(404, "not_found");
|
|
480
481
|
const data = options.newChatData ? await resolveNewChatData(options.newChatData, { user: { id: userId }, request }) : void 0;
|
|
481
|
-
const
|
|
482
|
+
const greeting = options.newChatGreeting ? await options.newChatGreeting({ userId, chatData: structuredClone(data) }) : null;
|
|
483
|
+
if (greeting !== null && (typeof greeting !== "object" || Array.isArray(greeting))) {
|
|
484
|
+
throw new TypeError("newChatGreeting \u0434\u043E\u043B\u0436\u0435\u043D \u0432\u0435\u0440\u043D\u0443\u0442\u044C \u043F\u0440\u0438\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0435 \u0438\u043B\u0438 null.");
|
|
485
|
+
}
|
|
486
|
+
const chat = await service.createChat(userId, { ...data !== void 0 && { data }, ...greeting !== null && { greeting } });
|
|
482
487
|
return Response.json({ id: chat.id }, { status: 201 });
|
|
483
488
|
}
|
|
484
489
|
if (chatId === "models" && action === void 0 && method === "GET") {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/chat-handler.ts","../src/heartbeat.ts","../src/request-body.ts","../src/memory-routes.ts","../src/subject-chat-route.ts","../src/new-chat-data.ts","../src/watch-stream.ts","../src/index.ts"],"sourcesContent":["import {\n ApprovalNotPendingError,\n AttachmentRejectedError,\n BrowserCallNotPendingError,\n ChatBusyError,\n ChatNotFoundError,\n createChatEvents,\n accessRefusedResponse,\n allowedUser,\n createChatService,\n hiddenStateKeyOf,\n createRunningAnswerRegistry,\n DuplicateMessageError,\n InvalidChatTitleError,\n InvalidMessageError,\n InvalidModelChoiceError,\n isScreenPlaceInput,\n isTabId,\n isVoiceSound,\n NothingToRetryError,\n createRequestOriginCheck,\n originRefusalHint,\n isSafeMethod,\n SpendingLimitReachedError,\n TooManyTurnsError,\n TurnRefusedError,\n ActionForbiddenError,\n ChatClosedError,\n ChatCreationRefusedError,\n turnOriginOf,\n InvalidWidgetAnswerError,\n WidgetAnswerCheckAbortedError,\n WidgetAnswerCheckTimeoutError,\n WidgetAnswerRefusedError,\n WidgetNotPendingError,\n} from \"@ykurilov/agent-chat-core\";\nimport type {\n AbandonedBurstsOptions,\n AfterTurnHandler,\n ModelChosenHandler,\n RecoverInterruptedAnswersOptions,\n ReplyChecksSetting,\n ThoughtTitlesSetting,\n StateChangedHandler,\n AgentSettings,\n BackgroundTasks,\n BeforeTurnHandler,\n BrowserCallAnswer,\n Chat,\n BeforeCreateChatHandler,\n ChatCapabilitiesHandler,\n ChatErrorKind,\n ChatService,\n ChatStorage,\n CompactionSettings,\n FlushOptions,\n IncomingUserMessage,\n MessageAttachments,\n ModelCatalog,\n ModelChoice,\n ResolvedUser,\n SpendingLimits,\n StaffActor,\n ChatInstructions,\n TurnFinishedHandler,\n TurnNotStartedHandler,\n TurnObserver,\n TurnRequestContext,\n VoiceSound,\n} from \"@ykurilov/agent-chat-core\";\nimport { assertHeartbeatInterval, createHeartbeatResponse, DEFAULT_HEARTBEAT_INTERVAL_MS } from \"./heartbeat\";\nimport { handleMemoryRequest } from \"./memory-routes\";\nimport type { ChatMemory } from \"./memory-routes\";\nimport { readJsonBody, RequestBodyTooLargeError } from \"./request-body\";\nimport { openSubjectChatRoute, turnRefusalResponse } from \"./subject-chat-route\";\nimport type { NewChatDataResolver } from \"./new-chat-data\";\nimport { resolveNewChatData } from \"./new-chat-data\";\nimport type { SubjectChatsResolver } from \"./subject-chat-route\";\nimport { createWatchStream } from \"./watch-stream\";\n\nexport type { ChatMemory, MemoryCounts, MemoryMergeRecord, MemoryResult } from \"./memory-routes\";\n\n/**\n * `busy` — в чате идёт ход; `approval_not_pending` — на этот запрос подтверждения отвечать уже нечем (решён или закрыт новым сообщением);\n * `approval_stale` — фоновая задача ждёт ответа уже на другой вопрос: ответ на старый не принят;\n * `not_pending` — браузерный вызов не ждёт ответа этой вкладки (уже отвечен, срок вышел, ход кончился, вкладка чужая);\n * `widget_not_pending` — виджет не ждёт ответа (уже отвечен, закрыт сообщением, в другой ветке, убран из настроек);\n * `invalid_answer` — ответ на виджет не прошёл схему, длиннее предела или тело запроса негодное; ничего не записано;\n * `widget_answer_refused` — приложение отказало в ответе на виджет (`checkAnswer`): `{ kind, code, message }`, ничего не записано,\n * виджет ждёт;\n * `widget_answer_cancelled` — человек нажал «Стоп», пока шла проверка ответа (`checkAnswer`): `{ kind, code, message }`, ничего не\n * записано, виджет ждёт; это не сбой;\n * `widget_check_timeout` (504) — проверка ответа (`checkAnswer`) не уложилась в `checkAnswerTimeoutMs`: `{ kind, code, message }`,\n * ничего не записано, виджет ждёт, повторить можно;\n * `too_many_turns` — у человека уже идёт столько ответов, сколько разрешено (`maxConcurrentTurnsPerUser`);\n * `forbidden_origin` — запрос, меняющий данные, пришёл с чужой страницы (`allowedOrigins`); `too_large` — тело больше 1 МиБ;\n * `turn_refused` — приложение отказало в ходе (`beforeTurn`); `access_refused` — приложение отказало в доступе (`resolveUser`);\n * `action_forbidden` — действие запрещено правами чата (`chatCapabilities`); `chat_closed` — чат закрыт (`closeChat`);\n * `chat_creation_refused` — приложение не дало завести новый чат (`beforeCreateChat`).\n * У отказов приложения одно тело: `{ kind, code, message }` (у `action_forbidden` ещё `action`).\n */\nexport type HandlerErrorKind =\n | ChatErrorKind\n | \"not_found\"\n | \"busy\"\n | \"invalid\"\n | \"spending_limit_reached\"\n | \"approval_not_pending\"\n | \"approval_stale\"\n | \"widget_not_pending\"\n | \"invalid_answer\"\n | \"widget_answer_refused\"\n | \"widget_answer_cancelled\"\n | \"widget_check_timeout\"\n | \"not_pending\"\n | \"too_many_turns\"\n | \"forbidden_origin\"\n | \"too_large\"\n | \"turn_refused\"\n | \"access_refused\"\n | \"action_forbidden\"\n | \"chat_closed\"\n | \"chat_creation_refused\";\n\n/** Вложения: приём в сообщение и сборка запроса (для ядра) плюс адреса под `/:chatId/attachments`. Даёт пакет `@ykurilov/agent-chat-attachments`. */\nexport interface ChatAttachments extends MessageAttachments {\n /** Ответ на `GET {base}/attachments/settings`. */\n settings(): Response;\n /** Всё под `/:chatId/attachments`; чат уже проверен как принадлежащий человеку. `segments` — путь после `attachments`. */\n handle(input: { request: Request; userId: string; chatId: string; segments: string[] }): Promise<Response>;\n}\n\n/** Опции службы ядра, которые сервер принимает от приложения: все, кроме тех, что сервер заводит сам. */\ntype ServiceOptionsOfServer = Omit<Parameters<typeof createChatService>[0], \"registry\" | \"chatEvents\">;\n\n/**\n * Опции сервера: все опции службы ядра (`createChatService`) плюс свои. Новая опция службы доходит сама — без правки\n * сервера; ниже повторены только поля с пояснением для сервера.\n */\nexport interface ChatHandlerOptions extends ServiceOptionsOfServer {\n /** Путь, под которым смонтирован обработчик, например `/api/chat`. */\n basePath: string;\n storage: ChatStorage;\n /**\n * Кто спрашивает: `{ id }` — человек, `null` — не вошёл (401 `invalid`). `{ refused: { code, message, status? } }` —\n * приложение отказывает человеку в доступе своими словами: на любом адресе ответ `status` (401 или 403, по умолчанию 403)\n * `{ error: { kind: \"access_refused\", code, message } }`. Код — `[a-z0-9_.-]`, 1–64 знака (иначе 500 `other` и строка\n * в `logError`); текст — до 300 знаков.\n */\n resolveUser: (request: Request) => Promise<ResolvedUser>;\n /** Выбор модели для новых чатов. Не задан — первая модель каталога у первого провайдера с ключом. */\n defaultChoice?: ModelChoice;\n catalog: ModelCatalog;\n agent: AgentSettings;\n /** Автосжатие истории: включено по умолчанию, `false` выключает. Подробнее — в настройках ядра. */\n compaction?: CompactionSettings | false;\n /** Вложения к сообщениям. Не заданы — адресов `/attachments` нет, сообщения только текстовые. */\n attachments?: ChatAttachments;\n /**\n * Лимиты расходов (пакет `@ykurilov/agent-chat-limits`). Исчерпанный лимит — ответ 429 `spending_limit_reached`\n * на отправку, повтор, переделку и правку; появляется адрес `GET {base}/:chatId/spending`. Не заданы — адреса нет.\n */\n limits?: SpendingLimits;\n /**\n * Сколько ответов одного человека могут идти одновременно во всех его чатах. Сверх этого — ответ 429\n * `too_many_turns` с полем `limit`, ничего не записано. Считается в памяти процесса. По умолчанию 3; `Infinity` — без предела.\n */\n maxConcurrentTurnsPerUser?: number;\n /**\n * Проверка приложения перед ходом: вернуть `{ code, message }` — ход не начнётся, ничего не запишется, ответ 403\n * `{ error: { kind: \"turn_refused\", code, message } }` на отправку, повтор, переделку, правку, ответ на подтверждение\n * и ответ на виджет. Подробнее — `ChatServiceOptions.beforeTurn`. Нет — ходы не проверяются.\n */\n beforeTurn?: BeforeTurnHandler;\n /**\n * Ход не начался, хотя `beforeTurn` его пропустил (чат занят, предел ходов, лимит, повтор id, неверный запрос, сбой):\n * снять резерв приложения. Ответ клиенту прежний. Подробнее — `ChatServiceOptions.turnNotStarted`.\n */\n turnNotStarted?: TurnNotStartedHandler;\n /**\n * Права человека в чате: частичный список `{ право: boolean }`, пропущенное — `true`. Запрещённое — 403\n * `{ error: { kind: \"action_forbidden\", action, code, message } }`, история отдаёт `capabilities`, экран прячет кнопки.\n * Право `attachments` закрывает и загрузку вложений (`/:chatId/attachments`, кроме GET). Подробнее —\n * `ChatServiceOptions.chatCapabilities`. Нет — всё разрешено.\n */\n chatCapabilities?: ChatCapabilitiesHandler;\n /**\n * Можно ли человеку завести новый чат (`POST {base}`; тот же хук спрашивают мессенджеры на `/new`). Отказ — 403\n * `{ error: { kind: \"chat_creation_refused\", code, message } }`, чат не создан. Подробнее —\n * `ChatServiceOptions.beforeCreateChat`. Нет — чаты заводятся без проверки.\n */\n beforeCreateChat?: BeforeCreateChatHandler;\n /**\n * Долгая память агента между чатами (пакет `@ykurilov/agent-chat-memory`): ядро добавляет её в запросы и инструменты,\n * появляются адреса `{base}/memory` для экрана памяти. Не задана — адресов нет (404), чат как раньше.\n */\n memory?: ChatMemory;\n /**\n * Список чатов человека: `GET {base}`, переименование `PATCH {base}/{id}` и удаление `DELETE {base}/{id}`.\n * Не включён — этих адресов нет (404), у приложения с одним чатом всё работает как раньше. Чаты объектов\n * (`subjectChats`) в списке не видны; `{ subjectChats: true }` — видны, с полем `subjectKey`.\n */\n chatList?: boolean | { subjectChats?: boolean };\n /**\n * Чаты объектов приложения (урок, заказ): адрес `POST {base}/subject { key }` → `{ id, created }` находит или\n * создаёт чат человека по ключу (1–200 знаков). Приложение по ключу отдаёт данные объекта (только для сервера),\n * название и приветствие нового чата — или отказ `{ code, message }` (ответ 403 `turn_refused`, чат не создан).\n * Не задано — адреса нет (404). Требует хранилища с `findChatBySubject`: без него — ошибка при создании обработчика.\n */\n subjectChats?: SubjectChatsResolver;\n /**\n * Данные нового чата, который человек создаёт из браузера (`POST {base}`): сервер решает, что записать, браузер\n * данных не присылает. JSON-объект до 16 КБ (`Chat.data`) или `undefined` — чат без данных. Видят `beforeTurn`,\n * `chatInstructions`, инструменты (`model.chat.data`) и `afterTurn` (`turn.chatData`); в браузер не уходят.\n * Вернул не объект или бросил — ответ 500, чат не создан. Чаты объектов (`subjectChats`) берут данные из своего решения.\n * Требует хранилища с `updateChatData`: без него — ошибка при создании обработчика.\n */\n newChatData?: NewChatDataResolver;\n /**\n * Чужие страницы, которым можно менять данные чата с куками человека, например `[\"https://school.getcourse.ru\"]`,\n * когда чат встроен в чужой сайт и шлёт запросы прямо оттуда. Свою страницу вносить не нужно: запросы с неё проходят всегда.\n * Запрос, меняющий данные (не GET), с чужой страницы — ответ 403 `forbidden_origin`, ничего не записано.\n * Запросы не из браузера (без заголовков Origin и Sec-Fetch-Site) проходят: их судит `resolveUser`.\n */\n allowedOrigins?: string[];\n /** Откуда брать ключи провайдеров. По умолчанию `process.env`. */\n env?: Record<string, string | undefined>;\n /** Журнал сервера: нет ключа провайдера, непредвиденные сбои. По умолчанию `console.error`. */\n logError?: (message: string) => void;\n /** Промежуток между признаками жизни в потоках ответа. По умолчанию 10 000 мс, не меньше 1000. */\n heartbeatIntervalMs?: number;\n /** Наблюдатель хода (песочница): см. `ChatServiceOptions.turnObserver`. Нет — запрос к модели побайтно прежний. */\n turnObserver?: TurnObserver;\n /** Своя инструкция чата (песочница): см. `ChatServiceOptions.chatInstructions`. `null` — инструкция приложения. */\n chatInstructions?: ChatInstructions;\n /** Сотрудник хода по чату (песочница: копия чата человека): см. `ChatServiceOptions.staffOfChat`. */\n staffOfChat?: (chat: Chat) => StaffActor | null | undefined;\n /** Инструменты, чей результат правка ответа не меняет (файлы): см. `ChatServiceOptions.lockedToolOutputs`. */\n lockedToolOutputs?: readonly string[];\n /** Несколько служб на одном хранилище: чья брошенная пачка. См. `ChatServiceOptions.abandonedBursts`. */\n abandonedBursts?: AbandonedBurstsOptions;\n /**\n * Фоновые задачи (пакет `@ykurilov/agent-chat-background`): появляются адреса `{base}/:chatId/tasks`. Не заданы —\n * адресов задач нет (404), запрос к модели прежний.\n */\n background?: BackgroundTasks;\n /**\n * Ход закончен: `{ userId, chatId, messageId, status, modelId, providerId }`, один раз на ход при любом итоге —\n * законченном, остановленном, с ошибкой, прерванном, ждущем человека. Зовётся, когда итог уже записан; ход обработчик\n * не ждёт, его сбой уходит в `logError`. Нет — ничего не зовётся.\n */\n onTurnFinished?: TurnFinishedHandler;\n /**\n * Итог хода для приложения: ход ждёт его до `finish`, возвращённые `events` ложатся в ответ частями `data-app-event`.\n * Повторы, срок, состояние в ответе и доделка после перезапуска — как у `ChatServiceOptions.afterTurn`. Событие вне\n * хода — `service.publishAppEvent` (служба из `createChatServer`): открытая вкладка получит его через `watch`.\n */\n afterTurn?: AfterTurnHandler;\n /** Срок хука итога на все попытки, мс. По умолчанию 30 000. */\n afterTurnTimeoutMs?: number;\n /** Попыток хука итога. По умолчанию 3. */\n afterTurnAttempts?: number;\n /**\n * Состояние чата сменилось (ход, правка, «Переделать», «Повторить», выбор ветки, правка ответа): копия состояния в\n * таблицах приложения. Как у `ChatServiceOptions.onStateChanged`; открытая вкладка получает событие `state-changed`\n * через `watch`.\n */\n onStateChanged?: StateChangedHandler;\n /** Срок хука смены состояния на все попытки, мс. По умолчанию 30 000. */\n onStateChangedTimeoutMs?: number;\n /** Попыток хука смены состояния. По умолчанию 3. */\n onStateChangedAttempts?: number;\n /**\n * Человек выбрал модель или уровень в меню (`PUT {chat}/model`): после записи выбора. Подстановка выбора и\n * `service.setModel(..., { by: \"app\" })` его не зовут. Как у `ChatServiceOptions.onModelChosen`.\n */\n onModelChosen?: ModelChosenHandler;\n /**\n * Видит ли человек `GET …/models`: `false` — 403 `action_forbidden` (`action: \"modelDetails\"`). Меню не знает чата,\n * поэтому право `modelDetails` его не закрывает. Нет — меню видно всем. Как у `ChatServiceOptions.modelMenu`.\n */\n modelMenu?: (input: { userId: string }) => boolean | Promise<boolean>;\n /**\n * Закрытые верхние ключи состояния чата: не уходят в браузер ни в истории, ни в потоке хода, ни в `state-changed`\n * адреса `watch`. Сервер видит их как обычно. Как у `ChatServiceOptions.privateStateKeys`.\n */\n privateStateKeys?: readonly string[];\n /** Проверки готового ответа перед записью. Как у `ChatServiceOptions.replyChecks`. */\n replyChecks?: ReplyChecksSetting;\n /** Заголовки раздумий вместо «Думал 3 сек». Как у `ChatServiceOptions.thoughtTitles`. */\n thoughtTitles?: ThoughtTitlesSetting;\n /** Часы службы: тесты и песочница подменяют время целиком. Как у `ChatServiceOptions.now`. */\n now?: () => Date;\n}\n\nexport interface ChatHandler {\n basePath: string;\n fetch(request: Request): Promise<Response>;\n /**\n * Помечает «прерванными» ответы, оборванные прошлым запуском сервера, и запускает регулярную проверку брошенных\n * ответов. Вызывать при старте, до приёма запросов; в Next.js — один раз сразу после `createChatHandler`: свои\n * живые ответы проверка не трогает. Без вызова оборванные ответы остаются «идущими». Возвращает число помеченных.\n * `{ soleServer: true }` — копия сервера на этой базе одна: оборванные ответы прерываются сразу, а не через 15–20 с.\n * Только при старте процесса: вызов позже прервёт ход, который как раз запускается. Нельзя с несколькими копиями\n * сервера на одной базе и с несколькими обработчиками в одном процессе (dev-режим Next.js): прервутся живые ответы.\n */\n recoverInterruptedAnswers(options?: RecoverInterruptedAnswersOptions): Promise<number>;\n /**\n * Штатная остановка: после того как сервер перестал принимать запросы, до закрытия хранилища. Ждёт идущие ответы до\n * `waitForAnswersMs` (по умолчанию 0), не успевшие останавливает, как «Стоп», с записью; возвращается, когда всё\n * записано и хуки итога закончены. Подробнее — `ChatService.flush`.\n */\n flush(options?: FlushOptions): Promise<void>;\n /**\n * Открыт ли у чата этого владельца поток `GET /:chatId/watch` (вкладка с чатом). Необязателен: обёртки над сервером\n * (песочница) реализуют этот тип без него; мессенджеры берут его у `ChatServerService`.\n */\n hasWatchers?(ownerId: string, chatId: string): boolean;\n}\n\n/** Служба чата сервера: служба ядра плюс то, что знает только сервер, — открытые вкладки и задачи фона. */\nexport interface ChatServerService extends ChatService {\n /** Открыт ли у чата этого владельца поток `GET /:chatId/watch`. */\n hasWatchers(ownerId: string, chatId: string): boolean;\n /** Задачи фона; нет фона — нет и этого поля. */\n tasks?: { list: BackgroundTasks[\"listForChat\"] } & Pick<BackgroundTasks, \"cancel\" | \"answerApproval\">;\n}\n\n/** Значение из адреса для строки журнала: с управляющими символами — в кавычках и экранировано, чтобы не подделать строки. */\nconst logValue = (value: string): string => (/[\\p{Cc}\\u2028\\u2029]/u.test(value) ? JSON.stringify(value) : value);\n\n/** Поля сервера, которых у службы нет: всё остальное уходит в `createChatService` как есть. */\nconst SERVER_ONLY_OPTIONS = {\n basePath: true,\n resolveUser: true,\n chatList: true,\n subjectChats: true,\n newChatData: true,\n allowedOrigins: true,\n heartbeatIntervalMs: true,\n} satisfies Record<Exclude<keyof ChatHandlerOptions, keyof ServiceOptionsOfServer>, true>;\n\n/** Опции службы из опций сервера: без полей сервера и без пустых (`undefined` — как не заданное). */\nfunction serviceOptionsOf(options: ChatHandlerOptions): ServiceOptionsOfServer {\n return Object.fromEntries(\n Object.entries(options).filter(([key, value]) => value !== undefined && !Object.hasOwn(SERVER_ONLY_OPTIONS, key)),\n ) as unknown as ServiceOptionsOfServer;\n}\n\n/** HTTP-обработчик чата на стандартных `Request`/`Response` (contracts/http-api.md). */\nexport function createChatHandler(options: ChatHandlerOptions): ChatHandler {\n return createChatServer(options).handler;\n}\n\n/**\n * Обработчик чата и его служба: приложению (песочнице) служба нужна для своих адресов поверх тех же чатов —\n * в одном процессе с обработчиком, с общим реестром идущих ответов.\n */\nexport function createChatServer(options: ChatHandlerOptions): { handler: ChatHandler; service: ChatServerService } {\n const basePath = options.basePath.replace(/\\/+$/, \"\");\n const heartbeatIntervalMs = options.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS;\n assertHeartbeatInterval(heartbeatIntervalMs);\n const streamResponse = (stream: ReadableStream<unknown>) =>\n createHeartbeatResponse({ stream, heartbeatIntervalMs });\n const logError = options.logError ?? ((message: string) => console.error(message));\n const allowsOrigin = createRequestOriginCheck({\n allowedOrigins: options.allowedOrigins,\n onRefused: (refusal) => logError(`Чат: ${originRefusalHint(refusal)}`),\n });\n if (options.newChatData && !options.storage.updateChatData) {\n throw new Error(\"newChatData требует хранилища с updateChatData: хранилище приложения его не умеет.\");\n }\n if (options.subjectChats && !options.storage.findChatBySubject) {\n throw new Error(\"subjectChats требует хранилища с findChatBySubject: хранилище приложения его не умеет.\");\n }\n const service = createChatService({\n ...serviceOptionsOf(options),\n registry: createRunningAnswerRegistry(),\n logError,\n chatEvents: createChatEvents((error) => logError(`События чата: сбой слушателя: ${describe(error)}`)),\n });\n /** Ключи состояния, которые не уходят вкладке (служба уже проверила имена). */\n const hiddenStateKey = hiddenStateKeyOf(options.privateStateKeys);\n const background = options.background;\n /** Ответ человека на вопрос фоновой задачи продолжает работу модели: приложение проверяет его, как любой ход. */\n async function answerTaskApproval(ownerId: string, chatId: string, taskId: string, approvalId: string, decision: \"allow\" | \"deny\") {\n await service.assertTurnAllowed(ownerId, chatId, \"approval\");\n return background!.answerApproval(ownerId, chatId, taskId, approvalId, decision);\n }\n /** Открытые потоки `watch` по владельцу и чату. */\n const watchers = new Map<string, number>();\n const watcherKey = (ownerId: string, chatId: string) => JSON.stringify([ownerId, chatId]);\n const hasWatchers = (ownerId: string, chatId: string) => (watchers.get(watcherKey(ownerId, chatId)) ?? 0) > 0;\n\n /** Подписчик считается, пока поток открыт; снимается при отмене потока, уходе клиента и сбое открытия — ровно один раз. */\n function watchResponse(request: Request, userId: string, chatId: string): Response {\n const key = watcherKey(userId, chatId);\n watchers.set(key, (watchers.get(key) ?? 0) + 1);\n let counted = true;\n const uncount = () => {\n if (!counted) return;\n counted = false;\n const left = (watchers.get(key) ?? 1) - 1;\n if (left > 0) watchers.set(key, left);\n else watchers.delete(key);\n };\n try {\n const stream = createWatchStream({\n ownerId: userId,\n chatId,\n signal: request.signal,\n subscribeEvents: (listener) => service.subscribeEvents(listener),\n hiddenStateKey,\n ...(background && { listTasks: () => background.listForChat(userId, chatId) }),\n async detachedTurn() {\n const history = await service.getHistory(userId, chatId);\n const answer = history.messages.at(-1);\n return history.detachedAnswer && answer ? { messageId: answer.id, ...turnOriginOf(history.messages) } : null;\n },\n onClose: uncount,\n logError,\n });\n return streamResponse(stream);\n } catch (error) {\n uncount();\n throw error;\n }\n }\n\n async function route(request: Request, userId: string, segments: string[]): Promise<Response> {\n const [chatId, action, ...rest] = segments;\n const method = request.method.toUpperCase();\n\n if (options.attachments) {\n // Идентификаторы чатов — UUID, поэтому путь `attachments/settings` с чатом не пересекается.\n if (chatId === \"attachments\" && action === \"settings\" && rest.length === 0 && method === \"GET\") {\n return options.attachments.settings();\n }\n if (chatId !== undefined && action === \"attachments\") {\n await service.assertOwnChat(userId, chatId);\n // Загрузка и удаление — под правом `attachments`; файлы уже отправленных сообщений читаются всегда.\n if (method !== \"GET\" && method !== \"HEAD\") await service.assertCapability(userId, chatId, \"attachments\");\n return options.attachments.handle({ request, userId, chatId, segments: rest });\n }\n }\n\n // Идентификаторы чатов — UUID, поэтому путь `memory` с чатом не пересекается.\n if (options.memory && chatId === \"memory\") {\n return handleMemoryRequest({ memory: options.memory, request, userId, segments: segments.slice(1) });\n }\n\n if (chatId !== undefined && action === \"approvals\" && rest.length === 1 && method === \"POST\") {\n const body = await readJsonBody(request);\n const decision = (body as { decision?: unknown } | null)?.decision;\n if (decision !== \"allow\" && decision !== \"deny\" && decision !== \"always\") return errorResponse(400, \"invalid\");\n const page = parsePage(body);\n if (page === \"invalid\") return errorResponse(400, \"invalid\");\n return Response.json(await service.answerApproval(userId, chatId, rest[0]!, decision, page));\n }\n\n if (chatId !== undefined && action === \"widgets\" && rest.length === 1 && method === \"POST\") {\n const toolCallId = rest[0]!;\n const body = await readWidgetAnswerBody(request);\n if (!body) {\n logError(`[виджет] отказ invalid_answer чат=${logValue(chatId)} вызов=${logValue(toolCallId)}`);\n return errorResponse(400, \"invalid_answer\");\n }\n const page = parsePage(body);\n if (page === \"invalid\") return errorResponse(400, \"invalid\");\n return Response.json(await service.answerWidget({ chatId, ownerId: userId, toolCallId, answer: body.answer, ...(page && { page }) }));\n }\n\n if (chatId !== undefined && action === \"browser-tools\" && rest.length === 1 && method === \"POST\") {\n const answer = parseBrowserCallAnswer(await readJsonBody(request, MAX_BROWSER_CALL_BODY_BYTES));\n if (!answer) return errorResponse(400, \"invalid\");\n await service.answerBrowserCall(userId, chatId, rest[0]!, answer);\n return new Response(null, { status: 204 });\n }\n\n if (chatId !== undefined && action === \"watch\" && rest.length === 0 && method === \"GET\") {\n await service.assertOwnChat(userId, chatId);\n return watchResponse(request, userId, chatId);\n }\n\n if (background && chatId !== undefined && action === \"tasks\") {\n const [taskId, taskAction, ...extra] = rest;\n if (taskId === undefined && method === \"GET\") {\n await service.assertOwnChat(userId, chatId);\n return Response.json(await background.listForChat(userId, chatId));\n }\n if (taskId !== undefined && taskAction === \"cancel\" && extra.length === 0 && method === \"POST\") {\n await service.assertOwnChat(userId, chatId);\n const status = await background.cancel(userId, chatId, taskId);\n return status === \"not_found\" ? errorResponse(404, \"not_found\") : Response.json({ status });\n }\n if (taskId !== undefined && taskAction === \"approval\" && extra.length === 0 && method === \"POST\") {\n await service.assertOwnChat(userId, chatId);\n const body = (await readJsonBody(request)) as { decision?: unknown; approvalId?: unknown } | null;\n const decision = body?.decision;\n const approvalId = body?.approvalId;\n if ((decision !== \"allow\" && decision !== \"deny\") || typeof approvalId !== \"string\" || approvalId === \"\") return errorResponse(400, \"invalid\");\n const status = await answerTaskApproval(userId, chatId, taskId, approvalId, decision);\n if (status === \"not_found\") return errorResponse(409, \"approval_not_pending\");\n if (status === \"stale\") return errorResponse(409, \"approval_stale\");\n return Response.json({ status });\n }\n }\n\n if (rest.length > 0) return errorResponse(404, \"not_found\");\n\n // Идентификаторы чатов — UUID, поэтому путь `subject` с чатом не пересекается.\n if (options.subjectChats && chatId === \"subject\" && action === undefined && method === \"POST\") {\n return openSubjectChatRoute({ request, userId, service, resolve: options.subjectChats, invalid: () => errorResponse(400, \"invalid\") });\n }\n\n if (chatId === undefined) {\n if (method === \"GET\" && options.chatList) return Response.json({ chats: await service.listChats(userId, chatListOptions(options.chatList)) });\n if (method !== \"POST\") return errorResponse(404, \"not_found\");\n const data = options.newChatData ? await resolveNewChatData(options.newChatData, { user: { id: userId }, request }) : undefined;\n const chat = await service.createChat(userId, data && { data });\n return Response.json({ id: chat.id }, { status: 201 });\n }\n\n // Идентификаторы чатов — UUID, поэтому путь `models` с чатом не пересекается.\n if (chatId === \"models\" && action === undefined && method === \"GET\") {\n return Response.json(await service.getModelMenu(userId));\n }\n\n if (action === undefined && method === \"GET\") {\n // `backgroundTasks` — заявка клиенту: адрес `{chat}/tasks` есть, только если фон подключён.\n return Response.json({ ...(await service.getHistory(userId, chatId)), backgroundTasks: background !== undefined });\n }\n\n if (action === undefined && method === \"PATCH\" && options.chatList) {\n const title = (await readJsonBody(request) as { title?: unknown } | null)?.title;\n if (typeof title !== \"string\") return errorResponse(400, \"invalid\");\n return Response.json({ id: chatId, title: await service.renameChat(userId, chatId, title) });\n }\n\n if (action === undefined && method === \"DELETE\" && options.chatList) {\n await service.deleteChat(userId, chatId);\n return new Response(null, { status: 204 });\n }\n\n if (action === \"compactions\" && method === \"GET\") {\n return Response.json(await service.getCompactions(userId, chatId));\n }\n\n if (action === \"spending\" && method === \"GET\" && options.limits) {\n return Response.json(await service.getSpendingStatus(userId, chatId));\n }\n\n if (action === \"branches\" && method === \"GET\") {\n return Response.json(await service.getBranchForks(userId, chatId));\n }\n\n if (action === \"branch\" && method === \"POST\") {\n const messageId = parseMessageId(await readJsonBody(request));\n if (!messageId) return errorResponse(400, \"invalid\");\n return Response.json(await service.selectBranch(userId, chatId, messageId));\n }\n\n if (action === \"regenerate\" && method === \"POST\") {\n const body = await readJsonBody(request);\n const messageId = parseMessageId(body);\n if (!messageId) return errorResponse(400, \"invalid\");\n const page = parsePage(body);\n if (page === \"invalid\") return errorResponse(400, \"invalid\");\n return streamResponse(await service.regenerate(userId, chatId, messageId, page));\n }\n\n if (action === \"edit\" && method === \"POST\") {\n const body = (await readJsonBody(request)) as { editedMessageId?: unknown; message?: IncomingUserMessage; voiceMode?: unknown; voiceSounds?: unknown } | null;\n const { editedMessageId, message } = body ?? {};\n if (typeof editedMessageId !== \"string\" || !message || typeof message !== \"object\") return errorResponse(400, \"invalid\");\n // Продолжение голосовой фразы (склейка): признак как у отправки; не булево — отказ, ничего не записано.\n if (body?.voiceMode !== undefined && typeof body.voiceMode !== \"boolean\") return errorResponse(400, \"invalid\");\n const voiceSounds = parseVoiceSounds(body?.voiceSounds);\n if (voiceSounds === \"invalid\") return errorResponse(400, \"invalid\");\n const page = parsePage(body);\n if (page === \"invalid\") return errorResponse(400, \"invalid\");\n return streamResponse(\n await service.editMessage(userId, chatId, editedMessageId, message, { voiceMode: body?.voiceMode === true, voiceSounds, ...(page && { page }) }),\n );\n }\n\n if (action === \"model\" && method === \"PUT\") {\n const choice = parseModelChoice(await readJsonBody(request));\n if (!choice) return errorResponse(400, \"invalid\");\n return Response.json(await service.setModel(userId, chatId, choice));\n }\n\n if (action === \"messages\" && method === \"POST\") {\n const body = (await readJsonBody(request)) as { message?: IncomingUserMessage; voiceMode?: unknown; voiceSounds?: unknown } | null;\n const message = body?.message;\n if (!message || typeof message !== \"object\") return errorResponse(400, \"invalid\");\n // Признак голосового режима: не булево — отказ, ничего не записано.\n if (body.voiceMode !== undefined && typeof body.voiceMode !== \"boolean\") return errorResponse(400, \"invalid\");\n const voiceSounds = parseVoiceSounds(body.voiceSounds);\n if (voiceSounds === \"invalid\") return errorResponse(400, \"invalid\");\n const page = parsePage(body);\n if (page === \"invalid\") return errorResponse(400, \"invalid\");\n // Поток не привязан к request.signal: обрыв соединения только отписывает слушателя.\n const stream = await service.sendMessage(userId, chatId, message, { voiceMode: body.voiceMode === true, voiceSounds, ...(page && { page }) });\n return streamResponse(stream);\n }\n\n if (action === \"steer\" && method === \"POST\") {\n const body = (await readJsonBody(request)) as { message?: IncomingUserMessage } | null;\n const message = body?.message;\n if (!message || typeof message !== \"object\") return errorResponse(400, \"invalid\");\n const page = parsePage(body);\n if (page === \"invalid\") return errorResponse(400, \"invalid\");\n // Поправка к идущему ответу или начало хода; ответ на неё — в потоке GET stream.\n return Response.json(await service.steer(userId, chatId, message, page && { page }), { status: 202 });\n }\n\n if (action === \"retry\" && method === \"POST\") {\n const page = parsePage(await readJsonBody(request));\n if (page === \"invalid\") return errorResponse(400, \"invalid\");\n const stream = await service.retry(userId, chatId, page);\n return streamResponse(stream);\n }\n\n if (action === \"stream\" && method === \"GET\") {\n // Как и у отправки, поток не привязан к request.signal: обрыв только отписывает слушателя.\n const stream = await service.subscribe(userId, chatId);\n if (!stream) return new Response(null, { status: 204 });\n return streamResponse(stream);\n }\n\n if (action === \"stop\" && method === \"POST\") {\n const stopped = await service.stop(userId, chatId);\n return new Response(null, { status: stopped ? 202 : 204 });\n }\n\n return errorResponse(404, \"not_found\");\n }\n\n const handler: ChatHandler = {\n basePath,\n recoverInterruptedAnswers: (recoverOptions) => service.recoverInterruptedAnswers(recoverOptions),\n flush: (flushOptions) => service.flush(flushOptions),\n hasWatchers,\n async fetch(request) {\n const segments = pathSegments(new URL(request.url).pathname, basePath);\n if (!segments) return errorResponse(404, \"not_found\");\n if (!isSafeMethod(request.method) && !allowsOrigin(request)) return errorResponse(403, \"forbidden_origin\");\n try {\n const resolved = await options.resolveUser(request);\n const refused = accessRefusedResponse(resolved);\n if (refused) return refused;\n const user = allowedUser(resolved);\n if (!user) return errorResponse(401, \"invalid\");\n return await route(request, user.id, segments);\n } catch (error) {\n const response = errorToResponse(error);\n if (response.status >= 500) logError(`Чат: сбой на ${request.method} ${new URL(request.url).pathname}: ${describe(error)}`);\n return response;\n }\n },\n };\n const serverService: ChatServerService = Object.assign(service, {\n hasWatchers,\n ...(background && { tasks: { list: background.listForChat.bind(background), cancel: background.cancel.bind(background), answerApproval: answerTaskApproval } }),\n });\n return { handler, service: serverService };\n}\n\nfunction pathSegments(pathname: string, basePath: string): string[] | null {\n if (pathname !== basePath && !pathname.startsWith(`${basePath}/`)) return null;\n try {\n return pathname\n .slice(basePath.length)\n .split(\"/\")\n .filter((segment) => segment.length > 0)\n .map((segment) => decodeURIComponent(segment));\n } catch {\n // Битая кодировка (например, `%E0`): такого чата не бывает.\n return null;\n }\n}\n\n/** Звуки человека из голосового режима: не передан — `undefined`; не список или вид вне закрытого списка — `\"invalid\"`. */\nfunction parseVoiceSounds(value: unknown): VoiceSound[] | undefined | \"invalid\" {\n if (value === undefined) return undefined;\n if (!Array.isArray(value) || !value.every(isVoiceSound)) return \"invalid\";\n return value;\n}\n\n/**\n * Место на экране, номер вкладки и часовой пояс человека из тела запроса хода. Нет ничего — `undefined` (как до 021);\n * неверная форма места или вкладки — `\"invalid\"`, запрос отклоняется до любой записи. Пояс-строку проверяет ядро:\n * не настоящий пояс — дата по запасному поясу, не ошибка; пояс не строкой не берётся.\n */\nfunction parsePage(body: unknown): TurnRequestContext | undefined | \"invalid\" {\n const { place, tabId, timeZone } = (typeof body === \"object\" && body !== null ? body : {}) as Record<string, unknown>;\n const zone = typeof timeZone === \"string\" ? timeZone : undefined;\n if (place === undefined && tabId === undefined && zone === undefined) return undefined;\n if (place !== undefined && !isScreenPlaceInput(place)) return \"invalid\";\n if (tabId !== undefined && !isTabId(tabId)) return \"invalid\";\n return { ...(place !== undefined && { place }), ...(tabId !== undefined && { tabId }), ...(zone !== undefined && { timeZone: zone }) };\n}\n\n/** Ответ вкладки: `{ tabId, output }` или `{ tabId, error: строка }`, ровно одно из двух; иначе `null`. */\nfunction parseBrowserCallAnswer(body: unknown): BrowserCallAnswer | null {\n if (typeof body !== \"object\" || body === null || Array.isArray(body)) return null;\n const { tabId } = body as { tabId?: unknown };\n if (!isTabId(tabId)) return null;\n const hasOutput = Object.hasOwn(body, \"output\");\n const hasError = Object.hasOwn(body, \"error\");\n if (hasOutput === hasError) return null;\n if (hasOutput) return { tabId, output: (body as { output: unknown }).output };\n const { error } = body as { error: unknown };\n return typeof error === \"string\" ? { tabId, error } : null;\n}\n\nfunction parseMessageId(body: unknown): string | null {\n const { messageId } = (body ?? {}) as Record<string, unknown>;\n return typeof messageId === \"string\" && messageId.length > 0 ? messageId : null;\n}\n\nfunction parseModelChoice(body: unknown): ModelChoice | null {\n const { modelId, providerId, reasoning } = (body ?? {}) as Record<string, unknown>;\n if (typeof modelId !== \"string\" || typeof providerId !== \"string\") return null;\n if (reasoning !== undefined && typeof reasoning !== \"string\") return null;\n return { modelId, providerId, ...(reasoning !== undefined && { reasoning }) };\n}\n\n/** Ответ вкладки больше этого не читается: модели и так уходит не больше 16 КБ результата. */\nconst MAX_BROWSER_CALL_BODY_BYTES = 64 * 1024;\n/** Тело ответа на виджет: сам ответ не длиннее 16 КБ (его меряет служба), остальное — место на экране. */\nconst MAX_WIDGET_BODY_BYTES = 64 * 1024;\n\n/** Тело `{ answer, ...место на экране }`; без `answer`, не JSON или длиннее предела — `null`. */\nasync function readWidgetAnswerBody(request: Request): Promise<{ answer: unknown } | null> {\n try {\n const body = await readJsonBody(request, MAX_WIDGET_BODY_BYTES);\n return typeof body === \"object\" && body !== null && !Array.isArray(body) && Object.hasOwn(body, \"answer\") ? (body as { answer: unknown }) : null;\n } catch (error) {\n if (error instanceof RequestBodyTooLargeError) return null;\n throw error;\n }\n}\n\n/** Какие чаты в списке: чаты объектов — только при `chatList: { subjectChats: true }`. */\nfunction chatListOptions(chatList: NonNullable<ChatHandlerOptions[\"chatList\"]>): { subjects?: \"include\" } | undefined {\n return typeof chatList === \"object\" && chatList.subjectChats ? { subjects: \"include\" } : undefined;\n}\n\nfunction errorResponse(status: number, kind: HandlerErrorKind): Response {\n return Response.json({ error: { kind } }, { status });\n}\n\nfunction describe(error: unknown): string {\n return error instanceof Error ? `${error.name}: ${error.message}` : String(error);\n}\n\nfunction errorToResponse(error: unknown): Response {\n if (error instanceof RequestBodyTooLargeError) return errorResponse(413, \"too_large\");\n if (error instanceof ChatNotFoundError) return errorResponse(404, \"not_found\");\n if (error instanceof InvalidChatTitleError) return errorResponse(400, \"invalid\");\n if (error instanceof InvalidMessageError) return errorResponse(400, \"invalid\");\n if (error instanceof AttachmentRejectedError) return Response.json({ error: { kind: error.kind } }, { status: 400 });\n if (error instanceof InvalidModelChoiceError) return errorResponse(400, \"invalid\");\n if (error instanceof SpendingLimitReachedError) {\n const { scope, period, resetsAt, timeZone, reason, windowId, hours, unit } = error;\n const details = { ...(timeZone && { timeZone }), ...(reason && { reason }), ...(windowId && { windowId }), ...(hours !== undefined && { hours }), ...(unit && { unit }) };\n return Response.json({ error: { kind: \"spending_limit_reached\", scope, period, resetsAt, ...details } }, { status: 429 });\n }\n if (error instanceof TurnRefusedError) return turnRefusalResponse(error.refusal);\n if (error instanceof ActionForbiddenError) {\n return Response.json({ error: { kind: \"action_forbidden\", action: error.action, code: error.action, message: error.message } }, { status: 403 });\n }\n if (error instanceof ChatClosedError) return Response.json({ error: { kind: \"chat_closed\", code: error.code, message: error.message } }, { status: 409 });\n if (error instanceof ChatCreationRefusedError) {\n return Response.json({ error: { kind: \"chat_creation_refused\", code: error.code, message: error.message } }, { status: 403 });\n }\n if (error instanceof TooManyTurnsError) return Response.json({ error: { kind: \"too_many_turns\", limit: error.limit } }, { status: 429 });\n if (error instanceof ChatBusyError) return errorResponse(409, \"busy\");\n if (error instanceof DuplicateMessageError) return errorResponse(409, \"invalid\");\n if (error instanceof NothingToRetryError) return errorResponse(409, \"invalid\");\n if (error instanceof ApprovalNotPendingError) return errorResponse(409, \"approval_not_pending\");\n if (error instanceof BrowserCallNotPendingError) return errorResponse(409, \"not_pending\");\n if (error instanceof WidgetNotPendingError) return errorResponse(409, \"widget_not_pending\");\n if (error instanceof InvalidWidgetAnswerError) return errorResponse(400, \"invalid_answer\");\n if (error instanceof WidgetAnswerRefusedError) {\n return Response.json({ error: { kind: \"widget_answer_refused\", code: \"widget_answer_refused\", message: error.message } }, { status: 409 });\n }\n if (error instanceof WidgetAnswerCheckAbortedError) {\n return Response.json({ error: { kind: \"widget_answer_cancelled\", code: \"widget_answer_cancelled\", message: error.message } }, { status: 409 });\n }\n if (error instanceof WidgetAnswerCheckTimeoutError) {\n return Response.json({ error: { kind: \"widget_check_timeout\", code: \"widget_check_timeout\", message: error.message } }, { status: 504 });\n }\n return errorResponse(500, \"other\");\n}\n","import { JsonToSseTransformStream, UI_MESSAGE_STREAM_HEADERS } from \"ai\";\n\n/** Заголовок, по которому вкладка узнаёт, как часто сервер шлёт признак жизни. */\nexport const HEARTBEAT_HEADER = \"x-agent-chat-heartbeat-ms\";\nexport const MIN_HEARTBEAT_INTERVAL_MS = 1000;\nexport const DEFAULT_HEARTBEAT_INTERVAL_MS = 10_000;\n\nconst PING = new TextEncoder().encode(\": ping\\n\\n\");\n\n/**\n * Ответ-поток из частей UI-сообщения с признаком жизни: SSE-комментарий `: ping` сразу и потом\n * каждые `heartbeatIntervalMs`. События приходят целиком (по одному на кусок), а пинг встаёт\n * только между ними. Клиент SDK комментарии пропускает, поэтому в сообщения и хранилище они\n * не попадают.\n */\nexport function createHeartbeatResponse(options: {\n stream: ReadableStream<unknown>;\n heartbeatIntervalMs: number;\n}): Response {\n const { stream, heartbeatIntervalMs } = options;\n assertHeartbeatInterval(heartbeatIntervalMs);\n const events = stream.pipeThrough(new JsonToSseTransformStream()).getReader();\n const encoder = new TextEncoder();\n let timer: ReturnType<typeof setInterval> | undefined;\n let cancelled = false;\n const stopTimer = () => {\n if (timer !== undefined) clearInterval(timer);\n timer = undefined;\n };\n const body = new ReadableStream<Uint8Array>({\n start(controller) {\n controller.enqueue(PING);\n timer = setInterval(() => controller.enqueue(PING), heartbeatIntervalMs);\n },\n async pull(controller) {\n try {\n const { done, value } = await events.read();\n // Читатель ушёл, пока чтение висело: поток уже отменён, закрывать и писать в него нельзя.\n if (cancelled) return;\n if (done) {\n stopTimer();\n controller.close();\n return;\n }\n controller.enqueue(encoder.encode(value));\n } catch (error) {\n stopTimer();\n throw error;\n }\n },\n cancel(reason) {\n cancelled = true;\n stopTimer();\n return events.cancel(reason);\n },\n });\n return new Response(body, {\n headers: { ...UI_MESSAGE_STREAM_HEADERS, [HEARTBEAT_HEADER]: String(heartbeatIntervalMs) },\n });\n}\n\nexport function assertHeartbeatInterval(heartbeatIntervalMs: number): void {\n if (!Number.isFinite(heartbeatIntervalMs) || heartbeatIntervalMs < MIN_HEARTBEAT_INTERVAL_MS) {\n throw new RangeError(\n `heartbeatIntervalMs должен быть не меньше ${MIN_HEARTBEAT_INTERVAL_MS} мс, получено ${heartbeatIntervalMs}`,\n );\n }\n}\n","/** Больше этого тело JSON не читается: самое длинное сообщение (100 000 знаков) с местом на экране занимает меньше 0,5 МиБ. */\nexport const MAX_JSON_BODY_BYTES = 1024 * 1024;\n\n/** Тело запроса длиннее предела: оно не дочитано и не разобрано, ответ — 413. */\nexport class RequestBodyTooLargeError extends Error {\n constructor(maxBytes: number) {\n super(`Тело запроса больше ${maxBytes} байт`);\n this.name = \"RequestBodyTooLargeError\";\n }\n}\n\n/**\n * JSON тела не длиннее `maxBytes`: заявленная длина больше — тело не читается, поток длиннее — чтение обрывается;\n * тогда `RequestBodyTooLargeError`. Нет тела или не JSON — `null`.\n */\nexport async function readJsonBody(request: Request, maxBytes = MAX_JSON_BODY_BYTES): Promise<unknown> {\n if (Number(request.headers.get(\"content-length\")) > maxBytes) throw new RequestBodyTooLargeError(maxBytes);\n if (!request.body) return null;\n const reader = request.body.getReader();\n const chunks: Uint8Array[] = [];\n let size = 0;\n try {\n for (let read = await reader.read(); !read.done; read = await reader.read()) {\n size += read.value.byteLength;\n if (size > maxBytes) {\n void reader.cancel().catch(() => undefined);\n throw new RequestBodyTooLargeError(maxBytes);\n }\n chunks.push(read.value);\n }\n } catch (error) {\n if (error instanceof RequestBodyTooLargeError) throw error;\n return null;\n }\n const bytes = new Uint8Array(size);\n let offset = 0;\n for (const chunk of chunks) {\n bytes.set(chunk, offset);\n offset += chunk.byteLength;\n }\n try {\n return JSON.parse(new TextDecoder().decode(bytes));\n } catch {\n return null;\n }\n}\n","import type { LongTermMemory, MemoryError, MemoryErrorKind, MemoryMergeRevertResult, MemoryNote } from \"@ykurilov/agent-chat-core\";\nimport { readJsonBody } from \"./request-body\";\n\n/** Исход действия с памятью: запись или ошибка-ответ. То же, что у пакета `@ykurilov/agent-chat-memory`. */\nexport type MemoryResult<T> = { ok: true; value: T } | { ok: false; error: MemoryError };\n\n/** Сколько записей человека, какой предел и сколько из них агент видит в каждом запросе. */\nexport interface MemoryCounts {\n total: number;\n limit: number;\n shown: number;\n}\n\n/** Слияние одного запуска: что стало (`result`) вместо каких записей (`originals`). Как у пакета `@ykurilov/agent-chat-memory`. */\nexport interface MemoryMergeRecord {\n runId: string;\n finishedAt: string;\n revertible: boolean;\n groups: { result: MemoryNote; originals: MemoryNote[] }[];\n}\n\n/**\n * Долгая память вместе с действиями для адресов `/memory`. Даёт пакет `@ykurilov/agent-chat-memory`.\n * `listMerges` и `revertMerge` есть не у всякой памяти: без них адреса `/memory/merges` отвечают 404.\n */\nexport interface ChatMemory extends LongTermMemory {\n list(ownerId: string): Promise<{ notes: MemoryNote[]; counts: MemoryCounts }>;\n create(ownerId: string, input: unknown): Promise<MemoryResult<MemoryNote>>;\n update(ownerId: string, id: string, version: number, input: unknown): Promise<MemoryResult<MemoryNote>>;\n remove(ownerId: string, id: string, version: number): Promise<MemoryResult<{ deleted: string }>>;\n clear(ownerId: string): Promise<number>;\n listMerges?(ownerId: string, limit?: number): Promise<MemoryMergeRecord[]>;\n revertMerge?(ownerId: string, runId: string): Promise<MemoryMergeRevertResult>;\n}\n\nconst statusOfKind: Record<MemoryErrorKind, number> = {\n invalid: 400,\n not_found: 404,\n stale_version: 409,\n duplicate: 409,\n limit_reached: 409,\n};\n\nconst errorResponse = (kind: MemoryErrorKind) => Response.json({ error: { kind } }, { status: statusOfKind[kind] });\n\n/** Запись для экрана: только поля договора, всё служебное хранилища остаётся внутри. */\nfunction publicNote({ id, categories, content, version, createdAt, updatedAt }: MemoryNote): MemoryNote {\n return { id, categories, content, version, createdAt, updatedAt };\n}\n\n/** Склейки для экрана: записи только с полями договора (оригиналы без служебного `mergedInto`). */\nfunction publicMerge({ runId, finishedAt, revertible, groups }: MemoryMergeRecord) {\n return {\n runId,\n finishedAt,\n revertible,\n groups: groups.map(({ result, originals }) => ({ result: publicNote(result), originals: originals.map(publicNote) })),\n };\n}\n\nconst isVersion = (value: unknown): value is number => typeof value === \"number\" && Number.isInteger(value) && value >= 1;\n\n/** @throws RequestBodyTooLargeError тело больше 1 МиБ. */\nasync function readBody(request: Request): Promise<Record<string, unknown> | null> {\n const body = await readJsonBody(request);\n return typeof body === \"object\" && body !== null && !Array.isArray(body) ? (body as Record<string, unknown>) : null;\n}\n\n/** Разделы и текст из тела запроса; всё остальное (`id`, `version`, `ownerId`…) до памяти не доходит. */\nconst noteFrom = (body: Record<string, unknown> | null) => ({ categories: body?.categories, content: body?.content });\n\n/**\n * Всё под `{base}/memory`. `segments` — путь после `memory`. Человек уже определён; память по своей природе\n * его собственная, поэтому чужая запись для сервера — просто «не найдена».\n */\nexport async function handleMemoryRequest(input: { memory: ChatMemory; request: Request; userId: string; segments: string[] }): Promise<Response> {\n const { memory, request, userId, segments } = input;\n const method = request.method.toUpperCase();\n const [id, ...rest] = segments;\n if (id === \"merges\") return handleMergesRequest({ memory, method, userId, rest });\n if (rest.length > 0) return errorResponse(\"not_found\");\n\n if (id === undefined) {\n if (method === \"GET\") return Response.json(await listForScreen(memory, userId));\n if (method === \"POST\") {\n const created = await memory.create(userId, noteFrom(await readBody(request)));\n return created.ok ? Response.json({ note: publicNote(created.value) }, { status: 201 }) : errorResponse(created.error.kind);\n }\n if (method === \"DELETE\") return Response.json({ deleted: await memory.clear(userId) });\n return errorResponse(\"not_found\");\n }\n\n if (method === \"PATCH\") {\n const body = await readBody(request);\n if (!isVersion(body?.version)) return errorResponse(\"invalid\");\n const updated = await memory.update(userId, id, body.version, noteFrom(body));\n return updated.ok ? Response.json({ note: publicNote(updated.value) }) : errorResponse(updated.error.kind);\n }\n\n if (method === \"DELETE\") {\n const version = Number(new URL(request.url).searchParams.get(\"version\") || Number.NaN);\n if (!isVersion(version)) return errorResponse(\"invalid\");\n const removed = await memory.remove(userId, id, version);\n return removed.ok ? new Response(null, { status: 204 }) : errorResponse(removed.error.kind);\n }\n\n return errorResponse(\"not_found\");\n}\n\n/** `/memory/merges` (список) и `/memory/merges/:runId/revert` (вернуть как было). Память без склеек — 404. */\nasync function handleMergesRequest(input: { memory: ChatMemory; method: string; userId: string; rest: string[] }): Promise<Response> {\n const { memory, method, userId, rest } = input;\n const { listMerges, revertMerge } = memory;\n if (typeof listMerges !== \"function\" || typeof revertMerge !== \"function\") return errorResponse(\"not_found\");\n\n if (rest.length === 0 && method === \"GET\") {\n const merges = await listMerges.call(memory, userId);\n return Response.json({ merges: merges.map(publicMerge) });\n }\n const [runId, action, ...extra] = rest;\n if (runId !== undefined && action === \"revert\" && extra.length === 0 && method === \"POST\") {\n const result = await revertMerge.call(memory, userId, runId);\n if (result === \"ok\") return Response.json({ reverted: runId });\n // 409: результат менялся (`conflict`), либо возврат вышел бы за пределы (`limit_reached`) или вернул бы повтор (`duplicate`).\n return result === \"not_found\" ? errorResponse(\"not_found\") : Response.json({ error: { kind: result } }, { status: 409 });\n }\n return errorResponse(\"not_found\");\n}\n\nasync function listForScreen(memory: ChatMemory, userId: string) {\n const { notes, counts } = await memory.list(userId);\n return { notes: notes.map(publicNote), counts: { total: counts.total, limit: counts.limit, shown: counts.shown } };\n}\n","import { normalizeTurnRefusal, SUBJECT_KEY_MAX_LENGTH } from \"@ykurilov/agent-chat-core\";\nimport type { ChatGreeting, ChatService, TurnRefusal } from \"@ykurilov/agent-chat-core\";\nimport { readJsonBody } from \"./request-body\";\n\n/** Что приложение отдаёт для чата объекта: данные (только для сервера), название и приветствие нового чата. */\nexport interface SubjectChatSettings {\n /** JSON-объект до 16 КБ: инструментам и хукам на сервере, в браузер не уходит. Пишется только при создании чата. */\n data?: Record<string, unknown>;\n /** Название нового чата; нет — из первого сообщения человека. */\n title?: string;\n /** Первое сообщение от агента в новом чате. */\n greeting?: ChatGreeting;\n}\n\n/**\n * Решение приложения о чате объекта: настройки или отказ `{ code, message }` (человеку нельзя в этот объект). Отказ\n * узнаётся по полю `code`.\n */\nexport type SubjectChatsResolver = (input: { user: { id: string }; key: string; request: Request }) => Promise<SubjectChatSettings | TurnRefusal>;\n\n/** Отказ приложения ответом 403 `turn_refused`: код и текст безопасно показать человеку. */\nexport function turnRefusalResponse(refusal: TurnRefusal): Response {\n const { code, message } = normalizeTurnRefusal(refusal);\n return Response.json({ error: { kind: \"turn_refused\", code, message } }, { status: 403 });\n}\n\n/** `POST {base}/subject { key }` → `{ id, created }`: найти или создать чат объекта человека. */\nexport async function openSubjectChatRoute(input: {\n request: Request;\n userId: string;\n service: Pick<ChatService, \"openSubjectChat\">;\n resolve: SubjectChatsResolver;\n invalid: () => Response;\n}): Promise<Response> {\n const key = ((await readJsonBody(input.request)) as { key?: unknown } | null)?.key;\n if (typeof key !== \"string\" || key.length === 0 || key.length > SUBJECT_KEY_MAX_LENGTH) return input.invalid();\n const settings = await input.resolve({ user: { id: input.userId }, key, request: input.request });\n if (typeof settings !== \"object\" || settings === null || Array.isArray(settings)) {\n // Ошибка приложения, а не человека: обработчик ответит 500 и запишет текст в журнал.\n throw new TypeError(`subjectChats должен вернуть объект настроек или отказ { code, message }, а вернул ${settings === null ? \"null\" : Array.isArray(settings) ? \"массив\" : typeof settings}.`);\n }\n if (\"code\" in settings) return turnRefusalResponse(settings);\n const { chat, created } = await input.service.openSubjectChat({ ownerId: input.userId, key, ...settings });\n return Response.json({ id: chat.id, created });\n}\n","/** Данные нового чата из браузера: решает приложение (`ChatHandlerOptions.newChatData`). */\nexport type NewChatDataResolver = (input: { user: { id: string }; request: Request }) => Promise<Record<string, unknown> | undefined> | Record<string, unknown> | undefined;\n\n/** Ответ приложения: объект данных или `undefined`. Другое — ошибка приложения, а не человека: обработчик ответит 500. */\nexport async function resolveNewChatData(resolve: NewChatDataResolver, input: { user: { id: string }; request: Request }): Promise<Record<string, unknown> | undefined> {\n const data = await resolve(input);\n if (data === undefined) return undefined;\n if (typeof data !== \"object\" || data === null || Array.isArray(data)) {\n throw new TypeError(`newChatData должен вернуть объект данных чата или undefined, а вернул ${data === null ? \"null\" : Array.isArray(data) ? \"массив\" : typeof data}.`);\n }\n return data;\n}\n","import { publicAppState } from \"@ykurilov/agent-chat-core\";\nimport type { AgentChannel, ChatEvent, ChatTurnTrigger, HiddenStateKey, TaskView } from \"@ykurilov/agent-chat-core\";\n\n/** Как часто поток сверяется с базой, пока у чата идут задачи. */\nexport const WATCH_RECONCILE_EVERY_MS = 5000;\n\n/** Событие для вкладки: без владельца — он ей не нужен. */\nexport type WatchEvent = ChatEvent extends infer Event ? (Event extends unknown ? Omit<Event, \"ownerId\"> : never) : never;\n\nconst LIVE_TASK_STATUSES: ReadonlySet<TaskView[\"status\"]> = new Set([\"queued\", \"running\", \"awaiting_approval\"]);\n\nexport interface WatchStreamOptions {\n ownerId: string;\n chatId: string;\n /** Запрос вкладки: клиент ушёл — поток закрывается, даже если его так и не начали читать. */\n signal?: AbortSignal;\n subscribeEvents(listener: (event: ChatEvent) => void): () => void;\n /** Ключи состояния, которые не уходят вкладке в `state-changed`; нет — только служебные ключи SDK. */\n hiddenStateKey?: HiddenStateKey;\n /** Задачи чата; нет фона — сверки нет. */\n listTasks?: () => Promise<TaskView[]>;\n /** Ход, ответ которого идёт в базе, но не на этой копии сервера, с источником и каналом по истории; нет — `null`. */\n detachedTurn(): Promise<{ messageId: string; trigger: ChatTurnTrigger; channel: AgentChannel } | null>;\n onClose(): void;\n logError(message: string): void;\n}\n\n/**\n * События одного чата: ход начался, ход закончился, задачи изменились. Пока у чата идут задачи, раз в 5 с поток\n * сверяется с базой: так видны ход и задачи, которые ведёт другая копия сервера.\n */\nexport function createWatchStream(options: WatchStreamOptions): ReadableStream<WatchEvent> {\n const { ownerId, chatId, listTasks } = options;\n let closed = false;\n let timer: ReturnType<typeof setTimeout> | undefined;\n let unsubscribe: (() => void) | undefined;\n let lastTasks: string | undefined;\n const announced = new Set<string>();\n\n const close = () => {\n if (closed) return;\n closed = true;\n clearTimeout(timer);\n unsubscribe?.();\n options.signal?.removeEventListener(\"abort\", onClientGone);\n options.onClose();\n };\n let onClientGone = () => close();\n\n return new ReadableStream<WatchEvent>({\n start(controller) {\n onClientGone = () => {\n const wasOpen = !closed;\n close();\n if (wasOpen) controller.close();\n };\n const send = (event: WatchEvent) => {\n if (!closed) controller.enqueue(event);\n };\n\n /** Задачи изменились с прошлой сверки — `tasks-changed`; возвращает, идут ли ещё задачи. */\n const checkTasks = async (): Promise<boolean> => {\n const tasks = await listTasks!();\n const snapshot = JSON.stringify(tasks);\n if (lastTasks !== undefined && snapshot !== lastTasks) send({ type: \"tasks-changed\", chatId });\n lastTasks = snapshot;\n return tasks.some((task) => LIVE_TASK_STATUSES.has(task.status));\n };\n\n const tick = async () => {\n timer = undefined;\n try {\n const live = await checkTasks();\n const turn = await options.detachedTurn();\n if (turn && !announced.has(turn.messageId)) {\n announced.add(turn.messageId);\n send({ type: \"turn-started\", chatId, ...turn });\n }\n if (live) scheduleReconcile();\n } catch (error) {\n options.logError(`Наблюдение за чатом ${chatId}: сверка с базой не удалась: ${error instanceof Error ? error.message : String(error)}`);\n scheduleReconcile();\n }\n };\n\n const scheduleReconcile = () => {\n if (closed || timer !== undefined || !listTasks) return;\n timer = setTimeout(() => void tick(), WATCH_RECONCILE_EVERY_MS);\n };\n\n unsubscribe = options.subscribeEvents((event) => {\n if (event.ownerId !== ownerId || event.chatId !== chatId) return;\n // Данные чата остаются на сервере: о смене вкладка не узнаёт.\n if (event.type === \"chat-data-changed\") return;\n const { ownerId: _, ...forTab } = event;\n if (event.type === \"turn-started\") announced.add(event.messageId);\n // Закрытые ключи состояния — только серверу.\n send(forTab.type === \"state-changed\" && options.hiddenStateKey ? { ...forTab, state: publicAppState(forTab.state, options.hiddenStateKey) } : forTab);\n // Задачи изменились — сверка снова нужна, даже если до этого задач не было.\n if (event.type === \"tasks-changed\") scheduleReconcile();\n });\n if (options.signal?.aborted) {\n onClientGone();\n return;\n }\n options.signal?.addEventListener(\"abort\", onClientGone, { once: true });\n\n if (listTasks) {\n void checkTasks()\n .then((live) => {\n if (live) scheduleReconcile();\n })\n .catch((error: unknown) => {\n options.logError(`Наблюдение за чатом ${chatId}: задачи не прочитаны: ${error instanceof Error ? error.message : String(error)}`);\n // Идут ли задачи, неизвестно: сверка повторится, как после сбоя очередной сверки.\n scheduleReconcile();\n });\n }\n },\n cancel() {\n close();\n },\n });\n}\n","export { createChatHandler, createChatServer } from \"./chat-handler\";\nexport type { ChatAttachments, ChatHandler, ChatHandlerOptions, ChatServerService, HandlerErrorKind } from \"./chat-handler\";\nexport type { AccessRefusal, ResolvedUser } from \"@ykurilov/agent-chat-core\";\nexport { accessRefusedResponse, allowedUser } from \"@ykurilov/agent-chat-core\";\nexport { turnRefusalResponse } from \"./subject-chat-route\";\nexport type { SubjectChatSettings, SubjectChatsResolver } from \"./subject-chat-route\";\n"],"mappings":"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","names":["errorResponse","accessRefusedResponse","allowedUser"]}
|
|
1
|
+
{"version":3,"sources":["../src/chat-handler.ts","../src/heartbeat.ts","../src/request-body.ts","../src/memory-routes.ts","../src/subject-chat-route.ts","../src/new-chat-data.ts","../src/watch-stream.ts","../src/index.ts"],"sourcesContent":["import {\n ApprovalNotPendingError,\n AttachmentRejectedError,\n BrowserCallNotPendingError,\n ChatBusyError,\n ChatNotFoundError,\n createChatEvents,\n accessRefusedResponse,\n allowedUser,\n createChatService,\n hiddenStateKeyOf,\n createRunningAnswerRegistry,\n DuplicateMessageError,\n InvalidChatTitleError,\n InvalidMessageError,\n InvalidModelChoiceError,\n isScreenPlaceInput,\n isTabId,\n isVoiceSound,\n NothingToRetryError,\n createRequestOriginCheck,\n originRefusalHint,\n isSafeMethod,\n SpendingLimitReachedError,\n TooManyTurnsError,\n TurnRefusedError,\n ActionForbiddenError,\n ChatClosedError,\n ChatCreationRefusedError,\n turnOriginOf,\n InvalidWidgetAnswerError,\n WidgetAnswerCheckAbortedError,\n WidgetAnswerCheckTimeoutError,\n WidgetAnswerRefusedError,\n WidgetNotPendingError,\n} from \"@ykurilov/agent-chat-core\";\nimport type { NewChatGreetingResolver } from \"./new-chat-greeting\";\nimport type {\n AbandonedBurstsOptions,\n AfterTurnHandler,\n ModelChosenHandler,\n RecoverInterruptedAnswersOptions,\n ReplyChecksSetting,\n ThoughtTitlesSetting,\n StateChangedHandler,\n AgentSettings,\n BackgroundTasks,\n BeforeTurnHandler,\n BrowserCallAnswer,\n Chat,\n BeforeCreateChatHandler,\n ChatCapabilitiesHandler,\n ChatErrorKind,\n ChatService,\n ChatStorage,\n CompactionSettings,\n FlushOptions,\n IncomingUserMessage,\n MessageAttachments,\n ModelCatalog,\n ModelChoice,\n ResolvedUser,\n SpendingLimits,\n StaffActor,\n ChatInstructions,\n TurnFinishedHandler,\n TurnNotStartedHandler,\n TurnObserver,\n TurnRequestContext,\n VoiceSound,\n} from \"@ykurilov/agent-chat-core\";\nimport { assertHeartbeatInterval, createHeartbeatResponse, DEFAULT_HEARTBEAT_INTERVAL_MS } from \"./heartbeat\";\nimport { handleMemoryRequest } from \"./memory-routes\";\nimport type { ChatMemory } from \"./memory-routes\";\nimport { readJsonBody, RequestBodyTooLargeError } from \"./request-body\";\nimport { openSubjectChatRoute, turnRefusalResponse } from \"./subject-chat-route\";\nimport type { NewChatDataResolver } from \"./new-chat-data\";\nimport { resolveNewChatData } from \"./new-chat-data\";\nimport type { SubjectChatsResolver } from \"./subject-chat-route\";\nimport { createWatchStream } from \"./watch-stream\";\n\nexport type { ChatMemory, MemoryCounts, MemoryMergeRecord, MemoryResult } from \"./memory-routes\";\n\n/**\n * `busy` — в чате идёт ход; `approval_not_pending` — на этот запрос подтверждения отвечать уже нечем (решён или закрыт новым сообщением);\n * `approval_stale` — фоновая задача ждёт ответа уже на другой вопрос: ответ на старый не принят;\n * `not_pending` — браузерный вызов не ждёт ответа этой вкладки (уже отвечен, срок вышел, ход кончился, вкладка чужая);\n * `widget_not_pending` — виджет не ждёт ответа (уже отвечен, закрыт сообщением, в другой ветке, убран из настроек);\n * `invalid_answer` — ответ на виджет не прошёл схему, длиннее предела или тело запроса негодное; ничего не записано;\n * `widget_answer_refused` — приложение отказало в ответе на виджет (`checkAnswer`): `{ kind, code, message }`, ничего не записано,\n * виджет ждёт;\n * `widget_answer_cancelled` — человек нажал «Стоп», пока шла проверка ответа (`checkAnswer`): `{ kind, code, message }`, ничего не\n * записано, виджет ждёт; это не сбой;\n * `widget_check_timeout` (504) — проверка ответа (`checkAnswer`) не уложилась в `checkAnswerTimeoutMs`: `{ kind, code, message }`,\n * ничего не записано, виджет ждёт, повторить можно;\n * `too_many_turns` — у человека уже идёт столько ответов, сколько разрешено (`maxConcurrentTurnsPerUser`);\n * `forbidden_origin` — запрос, меняющий данные, пришёл с чужой страницы (`allowedOrigins`); `too_large` — тело больше 1 МиБ;\n * `turn_refused` — приложение отказало в ходе (`beforeTurn`); `access_refused` — приложение отказало в доступе (`resolveUser`);\n * `action_forbidden` — действие запрещено правами чата (`chatCapabilities`); `chat_closed` — чат закрыт (`closeChat`);\n * `chat_creation_refused` — приложение не дало завести новый чат (`beforeCreateChat`).\n * У отказов приложения одно тело: `{ kind, code, message }` (у `action_forbidden` ещё `action`).\n */\nexport type HandlerErrorKind =\n | ChatErrorKind\n | \"not_found\"\n | \"busy\"\n | \"invalid\"\n | \"spending_limit_reached\"\n | \"approval_not_pending\"\n | \"approval_stale\"\n | \"widget_not_pending\"\n | \"invalid_answer\"\n | \"widget_answer_refused\"\n | \"widget_answer_cancelled\"\n | \"widget_check_timeout\"\n | \"not_pending\"\n | \"too_many_turns\"\n | \"forbidden_origin\"\n | \"too_large\"\n | \"turn_refused\"\n | \"access_refused\"\n | \"action_forbidden\"\n | \"chat_closed\"\n | \"chat_creation_refused\";\n\n/** Вложения: приём в сообщение и сборка запроса (для ядра) плюс адреса под `/:chatId/attachments`. Даёт пакет `@ykurilov/agent-chat-attachments`. */\nexport interface ChatAttachments extends MessageAttachments {\n /** Ответ на `GET {base}/attachments/settings`. */\n settings(): Response;\n /** Всё под `/:chatId/attachments`; чат уже проверен как принадлежащий человеку. `segments` — путь после `attachments`. */\n handle(input: { request: Request; userId: string; chatId: string; segments: string[] }): Promise<Response>;\n}\n\n/** Опции службы ядра, которые сервер принимает от приложения: все, кроме тех, что сервер заводит сам. */\ntype ServiceOptionsOfServer = Omit<Parameters<typeof createChatService>[0], \"registry\" | \"chatEvents\">;\n\n/**\n * Опции сервера: все опции службы ядра (`createChatService`) плюс свои. Новая опция службы доходит сама — без правки\n * сервера; ниже повторены только поля с пояснением для сервера.\n */\nexport interface ChatHandlerOptions extends ServiceOptionsOfServer {\n /** Путь, под которым смонтирован обработчик, например `/api/chat`. */\n basePath: string;\n storage: ChatStorage;\n /**\n * Кто спрашивает: `{ id }` — человек, `null` — не вошёл (401 `invalid`). `{ refused: { code, message, status? } }` —\n * приложение отказывает человеку в доступе своими словами: на любом адресе ответ `status` (401 или 403, по умолчанию 403)\n * `{ error: { kind: \"access_refused\", code, message } }`. Код — `[a-z0-9_.-]`, 1–64 знака (иначе 500 `other` и строка\n * в `logError`); текст — до 300 знаков.\n */\n resolveUser: (request: Request) => Promise<ResolvedUser>;\n /** Выбор модели для новых чатов. Не задан — первая модель каталога у первого провайдера с ключом. */\n defaultChoice?: ModelChoice;\n catalog: ModelCatalog;\n agent: AgentSettings;\n /** Автосжатие истории: включено по умолчанию, `false` выключает. Подробнее — в настройках ядра. */\n compaction?: CompactionSettings | false;\n /** Вложения к сообщениям. Не заданы — адресов `/attachments` нет, сообщения только текстовые. */\n attachments?: ChatAttachments;\n /**\n * Лимиты расходов (пакет `@ykurilov/agent-chat-limits`). Исчерпанный лимит — ответ 429 `spending_limit_reached`\n * на отправку, повтор, переделку и правку; появляется адрес `GET {base}/:chatId/spending`. Не заданы — адреса нет.\n */\n limits?: SpendingLimits;\n /**\n * Сколько ответов одного человека могут идти одновременно во всех его чатах. Сверх этого — ответ 429\n * `too_many_turns` с полем `limit`, ничего не записано. Считается в памяти процесса. По умолчанию 3; `Infinity` — без предела.\n */\n maxConcurrentTurnsPerUser?: number;\n /**\n * Проверка приложения перед ходом: вернуть `{ code, message }` — ход не начнётся, ничего не запишется, ответ 403\n * `{ error: { kind: \"turn_refused\", code, message } }` на отправку, повтор, переделку, правку, ответ на подтверждение\n * и ответ на виджет. Подробнее — `ChatServiceOptions.beforeTurn`. Нет — ходы не проверяются.\n */\n beforeTurn?: BeforeTurnHandler;\n /**\n * Ход не начался, хотя `beforeTurn` его пропустил (чат занят, предел ходов, лимит, повтор id, неверный запрос, сбой):\n * снять резерв приложения. Ответ клиенту прежний. Подробнее — `ChatServiceOptions.turnNotStarted`.\n */\n turnNotStarted?: TurnNotStartedHandler;\n /**\n * Права человека в чате: частичный список `{ право: boolean }`, пропущенное — `true`. Запрещённое — 403\n * `{ error: { kind: \"action_forbidden\", action, code, message } }`, история отдаёт `capabilities`, экран прячет кнопки.\n * Право `attachments` закрывает и загрузку вложений (`/:chatId/attachments`, кроме GET). Подробнее —\n * `ChatServiceOptions.chatCapabilities`. Нет — всё разрешено.\n */\n chatCapabilities?: ChatCapabilitiesHandler;\n /**\n * Можно ли человеку завести новый чат (`POST {base}`; тот же хук спрашивают мессенджеры на `/new`). Отказ — 403\n * `{ error: { kind: \"chat_creation_refused\", code, message } }`, чат не создан. Подробнее —\n * `ChatServiceOptions.beforeCreateChat`. Нет — чаты заводятся без проверки.\n */\n beforeCreateChat?: BeforeCreateChatHandler;\n /**\n * Долгая память агента между чатами (пакет `@ykurilov/agent-chat-memory`): ядро добавляет её в запросы и инструменты,\n * появляются адреса `{base}/memory` для экрана памяти. Не задана — адресов нет (404), чат как раньше.\n */\n memory?: ChatMemory;\n /**\n * Список чатов человека: `GET {base}`, переименование `PATCH {base}/{id}` и удаление `DELETE {base}/{id}`.\n * Не включён — этих адресов нет (404), у приложения с одним чатом всё работает как раньше. Чаты объектов\n * (`subjectChats`) в списке не видны; `{ subjectChats: true }` — видны, с полем `subjectKey`.\n */\n chatList?: boolean | { subjectChats?: boolean };\n /**\n * Чаты объектов приложения (урок, заказ): адрес `POST {base}/subject { key }` → `{ id, created }` находит или\n * создаёт чат человека по ключу (1–200 знаков). Приложение по ключу отдаёт данные объекта (только для сервера),\n * название и приветствие нового чата — или отказ `{ code, message }` (ответ 403 `turn_refused`, чат не создан).\n * Не задано — адреса нет (404). Требует хранилища с `findChatBySubject`: без него — ошибка при создании обработчика.\n */\n subjectChats?: SubjectChatsResolver;\n /**\n * Данные нового чата, который человек создаёт из браузера (`POST {base}`): сервер решает, что записать, браузер\n * данных не присылает. JSON-объект до 16 КБ (`Chat.data`) или `undefined` — чат без данных. Видят `beforeTurn`,\n * `chatInstructions`, инструменты (`model.chat.data`) и `afterTurn` (`turn.chatData`); в браузер не уходят.\n * Вернул не объект или бросил — ответ 500, чат не создан. Чаты объектов (`subjectChats`) берут данные из своего решения.\n * Требует хранилища с `updateChatData`: без него — ошибка при создании обработчика.\n */\n newChatData?: NewChatDataResolver;\n /**\n * Приветствие нового обычного чата (`POST {base}`), после `newChatData`. `null` — без приветствия.\n * Решение до записи: исключение или неверное приветствие — 500, чат не создан. Указание запускает один ход\n * после создания (обычные права/лимиты/расходы). GET, reload и чаты объектов эту функцию не вызывают.\n * Повтор HTTP POST создаёт другой чат: гарантия — одна побудка на созданный чат, не идемпотентность POST.\n */\n newChatGreeting?: NewChatGreetingResolver;\n /**\n * Чужие страницы, которым можно менять данные чата с куками человека, например `[\"https://school.getcourse.ru\"]`,\n * когда чат встроен в чужой сайт и шлёт запросы прямо оттуда. Свою страницу вносить не нужно: запросы с неё проходят всегда.\n * Запрос, меняющий данные (не GET), с чужой страницы — ответ 403 `forbidden_origin`, ничего не записано.\n * Запросы не из браузера (без заголовков Origin и Sec-Fetch-Site) проходят: их судит `resolveUser`.\n */\n allowedOrigins?: string[];\n /** Откуда брать ключи провайдеров. По умолчанию `process.env`. */\n env?: Record<string, string | undefined>;\n /** Журнал сервера: нет ключа провайдера, непредвиденные сбои. По умолчанию `console.error`. */\n logError?: (message: string) => void;\n /** Промежуток между признаками жизни в потоках ответа. По умолчанию 10 000 мс, не меньше 1000. */\n heartbeatIntervalMs?: number;\n /** Наблюдатель хода (песочница): см. `ChatServiceOptions.turnObserver`. Нет — запрос к модели побайтно прежний. */\n turnObserver?: TurnObserver;\n /** Своя инструкция чата (песочница): см. `ChatServiceOptions.chatInstructions`. `null` — инструкция приложения. */\n chatInstructions?: ChatInstructions;\n /** Сотрудник хода по чату (песочница: копия чата человека): см. `ChatServiceOptions.staffOfChat`. */\n staffOfChat?: (chat: Chat) => StaffActor | null | undefined;\n /** Инструменты, чей результат правка ответа не меняет (файлы): см. `ChatServiceOptions.lockedToolOutputs`. */\n lockedToolOutputs?: readonly string[];\n /** Несколько служб на одном хранилище: чья брошенная пачка. См. `ChatServiceOptions.abandonedBursts`. */\n abandonedBursts?: AbandonedBurstsOptions;\n /**\n * Фоновые задачи (пакет `@ykurilov/agent-chat-background`): появляются адреса `{base}/:chatId/tasks`. Не заданы —\n * адресов задач нет (404), запрос к модели прежний.\n */\n background?: BackgroundTasks;\n /**\n * Ход закончен: `{ userId, chatId, messageId, status, modelId, providerId }`, один раз на ход при любом итоге —\n * законченном, остановленном, с ошибкой, прерванном, ждущем человека. Зовётся, когда итог уже записан; ход обработчик\n * не ждёт, его сбой уходит в `logError`. Нет — ничего не зовётся.\n */\n onTurnFinished?: TurnFinishedHandler;\n /**\n * Итог хода для приложения: ход ждёт его до `finish`, возвращённые `events` ложатся в ответ частями `data-app-event`.\n * Повторы, срок, состояние в ответе и доделка после перезапуска — как у `ChatServiceOptions.afterTurn`. Событие вне\n * хода — `service.publishAppEvent` (служба из `createChatServer`): открытая вкладка получит его через `watch`.\n */\n afterTurn?: AfterTurnHandler;\n /** Срок хука итога на все попытки, мс. По умолчанию 30 000. */\n afterTurnTimeoutMs?: number;\n /** Попыток хука итога. По умолчанию 3. */\n afterTurnAttempts?: number;\n /**\n * Состояние чата сменилось (ход, правка, «Переделать», «Повторить», выбор ветки, правка ответа): копия состояния в\n * таблицах приложения. Как у `ChatServiceOptions.onStateChanged`; открытая вкладка получает событие `state-changed`\n * через `watch`.\n */\n onStateChanged?: StateChangedHandler;\n /** Срок хука смены состояния на все попытки, мс. По умолчанию 30 000. */\n onStateChangedTimeoutMs?: number;\n /** Попыток хука смены состояния. По умолчанию 3. */\n onStateChangedAttempts?: number;\n /**\n * Человек выбрал модель или уровень в меню (`PUT {chat}/model`): после записи выбора. Подстановка выбора и\n * `service.setModel(..., { by: \"app\" })` его не зовут. Как у `ChatServiceOptions.onModelChosen`.\n */\n onModelChosen?: ModelChosenHandler;\n /**\n * Видит ли человек `GET …/models`: `false` — 403 `action_forbidden` (`action: \"modelDetails\"`). Меню не знает чата,\n * поэтому право `modelDetails` его не закрывает. Нет — меню видно всем. Как у `ChatServiceOptions.modelMenu`.\n */\n modelMenu?: (input: { userId: string }) => boolean | Promise<boolean>;\n /**\n * Закрытые верхние ключи состояния чата: не уходят в браузер ни в истории, ни в потоке хода, ни в `state-changed`\n * адреса `watch`. Сервер видит их как обычно. Как у `ChatServiceOptions.privateStateKeys`.\n */\n privateStateKeys?: readonly string[];\n /** Проверки готового ответа перед записью. Как у `ChatServiceOptions.replyChecks`. */\n replyChecks?: ReplyChecksSetting;\n /** Заголовки раздумий вместо «Думал 3 сек». Как у `ChatServiceOptions.thoughtTitles`. */\n thoughtTitles?: ThoughtTitlesSetting;\n /** Часы службы: тесты и песочница подменяют время целиком. Как у `ChatServiceOptions.now`. */\n now?: () => Date;\n}\n\nexport interface ChatHandler {\n basePath: string;\n fetch(request: Request): Promise<Response>;\n /**\n * Помечает «прерванными» ответы, оборванные прошлым запуском сервера, и запускает регулярную проверку брошенных\n * ответов. Вызывать при старте, до приёма запросов; в Next.js — один раз сразу после `createChatHandler`: свои\n * живые ответы проверка не трогает. Без вызова оборванные ответы остаются «идущими». Возвращает число помеченных.\n * `{ soleServer: true }` — копия сервера на этой базе одна: оборванные ответы прерываются сразу, а не через 15–20 с.\n * Только при старте процесса: вызов позже прервёт ход, который как раз запускается. Нельзя с несколькими копиями\n * сервера на одной базе и с несколькими обработчиками в одном процессе (dev-режим Next.js): прервутся живые ответы.\n */\n recoverInterruptedAnswers(options?: RecoverInterruptedAnswersOptions): Promise<number>;\n /**\n * Штатная остановка: после того как сервер перестал принимать запросы, до закрытия хранилища. Ждёт идущие ответы до\n * `waitForAnswersMs` (по умолчанию 0), не успевшие останавливает, как «Стоп», с записью; возвращается, когда всё\n * записано и хуки итога закончены. Подробнее — `ChatService.flush`.\n */\n flush(options?: FlushOptions): Promise<void>;\n /**\n * Открыт ли у чата этого владельца поток `GET /:chatId/watch` (вкладка с чатом). Необязателен: обёртки над сервером\n * (песочница) реализуют этот тип без него; мессенджеры берут его у `ChatServerService`.\n */\n hasWatchers?(ownerId: string, chatId: string): boolean;\n}\n\n/** Служба чата сервера: служба ядра плюс то, что знает только сервер, — открытые вкладки и задачи фона. */\nexport interface ChatServerService extends ChatService {\n /** Открыт ли у чата этого владельца поток `GET /:chatId/watch`. */\n hasWatchers(ownerId: string, chatId: string): boolean;\n /** Задачи фона; нет фона — нет и этого поля. */\n tasks?: { list: BackgroundTasks[\"listForChat\"] } & Pick<BackgroundTasks, \"cancel\" | \"answerApproval\">;\n}\n\n/** Значение из адреса для строки журнала: с управляющими символами — в кавычках и экранировано, чтобы не подделать строки. */\nconst logValue = (value: string): string => (/[\\p{Cc}\\u2028\\u2029]/u.test(value) ? JSON.stringify(value) : value);\n\n/** Поля сервера, которых у службы нет: всё остальное уходит в `createChatService` как есть. */\nconst SERVER_ONLY_OPTIONS = {\n basePath: true,\n resolveUser: true,\n chatList: true,\n subjectChats: true,\n newChatData: true,\n newChatGreeting: true,\n allowedOrigins: true,\n heartbeatIntervalMs: true,\n} satisfies Record<Exclude<keyof ChatHandlerOptions, keyof ServiceOptionsOfServer>, true>;\n\n/** Опции службы из опций сервера: без полей сервера и без пустых (`undefined` — как не заданное). */\nfunction serviceOptionsOf(options: ChatHandlerOptions): ServiceOptionsOfServer {\n return Object.fromEntries(\n Object.entries(options).filter(([key, value]) => value !== undefined && !Object.hasOwn(SERVER_ONLY_OPTIONS, key)),\n ) as unknown as ServiceOptionsOfServer;\n}\n\n/** HTTP-обработчик чата на стандартных `Request`/`Response` (contracts/http-api.md). */\nexport function createChatHandler(options: ChatHandlerOptions): ChatHandler {\n return createChatServer(options).handler;\n}\n\n/**\n * Обработчик чата и его служба: приложению (песочнице) служба нужна для своих адресов поверх тех же чатов —\n * в одном процессе с обработчиком, с общим реестром идущих ответов.\n */\nexport function createChatServer(options: ChatHandlerOptions): { handler: ChatHandler; service: ChatServerService } {\n const basePath = options.basePath.replace(/\\/+$/, \"\");\n const heartbeatIntervalMs = options.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS;\n assertHeartbeatInterval(heartbeatIntervalMs);\n const streamResponse = (stream: ReadableStream<unknown>) =>\n createHeartbeatResponse({ stream, heartbeatIntervalMs });\n const logError = options.logError ?? ((message: string) => console.error(message));\n const allowsOrigin = createRequestOriginCheck({\n allowedOrigins: options.allowedOrigins,\n onRefused: (refusal) => logError(`Чат: ${originRefusalHint(refusal)}`),\n });\n if (options.newChatData && !options.storage.updateChatData) {\n throw new Error(\"newChatData требует хранилища с updateChatData: хранилище приложения его не умеет.\");\n }\n if (options.subjectChats && !options.storage.findChatBySubject) {\n throw new Error(\"subjectChats требует хранилища с findChatBySubject: хранилище приложения его не умеет.\");\n }\n const service = createChatService({\n ...serviceOptionsOf(options),\n registry: createRunningAnswerRegistry(),\n logError,\n chatEvents: createChatEvents((error) => logError(`События чата: сбой слушателя: ${describe(error)}`)),\n });\n /** Ключи состояния, которые не уходят вкладке (служба уже проверила имена). */\n const hiddenStateKey = hiddenStateKeyOf(options.privateStateKeys);\n const background = options.background;\n /** Ответ человека на вопрос фоновой задачи продолжает работу модели: приложение проверяет его, как любой ход. */\n async function answerTaskApproval(ownerId: string, chatId: string, taskId: string, approvalId: string, decision: \"allow\" | \"deny\") {\n await service.assertTurnAllowed(ownerId, chatId, \"approval\");\n return background!.answerApproval(ownerId, chatId, taskId, approvalId, decision);\n }\n /** Открытые потоки `watch` по владельцу и чату. */\n const watchers = new Map<string, number>();\n const watcherKey = (ownerId: string, chatId: string) => JSON.stringify([ownerId, chatId]);\n const hasWatchers = (ownerId: string, chatId: string) => (watchers.get(watcherKey(ownerId, chatId)) ?? 0) > 0;\n\n /** Подписчик считается, пока поток открыт; снимается при отмене потока, уходе клиента и сбое открытия — ровно один раз. */\n function watchResponse(request: Request, userId: string, chatId: string): Response {\n const key = watcherKey(userId, chatId);\n watchers.set(key, (watchers.get(key) ?? 0) + 1);\n let counted = true;\n const uncount = () => {\n if (!counted) return;\n counted = false;\n const left = (watchers.get(key) ?? 1) - 1;\n if (left > 0) watchers.set(key, left);\n else watchers.delete(key);\n };\n try {\n const stream = createWatchStream({\n ownerId: userId,\n chatId,\n signal: request.signal,\n subscribeEvents: (listener) => service.subscribeEvents(listener),\n hiddenStateKey,\n ...(background && { listTasks: () => background.listForChat(userId, chatId) }),\n async detachedTurn() {\n const history = await service.getHistory(userId, chatId);\n const answer = history.messages.at(-1);\n return history.detachedAnswer && answer ? { messageId: answer.id, ...turnOriginOf(history.messages) } : null;\n },\n onClose: uncount,\n logError,\n });\n return streamResponse(stream);\n } catch (error) {\n uncount();\n throw error;\n }\n }\n\n async function route(request: Request, userId: string, segments: string[]): Promise<Response> {\n const [chatId, action, ...rest] = segments;\n const method = request.method.toUpperCase();\n\n if (options.attachments) {\n // Идентификаторы чатов — UUID, поэтому путь `attachments/settings` с чатом не пересекается.\n if (chatId === \"attachments\" && action === \"settings\" && rest.length === 0 && method === \"GET\") {\n return options.attachments.settings();\n }\n if (chatId !== undefined && action === \"attachments\") {\n await service.assertOwnChat(userId, chatId);\n // Загрузка и удаление — под правом `attachments`; файлы уже отправленных сообщений читаются всегда.\n if (method !== \"GET\" && method !== \"HEAD\") await service.assertCapability(userId, chatId, \"attachments\");\n return options.attachments.handle({ request, userId, chatId, segments: rest });\n }\n }\n\n // Идентификаторы чатов — UUID, поэтому путь `memory` с чатом не пересекается.\n if (options.memory && chatId === \"memory\") {\n return handleMemoryRequest({ memory: options.memory, request, userId, segments: segments.slice(1) });\n }\n\n if (chatId !== undefined && action === \"approvals\" && rest.length === 1 && method === \"POST\") {\n const body = await readJsonBody(request);\n const decision = (body as { decision?: unknown } | null)?.decision;\n if (decision !== \"allow\" && decision !== \"deny\" && decision !== \"always\") return errorResponse(400, \"invalid\");\n const page = parsePage(body);\n if (page === \"invalid\") return errorResponse(400, \"invalid\");\n return Response.json(await service.answerApproval(userId, chatId, rest[0]!, decision, page));\n }\n\n if (chatId !== undefined && action === \"widgets\" && rest.length === 1 && method === \"POST\") {\n const toolCallId = rest[0]!;\n const body = await readWidgetAnswerBody(request);\n if (!body) {\n logError(`[виджет] отказ invalid_answer чат=${logValue(chatId)} вызов=${logValue(toolCallId)}`);\n return errorResponse(400, \"invalid_answer\");\n }\n const page = parsePage(body);\n if (page === \"invalid\") return errorResponse(400, \"invalid\");\n return Response.json(await service.answerWidget({ chatId, ownerId: userId, toolCallId, answer: body.answer, ...(page && { page }) }));\n }\n\n if (chatId !== undefined && action === \"browser-tools\" && rest.length === 1 && method === \"POST\") {\n const answer = parseBrowserCallAnswer(await readJsonBody(request, MAX_BROWSER_CALL_BODY_BYTES));\n if (!answer) return errorResponse(400, \"invalid\");\n await service.answerBrowserCall(userId, chatId, rest[0]!, answer);\n return new Response(null, { status: 204 });\n }\n\n if (chatId !== undefined && action === \"watch\" && rest.length === 0 && method === \"GET\") {\n await service.assertOwnChat(userId, chatId);\n return watchResponse(request, userId, chatId);\n }\n\n if (background && chatId !== undefined && action === \"tasks\") {\n const [taskId, taskAction, ...extra] = rest;\n if (taskId === undefined && method === \"GET\") {\n await service.assertOwnChat(userId, chatId);\n return Response.json(await background.listForChat(userId, chatId));\n }\n if (taskId !== undefined && taskAction === \"cancel\" && extra.length === 0 && method === \"POST\") {\n await service.assertOwnChat(userId, chatId);\n const status = await background.cancel(userId, chatId, taskId);\n return status === \"not_found\" ? errorResponse(404, \"not_found\") : Response.json({ status });\n }\n if (taskId !== undefined && taskAction === \"approval\" && extra.length === 0 && method === \"POST\") {\n await service.assertOwnChat(userId, chatId);\n const body = (await readJsonBody(request)) as { decision?: unknown; approvalId?: unknown } | null;\n const decision = body?.decision;\n const approvalId = body?.approvalId;\n if ((decision !== \"allow\" && decision !== \"deny\") || typeof approvalId !== \"string\" || approvalId === \"\") return errorResponse(400, \"invalid\");\n const status = await answerTaskApproval(userId, chatId, taskId, approvalId, decision);\n if (status === \"not_found\") return errorResponse(409, \"approval_not_pending\");\n if (status === \"stale\") return errorResponse(409, \"approval_stale\");\n return Response.json({ status });\n }\n }\n\n if (rest.length > 0) return errorResponse(404, \"not_found\");\n\n // Идентификаторы чатов — UUID, поэтому путь `subject` с чатом не пересекается.\n if (options.subjectChats && chatId === \"subject\" && action === undefined && method === \"POST\") {\n return openSubjectChatRoute({ request, userId, service, resolve: options.subjectChats, invalid: () => errorResponse(400, \"invalid\") });\n }\n\n if (chatId === undefined) {\n if (method === \"GET\" && options.chatList) return Response.json({ chats: await service.listChats(userId, chatListOptions(options.chatList)) });\n if (method !== \"POST\") return errorResponse(404, \"not_found\");\n const data = options.newChatData ? await resolveNewChatData(options.newChatData, { user: { id: userId }, request }) : undefined;\n const greeting = options.newChatGreeting ? await options.newChatGreeting({ userId, chatData: structuredClone(data) }) : null;\n if (greeting !== null && (typeof greeting !== \"object\" || Array.isArray(greeting))) {\n throw new TypeError(\"newChatGreeting должен вернуть приветствие или null.\");\n }\n const chat = await service.createChat(userId, { ...(data !== undefined && { data }), ...(greeting !== null && { greeting }) });\n return Response.json({ id: chat.id }, { status: 201 });\n }\n\n // Идентификаторы чатов — UUID, поэтому путь `models` с чатом не пересекается.\n if (chatId === \"models\" && action === undefined && method === \"GET\") {\n return Response.json(await service.getModelMenu(userId));\n }\n\n if (action === undefined && method === \"GET\") {\n // `backgroundTasks` — заявка клиенту: адрес `{chat}/tasks` есть, только если фон подключён.\n return Response.json({ ...(await service.getHistory(userId, chatId)), backgroundTasks: background !== undefined });\n }\n\n if (action === undefined && method === \"PATCH\" && options.chatList) {\n const title = (await readJsonBody(request) as { title?: unknown } | null)?.title;\n if (typeof title !== \"string\") return errorResponse(400, \"invalid\");\n return Response.json({ id: chatId, title: await service.renameChat(userId, chatId, title) });\n }\n\n if (action === undefined && method === \"DELETE\" && options.chatList) {\n await service.deleteChat(userId, chatId);\n return new Response(null, { status: 204 });\n }\n\n if (action === \"compactions\" && method === \"GET\") {\n return Response.json(await service.getCompactions(userId, chatId));\n }\n\n if (action === \"spending\" && method === \"GET\" && options.limits) {\n return Response.json(await service.getSpendingStatus(userId, chatId));\n }\n\n if (action === \"branches\" && method === \"GET\") {\n return Response.json(await service.getBranchForks(userId, chatId));\n }\n\n if (action === \"branch\" && method === \"POST\") {\n const messageId = parseMessageId(await readJsonBody(request));\n if (!messageId) return errorResponse(400, \"invalid\");\n return Response.json(await service.selectBranch(userId, chatId, messageId));\n }\n\n if (action === \"regenerate\" && method === \"POST\") {\n const body = await readJsonBody(request);\n const messageId = parseMessageId(body);\n if (!messageId) return errorResponse(400, \"invalid\");\n const page = parsePage(body);\n if (page === \"invalid\") return errorResponse(400, \"invalid\");\n return streamResponse(await service.regenerate(userId, chatId, messageId, page));\n }\n\n if (action === \"edit\" && method === \"POST\") {\n const body = (await readJsonBody(request)) as { editedMessageId?: unknown; message?: IncomingUserMessage; voiceMode?: unknown; voiceSounds?: unknown } | null;\n const { editedMessageId, message } = body ?? {};\n if (typeof editedMessageId !== \"string\" || !message || typeof message !== \"object\") return errorResponse(400, \"invalid\");\n // Продолжение голосовой фразы (склейка): признак как у отправки; не булево — отказ, ничего не записано.\n if (body?.voiceMode !== undefined && typeof body.voiceMode !== \"boolean\") return errorResponse(400, \"invalid\");\n const voiceSounds = parseVoiceSounds(body?.voiceSounds);\n if (voiceSounds === \"invalid\") return errorResponse(400, \"invalid\");\n const page = parsePage(body);\n if (page === \"invalid\") return errorResponse(400, \"invalid\");\n return streamResponse(\n await service.editMessage(userId, chatId, editedMessageId, message, { voiceMode: body?.voiceMode === true, voiceSounds, ...(page && { page }) }),\n );\n }\n\n if (action === \"model\" && method === \"PUT\") {\n const choice = parseModelChoice(await readJsonBody(request));\n if (!choice) return errorResponse(400, \"invalid\");\n return Response.json(await service.setModel(userId, chatId, choice));\n }\n\n if (action === \"messages\" && method === \"POST\") {\n const body = (await readJsonBody(request)) as { message?: IncomingUserMessage; voiceMode?: unknown; voiceSounds?: unknown } | null;\n const message = body?.message;\n if (!message || typeof message !== \"object\") return errorResponse(400, \"invalid\");\n // Признак голосового режима: не булево — отказ, ничего не записано.\n if (body.voiceMode !== undefined && typeof body.voiceMode !== \"boolean\") return errorResponse(400, \"invalid\");\n const voiceSounds = parseVoiceSounds(body.voiceSounds);\n if (voiceSounds === \"invalid\") return errorResponse(400, \"invalid\");\n const page = parsePage(body);\n if (page === \"invalid\") return errorResponse(400, \"invalid\");\n // Поток не привязан к request.signal: обрыв соединения только отписывает слушателя.\n const stream = await service.sendMessage(userId, chatId, message, { voiceMode: body.voiceMode === true, voiceSounds, ...(page && { page }) });\n return streamResponse(stream);\n }\n\n if (action === \"steer\" && method === \"POST\") {\n const body = (await readJsonBody(request)) as { message?: IncomingUserMessage } | null;\n const message = body?.message;\n if (!message || typeof message !== \"object\") return errorResponse(400, \"invalid\");\n const page = parsePage(body);\n if (page === \"invalid\") return errorResponse(400, \"invalid\");\n // Поправка к идущему ответу или начало хода; ответ на неё — в потоке GET stream.\n return Response.json(await service.steer(userId, chatId, message, page && { page }), { status: 202 });\n }\n\n if (action === \"retry\" && method === \"POST\") {\n const page = parsePage(await readJsonBody(request));\n if (page === \"invalid\") return errorResponse(400, \"invalid\");\n const stream = await service.retry(userId, chatId, page);\n return streamResponse(stream);\n }\n\n if (action === \"stream\" && method === \"GET\") {\n // Как и у отправки, поток не привязан к request.signal: обрыв только отписывает слушателя.\n const stream = await service.subscribe(userId, chatId);\n if (!stream) return new Response(null, { status: 204 });\n return streamResponse(stream);\n }\n\n if (action === \"stop\" && method === \"POST\") {\n const stopped = await service.stop(userId, chatId);\n return new Response(null, { status: stopped ? 202 : 204 });\n }\n\n return errorResponse(404, \"not_found\");\n }\n\n const handler: ChatHandler = {\n basePath,\n recoverInterruptedAnswers: (recoverOptions) => service.recoverInterruptedAnswers(recoverOptions),\n flush: (flushOptions) => service.flush(flushOptions),\n hasWatchers,\n async fetch(request) {\n const segments = pathSegments(new URL(request.url).pathname, basePath);\n if (!segments) return errorResponse(404, \"not_found\");\n if (!isSafeMethod(request.method) && !allowsOrigin(request)) return errorResponse(403, \"forbidden_origin\");\n try {\n const resolved = await options.resolveUser(request);\n const refused = accessRefusedResponse(resolved);\n if (refused) return refused;\n const user = allowedUser(resolved);\n if (!user) return errorResponse(401, \"invalid\");\n return await route(request, user.id, segments);\n } catch (error) {\n const response = errorToResponse(error);\n if (response.status >= 500) logError(`Чат: сбой на ${request.method} ${new URL(request.url).pathname}: ${describe(error)}`);\n return response;\n }\n },\n };\n const serverService: ChatServerService = Object.assign(service, {\n hasWatchers,\n ...(background && { tasks: { list: background.listForChat.bind(background), cancel: background.cancel.bind(background), answerApproval: answerTaskApproval } }),\n });\n return { handler, service: serverService };\n}\n\nfunction pathSegments(pathname: string, basePath: string): string[] | null {\n if (pathname !== basePath && !pathname.startsWith(`${basePath}/`)) return null;\n try {\n return pathname\n .slice(basePath.length)\n .split(\"/\")\n .filter((segment) => segment.length > 0)\n .map((segment) => decodeURIComponent(segment));\n } catch {\n // Битая кодировка (например, `%E0`): такого чата не бывает.\n return null;\n }\n}\n\n/** Звуки человека из голосового режима: не передан — `undefined`; не список или вид вне закрытого списка — `\"invalid\"`. */\nfunction parseVoiceSounds(value: unknown): VoiceSound[] | undefined | \"invalid\" {\n if (value === undefined) return undefined;\n if (!Array.isArray(value) || !value.every(isVoiceSound)) return \"invalid\";\n return value;\n}\n\n/**\n * Место на экране, номер вкладки и часовой пояс человека из тела запроса хода. Нет ничего — `undefined` (как до 021);\n * неверная форма места или вкладки — `\"invalid\"`, запрос отклоняется до любой записи. Пояс-строку проверяет ядро:\n * не настоящий пояс — дата по запасному поясу, не ошибка; пояс не строкой не берётся.\n */\nfunction parsePage(body: unknown): TurnRequestContext | undefined | \"invalid\" {\n const { place, tabId, timeZone } = (typeof body === \"object\" && body !== null ? body : {}) as Record<string, unknown>;\n const zone = typeof timeZone === \"string\" ? timeZone : undefined;\n if (place === undefined && tabId === undefined && zone === undefined) return undefined;\n if (place !== undefined && !isScreenPlaceInput(place)) return \"invalid\";\n if (tabId !== undefined && !isTabId(tabId)) return \"invalid\";\n return { ...(place !== undefined && { place }), ...(tabId !== undefined && { tabId }), ...(zone !== undefined && { timeZone: zone }) };\n}\n\n/** Ответ вкладки: `{ tabId, output }` или `{ tabId, error: строка }`, ровно одно из двух; иначе `null`. */\nfunction parseBrowserCallAnswer(body: unknown): BrowserCallAnswer | null {\n if (typeof body !== \"object\" || body === null || Array.isArray(body)) return null;\n const { tabId } = body as { tabId?: unknown };\n if (!isTabId(tabId)) return null;\n const hasOutput = Object.hasOwn(body, \"output\");\n const hasError = Object.hasOwn(body, \"error\");\n if (hasOutput === hasError) return null;\n if (hasOutput) return { tabId, output: (body as { output: unknown }).output };\n const { error } = body as { error: unknown };\n return typeof error === \"string\" ? { tabId, error } : null;\n}\n\nfunction parseMessageId(body: unknown): string | null {\n const { messageId } = (body ?? {}) as Record<string, unknown>;\n return typeof messageId === \"string\" && messageId.length > 0 ? messageId : null;\n}\n\nfunction parseModelChoice(body: unknown): ModelChoice | null {\n const { modelId, providerId, reasoning } = (body ?? {}) as Record<string, unknown>;\n if (typeof modelId !== \"string\" || typeof providerId !== \"string\") return null;\n if (reasoning !== undefined && typeof reasoning !== \"string\") return null;\n return { modelId, providerId, ...(reasoning !== undefined && { reasoning }) };\n}\n\n/** Ответ вкладки больше этого не читается: модели и так уходит не больше 16 КБ результата. */\nconst MAX_BROWSER_CALL_BODY_BYTES = 64 * 1024;\n/** Тело ответа на виджет: сам ответ не длиннее 16 КБ (его меряет служба), остальное — место на экране. */\nconst MAX_WIDGET_BODY_BYTES = 64 * 1024;\n\n/** Тело `{ answer, ...место на экране }`; без `answer`, не JSON или длиннее предела — `null`. */\nasync function readWidgetAnswerBody(request: Request): Promise<{ answer: unknown } | null> {\n try {\n const body = await readJsonBody(request, MAX_WIDGET_BODY_BYTES);\n return typeof body === \"object\" && body !== null && !Array.isArray(body) && Object.hasOwn(body, \"answer\") ? (body as { answer: unknown }) : null;\n } catch (error) {\n if (error instanceof RequestBodyTooLargeError) return null;\n throw error;\n }\n}\n\n/** Какие чаты в списке: чаты объектов — только при `chatList: { subjectChats: true }`. */\nfunction chatListOptions(chatList: NonNullable<ChatHandlerOptions[\"chatList\"]>): { subjects?: \"include\" } | undefined {\n return typeof chatList === \"object\" && chatList.subjectChats ? { subjects: \"include\" } : undefined;\n}\n\nfunction errorResponse(status: number, kind: HandlerErrorKind): Response {\n return Response.json({ error: { kind } }, { status });\n}\n\nfunction describe(error: unknown): string {\n return error instanceof Error ? `${error.name}: ${error.message}` : String(error);\n}\n\nfunction errorToResponse(error: unknown): Response {\n if (error instanceof RequestBodyTooLargeError) return errorResponse(413, \"too_large\");\n if (error instanceof ChatNotFoundError) return errorResponse(404, \"not_found\");\n if (error instanceof InvalidChatTitleError) return errorResponse(400, \"invalid\");\n if (error instanceof InvalidMessageError) return errorResponse(400, \"invalid\");\n if (error instanceof AttachmentRejectedError) return Response.json({ error: { kind: error.kind } }, { status: 400 });\n if (error instanceof InvalidModelChoiceError) return errorResponse(400, \"invalid\");\n if (error instanceof SpendingLimitReachedError) {\n const { scope, period, resetsAt, timeZone, reason, windowId, hours, unit } = error;\n const details = { ...(timeZone && { timeZone }), ...(reason && { reason }), ...(windowId && { windowId }), ...(hours !== undefined && { hours }), ...(unit && { unit }) };\n return Response.json({ error: { kind: \"spending_limit_reached\", scope, period, resetsAt, ...details } }, { status: 429 });\n }\n if (error instanceof TurnRefusedError) return turnRefusalResponse(error.refusal);\n if (error instanceof ActionForbiddenError) {\n return Response.json({ error: { kind: \"action_forbidden\", action: error.action, code: error.action, message: error.message } }, { status: 403 });\n }\n if (error instanceof ChatClosedError) return Response.json({ error: { kind: \"chat_closed\", code: error.code, message: error.message } }, { status: 409 });\n if (error instanceof ChatCreationRefusedError) {\n return Response.json({ error: { kind: \"chat_creation_refused\", code: error.code, message: error.message } }, { status: 403 });\n }\n if (error instanceof TooManyTurnsError) return Response.json({ error: { kind: \"too_many_turns\", limit: error.limit } }, { status: 429 });\n if (error instanceof ChatBusyError) return errorResponse(409, \"busy\");\n if (error instanceof DuplicateMessageError) return errorResponse(409, \"invalid\");\n if (error instanceof NothingToRetryError) return errorResponse(409, \"invalid\");\n if (error instanceof ApprovalNotPendingError) return errorResponse(409, \"approval_not_pending\");\n if (error instanceof BrowserCallNotPendingError) return errorResponse(409, \"not_pending\");\n if (error instanceof WidgetNotPendingError) return errorResponse(409, \"widget_not_pending\");\n if (error instanceof InvalidWidgetAnswerError) return errorResponse(400, \"invalid_answer\");\n if (error instanceof WidgetAnswerRefusedError) {\n return Response.json({ error: { kind: \"widget_answer_refused\", code: \"widget_answer_refused\", message: error.message } }, { status: 409 });\n }\n if (error instanceof WidgetAnswerCheckAbortedError) {\n return Response.json({ error: { kind: \"widget_answer_cancelled\", code: \"widget_answer_cancelled\", message: error.message } }, { status: 409 });\n }\n if (error instanceof WidgetAnswerCheckTimeoutError) {\n return Response.json({ error: { kind: \"widget_check_timeout\", code: \"widget_check_timeout\", message: error.message } }, { status: 504 });\n }\n return errorResponse(500, \"other\");\n}\n","import { JsonToSseTransformStream, UI_MESSAGE_STREAM_HEADERS } from \"ai\";\n\n/** Заголовок, по которому вкладка узнаёт, как часто сервер шлёт признак жизни. */\nexport const HEARTBEAT_HEADER = \"x-agent-chat-heartbeat-ms\";\nexport const MIN_HEARTBEAT_INTERVAL_MS = 1000;\nexport const DEFAULT_HEARTBEAT_INTERVAL_MS = 10_000;\n\nconst PING = new TextEncoder().encode(\": ping\\n\\n\");\n\n/**\n * Ответ-поток из частей UI-сообщения с признаком жизни: SSE-комментарий `: ping` сразу и потом\n * каждые `heartbeatIntervalMs`. События приходят целиком (по одному на кусок), а пинг встаёт\n * только между ними. Клиент SDK комментарии пропускает, поэтому в сообщения и хранилище они\n * не попадают.\n */\nexport function createHeartbeatResponse(options: {\n stream: ReadableStream<unknown>;\n heartbeatIntervalMs: number;\n}): Response {\n const { stream, heartbeatIntervalMs } = options;\n assertHeartbeatInterval(heartbeatIntervalMs);\n const events = stream.pipeThrough(new JsonToSseTransformStream()).getReader();\n const encoder = new TextEncoder();\n let timer: ReturnType<typeof setInterval> | undefined;\n let cancelled = false;\n const stopTimer = () => {\n if (timer !== undefined) clearInterval(timer);\n timer = undefined;\n };\n const body = new ReadableStream<Uint8Array>({\n start(controller) {\n controller.enqueue(PING);\n timer = setInterval(() => controller.enqueue(PING), heartbeatIntervalMs);\n },\n async pull(controller) {\n try {\n const { done, value } = await events.read();\n // Читатель ушёл, пока чтение висело: поток уже отменён, закрывать и писать в него нельзя.\n if (cancelled) return;\n if (done) {\n stopTimer();\n controller.close();\n return;\n }\n controller.enqueue(encoder.encode(value));\n } catch (error) {\n stopTimer();\n throw error;\n }\n },\n cancel(reason) {\n cancelled = true;\n stopTimer();\n return events.cancel(reason);\n },\n });\n return new Response(body, {\n headers: { ...UI_MESSAGE_STREAM_HEADERS, [HEARTBEAT_HEADER]: String(heartbeatIntervalMs) },\n });\n}\n\nexport function assertHeartbeatInterval(heartbeatIntervalMs: number): void {\n if (!Number.isFinite(heartbeatIntervalMs) || heartbeatIntervalMs < MIN_HEARTBEAT_INTERVAL_MS) {\n throw new RangeError(\n `heartbeatIntervalMs должен быть не меньше ${MIN_HEARTBEAT_INTERVAL_MS} мс, получено ${heartbeatIntervalMs}`,\n );\n }\n}\n","/** Больше этого тело JSON не читается: самое длинное сообщение (100 000 знаков) с местом на экране занимает меньше 0,5 МиБ. */\nexport const MAX_JSON_BODY_BYTES = 1024 * 1024;\n\n/** Тело запроса длиннее предела: оно не дочитано и не разобрано, ответ — 413. */\nexport class RequestBodyTooLargeError extends Error {\n constructor(maxBytes: number) {\n super(`Тело запроса больше ${maxBytes} байт`);\n this.name = \"RequestBodyTooLargeError\";\n }\n}\n\n/**\n * JSON тела не длиннее `maxBytes`: заявленная длина больше — тело не читается, поток длиннее — чтение обрывается;\n * тогда `RequestBodyTooLargeError`. Нет тела или не JSON — `null`.\n */\nexport async function readJsonBody(request: Request, maxBytes = MAX_JSON_BODY_BYTES): Promise<unknown> {\n if (Number(request.headers.get(\"content-length\")) > maxBytes) throw new RequestBodyTooLargeError(maxBytes);\n if (!request.body) return null;\n const reader = request.body.getReader();\n const chunks: Uint8Array[] = [];\n let size = 0;\n try {\n for (let read = await reader.read(); !read.done; read = await reader.read()) {\n size += read.value.byteLength;\n if (size > maxBytes) {\n void reader.cancel().catch(() => undefined);\n throw new RequestBodyTooLargeError(maxBytes);\n }\n chunks.push(read.value);\n }\n } catch (error) {\n if (error instanceof RequestBodyTooLargeError) throw error;\n return null;\n }\n const bytes = new Uint8Array(size);\n let offset = 0;\n for (const chunk of chunks) {\n bytes.set(chunk, offset);\n offset += chunk.byteLength;\n }\n try {\n return JSON.parse(new TextDecoder().decode(bytes));\n } catch {\n return null;\n }\n}\n","import type { LongTermMemory, MemoryError, MemoryErrorKind, MemoryMergeRevertResult, MemoryNote } from \"@ykurilov/agent-chat-core\";\nimport { readJsonBody } from \"./request-body\";\n\n/** Исход действия с памятью: запись или ошибка-ответ. То же, что у пакета `@ykurilov/agent-chat-memory`. */\nexport type MemoryResult<T> = { ok: true; value: T } | { ok: false; error: MemoryError };\n\n/** Сколько записей человека, какой предел и сколько из них агент видит в каждом запросе. */\nexport interface MemoryCounts {\n total: number;\n limit: number;\n shown: number;\n}\n\n/** Слияние одного запуска: что стало (`result`) вместо каких записей (`originals`). Как у пакета `@ykurilov/agent-chat-memory`. */\nexport interface MemoryMergeRecord {\n runId: string;\n finishedAt: string;\n revertible: boolean;\n groups: { result: MemoryNote; originals: MemoryNote[] }[];\n}\n\n/**\n * Долгая память вместе с действиями для адресов `/memory`. Даёт пакет `@ykurilov/agent-chat-memory`.\n * `listMerges` и `revertMerge` есть не у всякой памяти: без них адреса `/memory/merges` отвечают 404.\n */\nexport interface ChatMemory extends LongTermMemory {\n list(ownerId: string): Promise<{ notes: MemoryNote[]; counts: MemoryCounts }>;\n create(ownerId: string, input: unknown): Promise<MemoryResult<MemoryNote>>;\n update(ownerId: string, id: string, version: number, input: unknown): Promise<MemoryResult<MemoryNote>>;\n remove(ownerId: string, id: string, version: number): Promise<MemoryResult<{ deleted: string }>>;\n clear(ownerId: string): Promise<number>;\n listMerges?(ownerId: string, limit?: number): Promise<MemoryMergeRecord[]>;\n revertMerge?(ownerId: string, runId: string): Promise<MemoryMergeRevertResult>;\n}\n\nconst statusOfKind: Record<MemoryErrorKind, number> = {\n invalid: 400,\n not_found: 404,\n stale_version: 409,\n duplicate: 409,\n limit_reached: 409,\n};\n\nconst errorResponse = (kind: MemoryErrorKind) => Response.json({ error: { kind } }, { status: statusOfKind[kind] });\n\n/** Запись для экрана: только поля договора, всё служебное хранилища остаётся внутри. */\nfunction publicNote({ id, categories, content, version, createdAt, updatedAt }: MemoryNote): MemoryNote {\n return { id, categories, content, version, createdAt, updatedAt };\n}\n\n/** Склейки для экрана: записи только с полями договора (оригиналы без служебного `mergedInto`). */\nfunction publicMerge({ runId, finishedAt, revertible, groups }: MemoryMergeRecord) {\n return {\n runId,\n finishedAt,\n revertible,\n groups: groups.map(({ result, originals }) => ({ result: publicNote(result), originals: originals.map(publicNote) })),\n };\n}\n\nconst isVersion = (value: unknown): value is number => typeof value === \"number\" && Number.isInteger(value) && value >= 1;\n\n/** @throws RequestBodyTooLargeError тело больше 1 МиБ. */\nasync function readBody(request: Request): Promise<Record<string, unknown> | null> {\n const body = await readJsonBody(request);\n return typeof body === \"object\" && body !== null && !Array.isArray(body) ? (body as Record<string, unknown>) : null;\n}\n\n/** Разделы и текст из тела запроса; всё остальное (`id`, `version`, `ownerId`…) до памяти не доходит. */\nconst noteFrom = (body: Record<string, unknown> | null) => ({ categories: body?.categories, content: body?.content });\n\n/**\n * Всё под `{base}/memory`. `segments` — путь после `memory`. Человек уже определён; память по своей природе\n * его собственная, поэтому чужая запись для сервера — просто «не найдена».\n */\nexport async function handleMemoryRequest(input: { memory: ChatMemory; request: Request; userId: string; segments: string[] }): Promise<Response> {\n const { memory, request, userId, segments } = input;\n const method = request.method.toUpperCase();\n const [id, ...rest] = segments;\n if (id === \"merges\") return handleMergesRequest({ memory, method, userId, rest });\n if (rest.length > 0) return errorResponse(\"not_found\");\n\n if (id === undefined) {\n if (method === \"GET\") return Response.json(await listForScreen(memory, userId));\n if (method === \"POST\") {\n const created = await memory.create(userId, noteFrom(await readBody(request)));\n return created.ok ? Response.json({ note: publicNote(created.value) }, { status: 201 }) : errorResponse(created.error.kind);\n }\n if (method === \"DELETE\") return Response.json({ deleted: await memory.clear(userId) });\n return errorResponse(\"not_found\");\n }\n\n if (method === \"PATCH\") {\n const body = await readBody(request);\n if (!isVersion(body?.version)) return errorResponse(\"invalid\");\n const updated = await memory.update(userId, id, body.version, noteFrom(body));\n return updated.ok ? Response.json({ note: publicNote(updated.value) }) : errorResponse(updated.error.kind);\n }\n\n if (method === \"DELETE\") {\n const version = Number(new URL(request.url).searchParams.get(\"version\") || Number.NaN);\n if (!isVersion(version)) return errorResponse(\"invalid\");\n const removed = await memory.remove(userId, id, version);\n return removed.ok ? new Response(null, { status: 204 }) : errorResponse(removed.error.kind);\n }\n\n return errorResponse(\"not_found\");\n}\n\n/** `/memory/merges` (список) и `/memory/merges/:runId/revert` (вернуть как было). Память без склеек — 404. */\nasync function handleMergesRequest(input: { memory: ChatMemory; method: string; userId: string; rest: string[] }): Promise<Response> {\n const { memory, method, userId, rest } = input;\n const { listMerges, revertMerge } = memory;\n if (typeof listMerges !== \"function\" || typeof revertMerge !== \"function\") return errorResponse(\"not_found\");\n\n if (rest.length === 0 && method === \"GET\") {\n const merges = await listMerges.call(memory, userId);\n return Response.json({ merges: merges.map(publicMerge) });\n }\n const [runId, action, ...extra] = rest;\n if (runId !== undefined && action === \"revert\" && extra.length === 0 && method === \"POST\") {\n const result = await revertMerge.call(memory, userId, runId);\n if (result === \"ok\") return Response.json({ reverted: runId });\n // 409: результат менялся (`conflict`), либо возврат вышел бы за пределы (`limit_reached`) или вернул бы повтор (`duplicate`).\n return result === \"not_found\" ? errorResponse(\"not_found\") : Response.json({ error: { kind: result } }, { status: 409 });\n }\n return errorResponse(\"not_found\");\n}\n\nasync function listForScreen(memory: ChatMemory, userId: string) {\n const { notes, counts } = await memory.list(userId);\n return { notes: notes.map(publicNote), counts: { total: counts.total, limit: counts.limit, shown: counts.shown } };\n}\n","import { normalizeTurnRefusal, SUBJECT_KEY_MAX_LENGTH } from \"@ykurilov/agent-chat-core\";\nimport type { ChatGreeting, ChatService, TurnRefusal } from \"@ykurilov/agent-chat-core\";\nimport { readJsonBody } from \"./request-body\";\n\n/** Что приложение отдаёт для чата объекта: данные (только для сервера), название и приветствие нового чата. */\nexport interface SubjectChatSettings {\n /** JSON-объект до 16 КБ: инструментам и хукам на сервере, в браузер не уходит. Пишется только при создании чата. */\n data?: Record<string, unknown>;\n /** Название нового чата; нет — из первого сообщения человека. */\n title?: string;\n /** Первое сообщение от агента в новом чате. */\n greeting?: ChatGreeting;\n}\n\n/**\n * Решение приложения о чате объекта: настройки или отказ `{ code, message }` (человеку нельзя в этот объект). Отказ\n * узнаётся по полю `code`.\n */\nexport type SubjectChatsResolver = (input: { user: { id: string }; key: string; request: Request }) => Promise<SubjectChatSettings | TurnRefusal>;\n\n/** Отказ приложения ответом 403 `turn_refused`: код и текст безопасно показать человеку. */\nexport function turnRefusalResponse(refusal: TurnRefusal): Response {\n const { code, message } = normalizeTurnRefusal(refusal);\n return Response.json({ error: { kind: \"turn_refused\", code, message } }, { status: 403 });\n}\n\n/** `POST {base}/subject { key }` → `{ id, created }`: найти или создать чат объекта человека. */\nexport async function openSubjectChatRoute(input: {\n request: Request;\n userId: string;\n service: Pick<ChatService, \"openSubjectChat\">;\n resolve: SubjectChatsResolver;\n invalid: () => Response;\n}): Promise<Response> {\n const key = ((await readJsonBody(input.request)) as { key?: unknown } | null)?.key;\n if (typeof key !== \"string\" || key.length === 0 || key.length > SUBJECT_KEY_MAX_LENGTH) return input.invalid();\n const settings = await input.resolve({ user: { id: input.userId }, key, request: input.request });\n if (typeof settings !== \"object\" || settings === null || Array.isArray(settings)) {\n // Ошибка приложения, а не человека: обработчик ответит 500 и запишет текст в журнал.\n throw new TypeError(`subjectChats должен вернуть объект настроек или отказ { code, message }, а вернул ${settings === null ? \"null\" : Array.isArray(settings) ? \"массив\" : typeof settings}.`);\n }\n if (\"code\" in settings) return turnRefusalResponse(settings);\n const { chat, created } = await input.service.openSubjectChat({ ownerId: input.userId, key, ...settings });\n return Response.json({ id: chat.id, created });\n}\n","/** Данные нового чата из браузера: решает приложение (`ChatHandlerOptions.newChatData`). */\nexport type NewChatDataResolver = (input: { user: { id: string }; request: Request }) => Promise<Record<string, unknown> | undefined> | Record<string, unknown> | undefined;\n\n/** Ответ приложения: объект данных или `undefined`. Другое — ошибка приложения, а не человека: обработчик ответит 500. */\nexport async function resolveNewChatData(resolve: NewChatDataResolver, input: { user: { id: string }; request: Request }): Promise<Record<string, unknown> | undefined> {\n const data = await resolve(input);\n if (data === undefined) return undefined;\n if (typeof data !== \"object\" || data === null || Array.isArray(data)) {\n throw new TypeError(`newChatData должен вернуть объект данных чата или undefined, а вернул ${data === null ? \"null\" : Array.isArray(data) ? \"массив\" : typeof data}.`);\n }\n return data;\n}\n","import { publicAppState } from \"@ykurilov/agent-chat-core\";\nimport type { AgentChannel, ChatEvent, ChatTurnTrigger, HiddenStateKey, TaskView } from \"@ykurilov/agent-chat-core\";\n\n/** Как часто поток сверяется с базой, пока у чата идут задачи. */\nexport const WATCH_RECONCILE_EVERY_MS = 5000;\n\n/** Событие для вкладки: без владельца — он ей не нужен. */\nexport type WatchEvent = ChatEvent extends infer Event ? (Event extends unknown ? Omit<Event, \"ownerId\"> : never) : never;\n\nconst LIVE_TASK_STATUSES: ReadonlySet<TaskView[\"status\"]> = new Set([\"queued\", \"running\", \"awaiting_approval\"]);\n\nexport interface WatchStreamOptions {\n ownerId: string;\n chatId: string;\n /** Запрос вкладки: клиент ушёл — поток закрывается, даже если его так и не начали читать. */\n signal?: AbortSignal;\n subscribeEvents(listener: (event: ChatEvent) => void): () => void;\n /** Ключи состояния, которые не уходят вкладке в `state-changed`; нет — только служебные ключи SDK. */\n hiddenStateKey?: HiddenStateKey;\n /** Задачи чата; нет фона — сверки нет. */\n listTasks?: () => Promise<TaskView[]>;\n /** Ход, ответ которого идёт в базе, но не на этой копии сервера, с источником и каналом по истории; нет — `null`. */\n detachedTurn(): Promise<{ messageId: string; trigger: ChatTurnTrigger; channel: AgentChannel } | null>;\n onClose(): void;\n logError(message: string): void;\n}\n\n/**\n * События одного чата: ход начался, ход закончился, задачи изменились. Пока у чата идут задачи, раз в 5 с поток\n * сверяется с базой: так видны ход и задачи, которые ведёт другая копия сервера.\n */\nexport function createWatchStream(options: WatchStreamOptions): ReadableStream<WatchEvent> {\n const { ownerId, chatId, listTasks } = options;\n let closed = false;\n let timer: ReturnType<typeof setTimeout> | undefined;\n let unsubscribe: (() => void) | undefined;\n let lastTasks: string | undefined;\n const announced = new Set<string>();\n\n const close = () => {\n if (closed) return;\n closed = true;\n clearTimeout(timer);\n unsubscribe?.();\n options.signal?.removeEventListener(\"abort\", onClientGone);\n options.onClose();\n };\n let onClientGone = () => close();\n\n return new ReadableStream<WatchEvent>({\n start(controller) {\n onClientGone = () => {\n const wasOpen = !closed;\n close();\n if (wasOpen) controller.close();\n };\n const send = (event: WatchEvent) => {\n if (!closed) controller.enqueue(event);\n };\n\n /** Задачи изменились с прошлой сверки — `tasks-changed`; возвращает, идут ли ещё задачи. */\n const checkTasks = async (): Promise<boolean> => {\n const tasks = await listTasks!();\n const snapshot = JSON.stringify(tasks);\n if (lastTasks !== undefined && snapshot !== lastTasks) send({ type: \"tasks-changed\", chatId });\n lastTasks = snapshot;\n return tasks.some((task) => LIVE_TASK_STATUSES.has(task.status));\n };\n\n const tick = async () => {\n timer = undefined;\n try {\n const live = await checkTasks();\n const turn = await options.detachedTurn();\n if (turn && !announced.has(turn.messageId)) {\n announced.add(turn.messageId);\n send({ type: \"turn-started\", chatId, ...turn });\n }\n if (live) scheduleReconcile();\n } catch (error) {\n options.logError(`Наблюдение за чатом ${chatId}: сверка с базой не удалась: ${error instanceof Error ? error.message : String(error)}`);\n scheduleReconcile();\n }\n };\n\n const scheduleReconcile = () => {\n if (closed || timer !== undefined || !listTasks) return;\n timer = setTimeout(() => void tick(), WATCH_RECONCILE_EVERY_MS);\n };\n\n unsubscribe = options.subscribeEvents((event) => {\n if (event.ownerId !== ownerId || event.chatId !== chatId) return;\n // Данные чата остаются на сервере: о смене вкладка не узнаёт.\n if (event.type === \"chat-data-changed\") return;\n const { ownerId: _, ...forTab } = event;\n if (event.type === \"turn-started\") announced.add(event.messageId);\n // Закрытые ключи состояния — только серверу.\n send(forTab.type === \"state-changed\" && options.hiddenStateKey ? { ...forTab, state: publicAppState(forTab.state, options.hiddenStateKey) } : forTab);\n // Задачи изменились — сверка снова нужна, даже если до этого задач не было.\n if (event.type === \"tasks-changed\") scheduleReconcile();\n });\n if (options.signal?.aborted) {\n onClientGone();\n return;\n }\n options.signal?.addEventListener(\"abort\", onClientGone, { once: true });\n\n if (listTasks) {\n void checkTasks()\n .then((live) => {\n if (live) scheduleReconcile();\n })\n .catch((error: unknown) => {\n options.logError(`Наблюдение за чатом ${chatId}: задачи не прочитаны: ${error instanceof Error ? error.message : String(error)}`);\n // Идут ли задачи, неизвестно: сверка повторится, как после сбоя очередной сверки.\n scheduleReconcile();\n });\n }\n },\n cancel() {\n close();\n },\n });\n}\n","export { createChatHandler, createChatServer } from \"./chat-handler\";\nexport type { ChatAttachments, ChatHandler, ChatHandlerOptions, ChatServerService, HandlerErrorKind } from \"./chat-handler\";\nexport type { AccessRefusal, ResolvedUser } from \"@ykurilov/agent-chat-core\";\nexport { accessRefusedResponse, allowedUser } from \"@ykurilov/agent-chat-core\";\nexport { turnRefusalResponse } from \"./subject-chat-route\";\nexport type { SubjectChatSettings, SubjectChatsResolver } from \"./subject-chat-route\";\nexport type { NewChatGreetingResolver } from \"./new-chat-greeting\";\n"],"mappings":"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","names":["errorResponse","accessRefusedResponse","allowedUser"]}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createChatService } from "@ykurilov/agent-chat-core";
|
|
2
|
+
import type { NewChatGreetingResolver } from "./new-chat-greeting.js";
|
|
2
3
|
import type { AbandonedBurstsOptions, AfterTurnHandler, ModelChosenHandler, RecoverInterruptedAnswersOptions, ReplyChecksSetting, ThoughtTitlesSetting, StateChangedHandler, AgentSettings, BackgroundTasks, BeforeTurnHandler, Chat, BeforeCreateChatHandler, ChatCapabilitiesHandler, ChatErrorKind, ChatService, ChatStorage, CompactionSettings, FlushOptions, MessageAttachments, ModelCatalog, ModelChoice, ResolvedUser, SpendingLimits, StaffActor, ChatInstructions, TurnFinishedHandler, TurnNotStartedHandler, TurnObserver } from "@ykurilov/agent-chat-core";
|
|
3
4
|
import type { ChatMemory } from "./memory-routes.js";
|
|
4
5
|
import type { NewChatDataResolver } from "./new-chat-data.js";
|
|
@@ -123,6 +124,13 @@ export interface ChatHandlerOptions extends ServiceOptionsOfServer {
|
|
|
123
124
|
* Требует хранилища с `updateChatData`: без него — ошибка при создании обработчика.
|
|
124
125
|
*/
|
|
125
126
|
newChatData?: NewChatDataResolver;
|
|
127
|
+
/**
|
|
128
|
+
* Приветствие нового обычного чата (`POST {base}`), после `newChatData`. `null` — без приветствия.
|
|
129
|
+
* Решение до записи: исключение или неверное приветствие — 500, чат не создан. Указание запускает один ход
|
|
130
|
+
* после создания (обычные права/лимиты/расходы). GET, reload и чаты объектов эту функцию не вызывают.
|
|
131
|
+
* Повтор HTTP POST создаёт другой чат: гарантия — одна побудка на созданный чат, не идемпотентность POST.
|
|
132
|
+
*/
|
|
133
|
+
newChatGreeting?: NewChatGreetingResolver;
|
|
126
134
|
/**
|
|
127
135
|
* Чужие страницы, которым можно менять данные чата с куками человека, например `["https://school.getcourse.ru"]`,
|
|
128
136
|
* когда чат встроен в чужой сайт и шлёт запросы прямо оттуда. Свою страницу вносить не нужно: запросы с неё проходят всегда.
|
package/dist/types/index.d.ts
CHANGED
|
@@ -4,3 +4,4 @@ export type { AccessRefusal, ResolvedUser } from "@ykurilov/agent-chat-core";
|
|
|
4
4
|
export { accessRefusedResponse, allowedUser } from "@ykurilov/agent-chat-core";
|
|
5
5
|
export { turnRefusalResponse } from "./subject-chat-route.js";
|
|
6
6
|
export type { SubjectChatSettings, SubjectChatsResolver } from "./subject-chat-route.js";
|
|
7
|
+
export type { NewChatGreetingResolver } from "./new-chat-greeting.js";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ChatGreeting } from "@ykurilov/agent-chat-core";
|
|
2
|
+
/** Приветствие HTTP-нового чата. Решение только сервера, после newChatData и до создания. */
|
|
3
|
+
export type NewChatGreetingResolver = (input: {
|
|
4
|
+
userId: string;
|
|
5
|
+
chatData: Record<string, unknown> | undefined;
|
|
6
|
+
}) => ChatGreeting | null | Promise<ChatGreeting | null>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ykurilov/agent-chat-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.79.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"ai": "7.0.122",
|
|
48
|
-
"@ykurilov/agent-chat-core": "0.
|
|
48
|
+
"@ykurilov/agent-chat-core": "0.79.0"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
51
|
"hono": "^4.13.10"
|