@ykurilov/agent-chat-server 0.0.0-stage → 0.56.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 +1784 -0
- package/LICENSE +21 -0
- package/README.md +107 -2
- package/dist/hono.js +13 -0
- package/dist/hono.js.map +1 -0
- package/dist/index.js +732 -0
- package/dist/index.js.map +1 -0
- package/dist/next.js +10 -0
- package/dist/next.js.map +1 -0
- package/dist/node.js +101 -0
- package/dist/node.js.map +1 -0
- package/dist/types/chat-handler.d.ts +235 -0
- package/dist/types/heartbeat.d.ts +15 -0
- package/dist/types/hono.d.ts +4 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/memory-routes.d.ts +53 -0
- package/dist/types/new-chat-data.d.ts +14 -0
- package/dist/types/next.d.ts +8 -0
- package/dist/types/node.d.ts +11 -0
- package/dist/types/request-body.d.ts +11 -0
- package/dist/types/subject-chat-route.d.ts +31 -0
- package/dist/types/watch-stream.d.ts +29 -0
- package/package.json +63 -3
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import type { AfterTurnHandler, ModelChosenHandler, RecoverInterruptedAnswersOptions, ReplyChecksSetting, 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";
|
|
2
|
+
import type { ChatMemory } from "./memory-routes.js";
|
|
3
|
+
import type { NewChatDataResolver } from "./new-chat-data.js";
|
|
4
|
+
import type { SubjectChatsResolver } from "./subject-chat-route.js";
|
|
5
|
+
export type { ChatMemory, MemoryCounts, MemoryMergeRecord, MemoryResult } from "./memory-routes.js";
|
|
6
|
+
/**
|
|
7
|
+
* `busy` — в чате идёт ход; `approval_not_pending` — на этот запрос подтверждения отвечать уже нечем (решён или закрыт новым сообщением);
|
|
8
|
+
* `approval_stale` — фоновая задача ждёт ответа уже на другой вопрос: ответ на старый не принят;
|
|
9
|
+
* `not_pending` — браузерный вызов не ждёт ответа этой вкладки (уже отвечен, срок вышел, ход кончился, вкладка чужая);
|
|
10
|
+
* `widget_not_pending` — виджет не ждёт ответа (уже отвечен, закрыт сообщением, в другой ветке, убран из настроек);
|
|
11
|
+
* `invalid_answer` — ответ на виджет не прошёл схему, длиннее предела или тело запроса негодное; ничего не записано;
|
|
12
|
+
* `widget_answer_refused` — приложение отказало в ответе на виджет (`checkAnswer`): `{ kind, code, message }`, ничего не записано,
|
|
13
|
+
* виджет ждёт;
|
|
14
|
+
* `widget_answer_cancelled` — человек нажал «Стоп», пока шла проверка ответа (`checkAnswer`): `{ kind, code, message }`, ничего не
|
|
15
|
+
* записано, виджет ждёт; это не сбой;
|
|
16
|
+
* `widget_check_timeout` (504) — проверка ответа (`checkAnswer`) не уложилась в `checkAnswerTimeoutMs`: `{ kind, code, message }`,
|
|
17
|
+
* ничего не записано, виджет ждёт, повторить можно;
|
|
18
|
+
* `too_many_turns` — у человека уже идёт столько ответов, сколько разрешено (`maxConcurrentTurnsPerUser`);
|
|
19
|
+
* `forbidden_origin` — запрос, меняющий данные, пришёл с чужой страницы (`allowedOrigins`); `too_large` — тело больше 1 МиБ;
|
|
20
|
+
* `turn_refused` — приложение отказало в ходе (`beforeTurn`); `access_refused` — приложение отказало в доступе (`resolveUser`);
|
|
21
|
+
* `action_forbidden` — действие запрещено правами чата (`chatCapabilities`); `chat_closed` — чат закрыт (`closeChat`);
|
|
22
|
+
* `chat_creation_refused` — приложение не дало завести новый чат (`beforeCreateChat`).
|
|
23
|
+
* У отказов приложения одно тело: `{ kind, code, message }` (у `action_forbidden` ещё `action`).
|
|
24
|
+
*/
|
|
25
|
+
export type HandlerErrorKind = ChatErrorKind | "not_found" | "busy" | "invalid" | "spending_limit_reached" | "approval_not_pending" | "approval_stale" | "widget_not_pending" | "invalid_answer" | "widget_answer_refused" | "widget_answer_cancelled" | "widget_check_timeout" | "not_pending" | "too_many_turns" | "forbidden_origin" | "too_large" | "turn_refused" | "access_refused" | "action_forbidden" | "chat_closed" | "chat_creation_refused";
|
|
26
|
+
/** Вложения: приём в сообщение и сборка запроса (для ядра) плюс адреса под `/:chatId/attachments`. Даёт пакет `@ykurilov/agent-chat-attachments`. */
|
|
27
|
+
export interface ChatAttachments extends MessageAttachments {
|
|
28
|
+
/** Ответ на `GET {base}/attachments/settings`. */
|
|
29
|
+
settings(): Response;
|
|
30
|
+
/** Всё под `/:chatId/attachments`; чат уже проверен как принадлежащий человеку. `segments` — путь после `attachments`. */
|
|
31
|
+
handle(input: {
|
|
32
|
+
request: Request;
|
|
33
|
+
userId: string;
|
|
34
|
+
chatId: string;
|
|
35
|
+
segments: string[];
|
|
36
|
+
}): Promise<Response>;
|
|
37
|
+
}
|
|
38
|
+
export interface ChatHandlerOptions {
|
|
39
|
+
/** Путь, под которым смонтирован обработчик, например `/api/chat`. */
|
|
40
|
+
basePath: string;
|
|
41
|
+
storage: ChatStorage;
|
|
42
|
+
/**
|
|
43
|
+
* Кто спрашивает: `{ id }` — человек, `null` — не вошёл (401 `invalid`). `{ refused: { code, message, status? } }` —
|
|
44
|
+
* приложение отказывает человеку в доступе своими словами: на любом адресе ответ `status` (401 или 403, по умолчанию 403)
|
|
45
|
+
* `{ error: { kind: "access_refused", code, message } }`. Код — `[a-z0-9_.-]`, 1–64 знака (иначе 500 `other` и строка
|
|
46
|
+
* в `logError`); текст — до 300 знаков.
|
|
47
|
+
*/
|
|
48
|
+
resolveUser: (request: Request) => Promise<ResolvedUser>;
|
|
49
|
+
/** Выбор модели для новых чатов. Не задан — первая модель каталога у первого провайдера с ключом. */
|
|
50
|
+
defaultChoice?: ModelChoice;
|
|
51
|
+
catalog: ModelCatalog;
|
|
52
|
+
agent: AgentSettings;
|
|
53
|
+
/** Автосжатие истории: включено по умолчанию, `false` выключает. Подробнее — в настройках ядра. */
|
|
54
|
+
compaction?: CompactionSettings | false;
|
|
55
|
+
/** Вложения к сообщениям. Не заданы — адресов `/attachments` нет, сообщения только текстовые. */
|
|
56
|
+
attachments?: ChatAttachments;
|
|
57
|
+
/**
|
|
58
|
+
* Лимиты расходов (пакет `@ykurilov/agent-chat-limits`). Исчерпанный лимит — ответ 429 `spending_limit_reached`
|
|
59
|
+
* на отправку, повтор, переделку и правку; появляется адрес `GET {base}/:chatId/spending`. Не заданы — адреса нет.
|
|
60
|
+
*/
|
|
61
|
+
limits?: SpendingLimits;
|
|
62
|
+
/**
|
|
63
|
+
* Сколько ответов одного человека могут идти одновременно во всех его чатах. Сверх этого — ответ 429
|
|
64
|
+
* `too_many_turns` с полем `limit`, ничего не записано. Считается в памяти процесса. По умолчанию 3; `Infinity` — без предела.
|
|
65
|
+
*/
|
|
66
|
+
maxConcurrentTurnsPerUser?: number;
|
|
67
|
+
/**
|
|
68
|
+
* Проверка приложения перед ходом: вернуть `{ code, message }` — ход не начнётся, ничего не запишется, ответ 403
|
|
69
|
+
* `{ error: { kind: "turn_refused", code, message } }` на отправку, повтор, переделку, правку, ответ на подтверждение
|
|
70
|
+
* и ответ на виджет. Подробнее — `ChatServiceOptions.beforeTurn`. Нет — ходы не проверяются.
|
|
71
|
+
*/
|
|
72
|
+
beforeTurn?: BeforeTurnHandler;
|
|
73
|
+
/**
|
|
74
|
+
* Ход не начался, хотя `beforeTurn` его пропустил (чат занят, предел ходов, лимит, повтор id, неверный запрос, сбой):
|
|
75
|
+
* снять резерв приложения. Ответ клиенту прежний. Подробнее — `ChatServiceOptions.turnNotStarted`.
|
|
76
|
+
*/
|
|
77
|
+
turnNotStarted?: TurnNotStartedHandler;
|
|
78
|
+
/**
|
|
79
|
+
* Права человека в чате: частичный список `{ право: boolean }`, пропущенное — `true`. Запрещённое — 403
|
|
80
|
+
* `{ error: { kind: "action_forbidden", action, code, message } }`, история отдаёт `capabilities`, экран прячет кнопки.
|
|
81
|
+
* Право `attachments` закрывает и загрузку вложений (`/:chatId/attachments`, кроме GET). Подробнее —
|
|
82
|
+
* `ChatServiceOptions.chatCapabilities`. Нет — всё разрешено.
|
|
83
|
+
*/
|
|
84
|
+
chatCapabilities?: ChatCapabilitiesHandler;
|
|
85
|
+
/**
|
|
86
|
+
* Можно ли человеку завести новый чат (`POST {base}`; тот же хук спрашивают мессенджеры на `/new`). Отказ — 403
|
|
87
|
+
* `{ error: { kind: "chat_creation_refused", code, message } }`, чат не создан. Подробнее —
|
|
88
|
+
* `ChatServiceOptions.beforeCreateChat`. Нет — чаты заводятся без проверки.
|
|
89
|
+
*/
|
|
90
|
+
beforeCreateChat?: BeforeCreateChatHandler;
|
|
91
|
+
/**
|
|
92
|
+
* Долгая память агента между чатами (пакет `@ykurilov/agent-chat-memory`): ядро добавляет её в запросы и инструменты,
|
|
93
|
+
* появляются адреса `{base}/memory` для экрана памяти. Не задана — адресов нет (404), чат как раньше.
|
|
94
|
+
*/
|
|
95
|
+
memory?: ChatMemory;
|
|
96
|
+
/**
|
|
97
|
+
* Список чатов человека: `GET {base}`, переименование `PATCH {base}/{id}` и удаление `DELETE {base}/{id}`.
|
|
98
|
+
* Не включён — этих адресов нет (404), у приложения с одним чатом всё работает как раньше. Чаты объектов
|
|
99
|
+
* (`subjectChats`) в списке не видны; `{ subjectChats: true }` — видны, с полем `subjectKey`.
|
|
100
|
+
*/
|
|
101
|
+
chatList?: boolean | {
|
|
102
|
+
subjectChats?: boolean;
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* Чаты объектов приложения (урок, заказ): адрес `POST {base}/subject { key }` → `{ id, created }` находит или
|
|
106
|
+
* создаёт чат человека по ключу (1–200 знаков). Приложение по ключу отдаёт данные объекта (только для сервера),
|
|
107
|
+
* название и приветствие нового чата — или отказ `{ code, message }` (ответ 403 `turn_refused`, чат не создан).
|
|
108
|
+
* Не задано — адреса нет (404). Требует хранилища с `findChatBySubject`: без него — ошибка при создании обработчика.
|
|
109
|
+
*/
|
|
110
|
+
subjectChats?: SubjectChatsResolver;
|
|
111
|
+
/**
|
|
112
|
+
* Данные нового чата, который человек создаёт из браузера (`POST {base}`): сервер решает, что записать, браузер
|
|
113
|
+
* данных не присылает. JSON-объект до 16 КБ (`Chat.data`) или `undefined` — чат без данных. Видят `beforeTurn`,
|
|
114
|
+
* `chatInstructions`, инструменты (`model.chat.data`) и `afterTurn` (`turn.chatData`); в браузер не уходят.
|
|
115
|
+
* Вернул не объект или бросил — ответ 500, чат не создан. Чаты объектов (`subjectChats`) берут данные из своего решения.
|
|
116
|
+
* Требует хранилища с `updateChatData`: без него — ошибка при создании обработчика.
|
|
117
|
+
*/
|
|
118
|
+
newChatData?: NewChatDataResolver;
|
|
119
|
+
/**
|
|
120
|
+
* Чужие страницы, которым можно менять данные чата с куками человека, например `["https://school.getcourse.ru"]`,
|
|
121
|
+
* когда чат встроен в чужой сайт и шлёт запросы прямо оттуда. Свою страницу вносить не нужно: запросы с неё проходят всегда.
|
|
122
|
+
* Запрос, меняющий данные (не GET), с чужой страницы — ответ 403 `forbidden_origin`, ничего не записано.
|
|
123
|
+
* Запросы не из браузера (без заголовков Origin и Sec-Fetch-Site) проходят: их судит `resolveUser`.
|
|
124
|
+
*/
|
|
125
|
+
allowedOrigins?: string[];
|
|
126
|
+
/** Откуда брать ключи провайдеров. По умолчанию `process.env`. */
|
|
127
|
+
env?: Record<string, string | undefined>;
|
|
128
|
+
/** Журнал сервера: нет ключа провайдера, непредвиденные сбои. По умолчанию `console.error`. */
|
|
129
|
+
logError?: (message: string) => void;
|
|
130
|
+
/** Промежуток между признаками жизни в потоках ответа. По умолчанию 10 000 мс, не меньше 1000. */
|
|
131
|
+
heartbeatIntervalMs?: number;
|
|
132
|
+
/** Наблюдатель хода (песочница): см. `ChatServiceOptions.turnObserver`. Нет — запрос к модели побайтно прежний. */
|
|
133
|
+
turnObserver?: TurnObserver;
|
|
134
|
+
/** Своя инструкция чата (песочница): см. `ChatServiceOptions.chatInstructions`. `null` — инструкция приложения. */
|
|
135
|
+
chatInstructions?: ChatInstructions;
|
|
136
|
+
/** Сотрудник хода по чату (песочница: копия чата человека): см. `ChatServiceOptions.staffOfChat`. */
|
|
137
|
+
staffOfChat?: (chat: Chat) => StaffActor | null | undefined;
|
|
138
|
+
/** Инструменты, чей результат правка ответа не меняет (файлы): см. `ChatServiceOptions.lockedToolOutputs`. */
|
|
139
|
+
lockedToolOutputs?: readonly string[];
|
|
140
|
+
/**
|
|
141
|
+
* Фоновые задачи (пакет `@ykurilov/agent-chat-background`): появляются адреса `{base}/:chatId/tasks`. Не заданы —
|
|
142
|
+
* адресов задач нет (404), запрос к модели прежний.
|
|
143
|
+
*/
|
|
144
|
+
background?: BackgroundTasks;
|
|
145
|
+
/**
|
|
146
|
+
* Ход закончен: `{ userId, chatId, messageId, status, modelId, providerId }`, один раз на ход при любом итоге —
|
|
147
|
+
* законченном, остановленном, с ошибкой, прерванном, ждущем человека. Зовётся, когда итог уже записан; ход обработчик
|
|
148
|
+
* не ждёт, его сбой уходит в `logError`. Нет — ничего не зовётся.
|
|
149
|
+
*/
|
|
150
|
+
onTurnFinished?: TurnFinishedHandler;
|
|
151
|
+
/**
|
|
152
|
+
* Итог хода для приложения: ход ждёт его до `finish`, возвращённые `events` ложатся в ответ частями `data-app-event`.
|
|
153
|
+
* Повторы, срок, состояние в ответе и доделка после перезапуска — как у `ChatServiceOptions.afterTurn`. Событие вне
|
|
154
|
+
* хода — `service.publishAppEvent` (служба из `createChatServer`): открытая вкладка получит его через `watch`.
|
|
155
|
+
*/
|
|
156
|
+
afterTurn?: AfterTurnHandler;
|
|
157
|
+
/** Срок хука итога на все попытки, мс. По умолчанию 30 000. */
|
|
158
|
+
afterTurnTimeoutMs?: number;
|
|
159
|
+
/** Попыток хука итога. По умолчанию 3. */
|
|
160
|
+
afterTurnAttempts?: number;
|
|
161
|
+
/**
|
|
162
|
+
* Состояние чата сменилось (ход, правка, «Переделать», «Повторить», выбор ветки, правка ответа): копия состояния в
|
|
163
|
+
* таблицах приложения. Как у `ChatServiceOptions.onStateChanged`; открытая вкладка получает событие `state-changed`
|
|
164
|
+
* через `watch`.
|
|
165
|
+
*/
|
|
166
|
+
onStateChanged?: StateChangedHandler;
|
|
167
|
+
/** Срок хука смены состояния на все попытки, мс. По умолчанию 30 000. */
|
|
168
|
+
onStateChangedTimeoutMs?: number;
|
|
169
|
+
/** Попыток хука смены состояния. По умолчанию 3. */
|
|
170
|
+
onStateChangedAttempts?: number;
|
|
171
|
+
/**
|
|
172
|
+
* Человек выбрал модель или уровень в меню (`PUT {chat}/model`): после записи выбора. Подстановка выбора и
|
|
173
|
+
* `service.setModel(..., { by: "app" })` его не зовут. Как у `ChatServiceOptions.onModelChosen`.
|
|
174
|
+
*/
|
|
175
|
+
onModelChosen?: ModelChosenHandler;
|
|
176
|
+
/**
|
|
177
|
+
* Видит ли человек `GET …/models`: `false` — 403 `action_forbidden` (`action: "modelDetails"`). Меню не знает чата,
|
|
178
|
+
* поэтому право `modelDetails` его не закрывает. Нет — меню видно всем. Как у `ChatServiceOptions.modelMenu`.
|
|
179
|
+
*/
|
|
180
|
+
modelMenu?: (input: {
|
|
181
|
+
userId: string;
|
|
182
|
+
}) => boolean | Promise<boolean>;
|
|
183
|
+
/**
|
|
184
|
+
* Закрытые верхние ключи состояния чата: не уходят в браузер ни в истории, ни в потоке хода, ни в `state-changed`
|
|
185
|
+
* адреса `watch`. Сервер видит их как обычно. Как у `ChatServiceOptions.privateStateKeys`.
|
|
186
|
+
*/
|
|
187
|
+
privateStateKeys?: readonly string[];
|
|
188
|
+
/** Проверки готового ответа перед записью. Как у `ChatServiceOptions.replyChecks`. */
|
|
189
|
+
replyChecks?: ReplyChecksSetting;
|
|
190
|
+
/** Часы службы: тесты и песочница подменяют время целиком. Как у `ChatServiceOptions.now`. */
|
|
191
|
+
now?: () => Date;
|
|
192
|
+
}
|
|
193
|
+
export interface ChatHandler {
|
|
194
|
+
basePath: string;
|
|
195
|
+
fetch(request: Request): Promise<Response>;
|
|
196
|
+
/**
|
|
197
|
+
* Помечает «прерванными» ответы, оборванные прошлым запуском сервера, и запускает регулярную проверку брошенных
|
|
198
|
+
* ответов. Вызывать при старте, до приёма запросов; в Next.js — один раз сразу после `createChatHandler`: свои
|
|
199
|
+
* живые ответы проверка не трогает. Без вызова оборванные ответы остаются «идущими». Возвращает число помеченных.
|
|
200
|
+
* `{ soleServer: true }` — копия сервера на этой базе одна: оборванные ответы прерываются сразу, а не через 15–20 с.
|
|
201
|
+
* Только при старте процесса: вызов позже прервёт ход, который как раз запускается. Нельзя с несколькими копиями
|
|
202
|
+
* сервера на одной базе и с несколькими обработчиками в одном процессе (dev-режим Next.js): прервутся живые ответы.
|
|
203
|
+
*/
|
|
204
|
+
recoverInterruptedAnswers(options?: RecoverInterruptedAnswersOptions): Promise<number>;
|
|
205
|
+
/**
|
|
206
|
+
* Штатная остановка: после того как сервер перестал принимать запросы, до закрытия хранилища. Ждёт идущие ответы до
|
|
207
|
+
* `waitForAnswersMs` (по умолчанию 0), не успевшие останавливает, как «Стоп», с записью; возвращается, когда всё
|
|
208
|
+
* записано и хуки итога закончены. Подробнее — `ChatService.flush`.
|
|
209
|
+
*/
|
|
210
|
+
flush(options?: FlushOptions): Promise<void>;
|
|
211
|
+
/**
|
|
212
|
+
* Открыт ли у чата этого владельца поток `GET /:chatId/watch` (вкладка с чатом). Необязателен: обёртки над сервером
|
|
213
|
+
* (песочница) реализуют этот тип без него; мессенджеры берут его у `ChatServerService`.
|
|
214
|
+
*/
|
|
215
|
+
hasWatchers?(ownerId: string, chatId: string): boolean;
|
|
216
|
+
}
|
|
217
|
+
/** Служба чата сервера: служба ядра плюс то, что знает только сервер, — открытые вкладки и задачи фона. */
|
|
218
|
+
export interface ChatServerService extends ChatService {
|
|
219
|
+
/** Открыт ли у чата этого владельца поток `GET /:chatId/watch`. */
|
|
220
|
+
hasWatchers(ownerId: string, chatId: string): boolean;
|
|
221
|
+
/** Задачи фона; нет фона — нет и этого поля. */
|
|
222
|
+
tasks?: {
|
|
223
|
+
list: BackgroundTasks["listForChat"];
|
|
224
|
+
} & Pick<BackgroundTasks, "cancel" | "answerApproval">;
|
|
225
|
+
}
|
|
226
|
+
/** HTTP-обработчик чата на стандартных `Request`/`Response` (contracts/http-api.md). */
|
|
227
|
+
export declare function createChatHandler(options: ChatHandlerOptions): ChatHandler;
|
|
228
|
+
/**
|
|
229
|
+
* Обработчик чата и его служба: приложению (песочнице) служба нужна для своих адресов поверх тех же чатов —
|
|
230
|
+
* в одном процессе с обработчиком, с общим реестром идущих ответов.
|
|
231
|
+
*/
|
|
232
|
+
export declare function createChatServer(options: ChatHandlerOptions): {
|
|
233
|
+
handler: ChatHandler;
|
|
234
|
+
service: ChatServerService;
|
|
235
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Заголовок, по которому вкладка узнаёт, как часто сервер шлёт признак жизни. */
|
|
2
|
+
export declare const HEARTBEAT_HEADER = "x-agent-chat-heartbeat-ms";
|
|
3
|
+
export declare const MIN_HEARTBEAT_INTERVAL_MS = 1000;
|
|
4
|
+
export declare const DEFAULT_HEARTBEAT_INTERVAL_MS = 10000;
|
|
5
|
+
/**
|
|
6
|
+
* Ответ-поток из частей UI-сообщения с признаком жизни: SSE-комментарий `: ping` сразу и потом
|
|
7
|
+
* каждые `heartbeatIntervalMs`. События приходят целиком (по одному на кусок), а пинг встаёт
|
|
8
|
+
* только между ними. Клиент SDK комментарии пропускает, поэтому в сообщения и хранилище они
|
|
9
|
+
* не попадают.
|
|
10
|
+
*/
|
|
11
|
+
export declare function createHeartbeatResponse(options: {
|
|
12
|
+
stream: ReadableStream<unknown>;
|
|
13
|
+
heartbeatIntervalMs: number;
|
|
14
|
+
}): Response;
|
|
15
|
+
export declare function assertHeartbeatInterval(heartbeatIntervalMs: number): void;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Hono } from "hono";
|
|
2
|
+
import type { ChatHandler } from "./chat-handler.js";
|
|
3
|
+
/** Монтирует обработчик чата (или расширения с такой же формой: `basePath` и `fetch`, например голоса) в приложение Hono по его `basePath`. */
|
|
4
|
+
export declare function mountChat(app: Hono<any, any, any>, handler: Pick<ChatHandler, "basePath" | "fetch">): void;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { createChatHandler, createChatServer } from "./chat-handler.js";
|
|
2
|
+
export type { ChatAttachments, ChatHandler, ChatHandlerOptions, ChatServerService, HandlerErrorKind } from "./chat-handler.js";
|
|
3
|
+
export type { AccessRefusal, ResolvedUser } from "@ykurilov/agent-chat-core";
|
|
4
|
+
export { accessRefusedResponse, allowedUser } from "@ykurilov/agent-chat-core";
|
|
5
|
+
export { turnRefusalResponse } from "./subject-chat-route.js";
|
|
6
|
+
export type { SubjectChatSettings, SubjectChatsResolver } from "./subject-chat-route.js";
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { LongTermMemory, MemoryError, MemoryMergeRevertResult, MemoryNote } from "@ykurilov/agent-chat-core";
|
|
2
|
+
/** Исход действия с памятью: запись или ошибка-ответ. То же, что у пакета `@ykurilov/agent-chat-memory`. */
|
|
3
|
+
export type MemoryResult<T> = {
|
|
4
|
+
ok: true;
|
|
5
|
+
value: T;
|
|
6
|
+
} | {
|
|
7
|
+
ok: false;
|
|
8
|
+
error: MemoryError;
|
|
9
|
+
};
|
|
10
|
+
/** Сколько записей человека, какой предел и сколько из них агент видит в каждом запросе. */
|
|
11
|
+
export interface MemoryCounts {
|
|
12
|
+
total: number;
|
|
13
|
+
limit: number;
|
|
14
|
+
shown: number;
|
|
15
|
+
}
|
|
16
|
+
/** Слияние одного запуска: что стало (`result`) вместо каких записей (`originals`). Как у пакета `@ykurilov/agent-chat-memory`. */
|
|
17
|
+
export interface MemoryMergeRecord {
|
|
18
|
+
runId: string;
|
|
19
|
+
finishedAt: string;
|
|
20
|
+
revertible: boolean;
|
|
21
|
+
groups: {
|
|
22
|
+
result: MemoryNote;
|
|
23
|
+
originals: MemoryNote[];
|
|
24
|
+
}[];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Долгая память вместе с действиями для адресов `/memory`. Даёт пакет `@ykurilov/agent-chat-memory`.
|
|
28
|
+
* `listMerges` и `revertMerge` есть не у всякой памяти: без них адреса `/memory/merges` отвечают 404.
|
|
29
|
+
*/
|
|
30
|
+
export interface ChatMemory extends LongTermMemory {
|
|
31
|
+
list(ownerId: string): Promise<{
|
|
32
|
+
notes: MemoryNote[];
|
|
33
|
+
counts: MemoryCounts;
|
|
34
|
+
}>;
|
|
35
|
+
create(ownerId: string, input: unknown): Promise<MemoryResult<MemoryNote>>;
|
|
36
|
+
update(ownerId: string, id: string, version: number, input: unknown): Promise<MemoryResult<MemoryNote>>;
|
|
37
|
+
remove(ownerId: string, id: string, version: number): Promise<MemoryResult<{
|
|
38
|
+
deleted: string;
|
|
39
|
+
}>>;
|
|
40
|
+
clear(ownerId: string): Promise<number>;
|
|
41
|
+
listMerges?(ownerId: string, limit?: number): Promise<MemoryMergeRecord[]>;
|
|
42
|
+
revertMerge?(ownerId: string, runId: string): Promise<MemoryMergeRevertResult>;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Всё под `{base}/memory`. `segments` — путь после `memory`. Человек уже определён; память по своей природе
|
|
46
|
+
* его собственная, поэтому чужая запись для сервера — просто «не найдена».
|
|
47
|
+
*/
|
|
48
|
+
export declare function handleMemoryRequest(input: {
|
|
49
|
+
memory: ChatMemory;
|
|
50
|
+
request: Request;
|
|
51
|
+
userId: string;
|
|
52
|
+
segments: string[];
|
|
53
|
+
}): Promise<Response>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Данные нового чата из браузера: решает приложение (`ChatHandlerOptions.newChatData`). */
|
|
2
|
+
export type NewChatDataResolver = (input: {
|
|
3
|
+
user: {
|
|
4
|
+
id: string;
|
|
5
|
+
};
|
|
6
|
+
request: Request;
|
|
7
|
+
}) => Promise<Record<string, unknown> | undefined> | Record<string, unknown> | undefined;
|
|
8
|
+
/** Ответ приложения: объект данных или `undefined`. Другое — ошибка приложения, а не человека: обработчик ответит 500. */
|
|
9
|
+
export declare function resolveNewChatData(resolve: NewChatDataResolver, input: {
|
|
10
|
+
user: {
|
|
11
|
+
id: string;
|
|
12
|
+
};
|
|
13
|
+
request: Request;
|
|
14
|
+
}): Promise<Record<string, unknown> | undefined>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ChatHandler } from "./chat-handler.js";
|
|
2
|
+
type RouteHandler = (request: Request) => Promise<Response>;
|
|
3
|
+
declare const routeMethods: readonly ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"];
|
|
4
|
+
/** Методы для файла маршрута Next.js (App Router): `export const { GET, POST, … } = createNextRouteHandlers(handler)`. */
|
|
5
|
+
export type NextRouteHandlers = Record<(typeof routeMethods)[number], RouteHandler>;
|
|
6
|
+
/** Отдаёт обработчик чата (или расширения с такой же формой: `basePath` и `fetch`) как обработчики маршрута Next.js; ответ идёт как есть, поток не буферизуется. */
|
|
7
|
+
export declare function createNextRouteHandlers(handler: Pick<ChatHandler, "fetch">): NextRouteHandlers;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import type { ChatHandler } from "./chat-handler.js";
|
|
3
|
+
/** Слушатель для `http.createServer` и промежуточный обработчик Express: `app.use(toNodeListener(handler))`. */
|
|
4
|
+
export type NodeListener = (req: IncomingMessage, res: ServerResponse, next?: (error?: unknown) => void) => void;
|
|
5
|
+
/**
|
|
6
|
+
* Отдаёт обработчик чата (или расширения с такой же формой: `basePath` и `fetch`) как слушатель Node.
|
|
7
|
+
* Адрес вне `basePath` — `next()`, без `next` — 404. Тело запроса идёт потоком; уже прочитанное разборщиком (`req.body`)
|
|
8
|
+
* уходит как есть. Ответ пишется по кускам, без буфера. Разрыв соединения до конца ответа отменяет запрос
|
|
9
|
+
* (`request.signal`) и поток ответа.
|
|
10
|
+
*/
|
|
11
|
+
export declare function toNodeListener(handler: Pick<ChatHandler, "basePath" | "fetch">): NodeListener;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Больше этого тело JSON не читается: самое длинное сообщение (100 000 знаков) с местом на экране занимает меньше 0,5 МиБ. */
|
|
2
|
+
export declare const MAX_JSON_BODY_BYTES: number;
|
|
3
|
+
/** Тело запроса длиннее предела: оно не дочитано и не разобрано, ответ — 413. */
|
|
4
|
+
export declare class RequestBodyTooLargeError extends Error {
|
|
5
|
+
constructor(maxBytes: number);
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* JSON тела не длиннее `maxBytes`: заявленная длина больше — тело не читается, поток длиннее — чтение обрывается;
|
|
9
|
+
* тогда `RequestBodyTooLargeError`. Нет тела или не JSON — `null`.
|
|
10
|
+
*/
|
|
11
|
+
export declare function readJsonBody(request: Request, maxBytes?: number): Promise<unknown>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ChatGreeting, ChatService, TurnRefusal } from "@ykurilov/agent-chat-core";
|
|
2
|
+
/** Что приложение отдаёт для чата объекта: данные (только для сервера), название и приветствие нового чата. */
|
|
3
|
+
export interface SubjectChatSettings {
|
|
4
|
+
/** JSON-объект до 16 КБ: инструментам и хукам на сервере, в браузер не уходит. Пишется только при создании чата. */
|
|
5
|
+
data?: Record<string, unknown>;
|
|
6
|
+
/** Название нового чата; нет — из первого сообщения человека. */
|
|
7
|
+
title?: string;
|
|
8
|
+
/** Первое сообщение от агента в новом чате. */
|
|
9
|
+
greeting?: ChatGreeting;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Решение приложения о чате объекта: настройки или отказ `{ code, message }` (человеку нельзя в этот объект). Отказ
|
|
13
|
+
* узнаётся по полю `code`.
|
|
14
|
+
*/
|
|
15
|
+
export type SubjectChatsResolver = (input: {
|
|
16
|
+
user: {
|
|
17
|
+
id: string;
|
|
18
|
+
};
|
|
19
|
+
key: string;
|
|
20
|
+
request: Request;
|
|
21
|
+
}) => Promise<SubjectChatSettings | TurnRefusal>;
|
|
22
|
+
/** Отказ приложения ответом 403 `turn_refused`: код и текст безопасно показать человеку. */
|
|
23
|
+
export declare function turnRefusalResponse(refusal: TurnRefusal): Response;
|
|
24
|
+
/** `POST {base}/subject { key }` → `{ id, created }`: найти или создать чат объекта человека. */
|
|
25
|
+
export declare function openSubjectChatRoute(input: {
|
|
26
|
+
request: Request;
|
|
27
|
+
userId: string;
|
|
28
|
+
service: Pick<ChatService, "openSubjectChat">;
|
|
29
|
+
resolve: SubjectChatsResolver;
|
|
30
|
+
invalid: () => Response;
|
|
31
|
+
}): Promise<Response>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { AgentChannel, ChatEvent, ChatTurnTrigger, HiddenStateKey, TaskView } from "@ykurilov/agent-chat-core";
|
|
2
|
+
/** Как часто поток сверяется с базой, пока у чата идут задачи. */
|
|
3
|
+
export declare const WATCH_RECONCILE_EVERY_MS = 5000;
|
|
4
|
+
/** Событие для вкладки: без владельца — он ей не нужен. */
|
|
5
|
+
export type WatchEvent = ChatEvent extends infer Event ? (Event extends unknown ? Omit<Event, "ownerId"> : never) : never;
|
|
6
|
+
export interface WatchStreamOptions {
|
|
7
|
+
ownerId: string;
|
|
8
|
+
chatId: string;
|
|
9
|
+
/** Запрос вкладки: клиент ушёл — поток закрывается, даже если его так и не начали читать. */
|
|
10
|
+
signal?: AbortSignal;
|
|
11
|
+
subscribeEvents(listener: (event: ChatEvent) => void): () => void;
|
|
12
|
+
/** Ключи состояния, которые не уходят вкладке в `state-changed`; нет — только служебные ключи SDK. */
|
|
13
|
+
hiddenStateKey?: HiddenStateKey;
|
|
14
|
+
/** Задачи чата; нет фона — сверки нет. */
|
|
15
|
+
listTasks?: () => Promise<TaskView[]>;
|
|
16
|
+
/** Ход, ответ которого идёт в базе, но не на этой копии сервера, с источником и каналом по истории; нет — `null`. */
|
|
17
|
+
detachedTurn(): Promise<{
|
|
18
|
+
messageId: string;
|
|
19
|
+
trigger: ChatTurnTrigger;
|
|
20
|
+
channel: AgentChannel;
|
|
21
|
+
} | null>;
|
|
22
|
+
onClose(): void;
|
|
23
|
+
logError(message: string): void;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* События одного чата: ход начался, ход закончился, задачи изменились. Пока у чата идут задачи, раз в 5 с поток
|
|
27
|
+
* сверяется с базой: так видны ход и задачи, которые ведёт другая копия сервера.
|
|
28
|
+
*/
|
|
29
|
+
export declare function createWatchStream(options: WatchStreamOptions): ReadableStream<WatchEvent>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,66 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ykurilov/agent-chat-server",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.56.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"README.md",
|
|
10
|
+
"CHANGELOG.md"
|
|
11
|
+
],
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"@ykurilov/agent-chat-source": "./src/index.ts",
|
|
15
|
+
"types": "./dist/types/index.d.ts",
|
|
16
|
+
"import": "./dist/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./hono": {
|
|
19
|
+
"@ykurilov/agent-chat-source": "./src/hono.ts",
|
|
20
|
+
"types": "./dist/types/hono.d.ts",
|
|
21
|
+
"import": "./dist/hono.js"
|
|
22
|
+
},
|
|
23
|
+
"./next": {
|
|
24
|
+
"@ykurilov/agent-chat-source": "./src/next.ts",
|
|
25
|
+
"types": "./dist/types/next.d.ts",
|
|
26
|
+
"import": "./dist/next.js"
|
|
27
|
+
},
|
|
28
|
+
"./node": {
|
|
29
|
+
"@ykurilov/agent-chat-source": "./src/node.ts",
|
|
30
|
+
"types": "./dist/types/node.d.ts",
|
|
31
|
+
"import": "./dist/node.js"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=22"
|
|
36
|
+
},
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"registry": "https://registry.npmjs.org",
|
|
39
|
+
"access": "public"
|
|
40
|
+
},
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "git+https://github.com/ykurilov/agent-chat-sdk.git",
|
|
44
|
+
"directory": "packages/server"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"ai": "7.0.122",
|
|
48
|
+
"@ykurilov/agent-chat-core": "0.56.0"
|
|
49
|
+
},
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"hono": "^4.13.10"
|
|
52
|
+
},
|
|
53
|
+
"peerDependenciesMeta": {
|
|
54
|
+
"hono": {
|
|
55
|
+
"optional": true
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@ai-sdk/provider": "4.0.19",
|
|
60
|
+
"hono": "^4.13.10"
|
|
61
|
+
},
|
|
62
|
+
"scripts": {
|
|
63
|
+
"build": "node ../../scripts/build-package.mjs",
|
|
64
|
+
"typecheck": "tsc -p tsconfig.json"
|
|
65
|
+
}
|
|
6
66
|
}
|