@ykurilov/agent-chat-core 0.55.0 → 0.57.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 +33 -0
- package/README.md +2 -0
- package/dist/access.js +2 -2
- package/dist/{chunk-PWW3R3VL.js → chunk-BS2HDKZB.js} +586 -41
- package/dist/chunk-BS2HDKZB.js.map +1 -0
- package/dist/{chunk-SNPF4BPB.js → chunk-FBULM4V6.js} +2 -2
- package/dist/{chunk-PWGI6NH6.js → chunk-FOAIG4DY.js} +1 -1
- package/dist/chunk-FOAIG4DY.js.map +1 -0
- package/dist/index.js +31 -3
- package/dist/index.js.map +1 -1
- package/dist/storage-contract.js +89 -7
- package/dist/storage-contract.js.map +1 -1
- package/dist/types/agent-run/agent-viewer.d.ts +60 -0
- package/dist/types/agent-run/garbled-tail.d.ts +69 -0
- package/dist/types/agent-run/reply-checks.d.ts +3 -1
- package/dist/types/agent-run/run-agent-turn.d.ts +8 -0
- package/dist/types/agent-run/staff-replies.d.ts +7 -0
- package/dist/types/bursts/message-bursts.d.ts +12 -0
- package/dist/types/chat-events/chat-events.d.ts +7 -0
- package/dist/types/chat-service.d.ts +29 -0
- package/dist/types/compaction/compaction-service.d.ts +3 -0
- package/dist/types/conversation/types.d.ts +3 -1
- package/dist/types/frozen-context/context-blocks.d.ts +3 -0
- package/dist/types/frozen-context/turn-context.d.ts +3 -0
- package/dist/types/index.d.ts +4 -2
- package/dist/types/storage/chat-storage.d.ts +47 -0
- package/dist/types/storage/storage-contract.d.ts +5 -0
- package/dist/types/turn-hooks/turn-gate.d.ts +2 -0
- package/dist/types/turn-hooks/turn-hooks.d.ts +7 -0
- package/package.json +1 -1
- package/dist/chunk-PWGI6NH6.js.map +0 -1
- package/dist/chunk-PWW3R3VL.js.map +0 -1
- /package/dist/{chunk-SNPF4BPB.js.map → chunk-FBULM4V6.js.map} +0 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { AgentAudience, AgentChannel, ChatSubject } from "../conversation/types.js";
|
|
2
|
+
import type { StaffActor } from "../turn-hooks/turn-hooks.js";
|
|
3
|
+
/**
|
|
4
|
+
* Кто смотрит на ход: права и признаки человека, которые приложение берёт из своей базы (`agent.viewer`). Ядро его не
|
|
5
|
+
* читает, только передаёт. `isAdmin` — общий признак для пакетов (серверы MCP, навыки): «только админы» видны, когда он
|
|
6
|
+
* `true`. Остальные поля — свои у приложения.
|
|
7
|
+
*/
|
|
8
|
+
export interface AgentViewer {
|
|
9
|
+
isAdmin?: boolean;
|
|
10
|
+
readonly [key: string]: unknown;
|
|
11
|
+
}
|
|
12
|
+
/** Что видит `agent.viewer` в начале хода. */
|
|
13
|
+
export interface AgentViewerInput {
|
|
14
|
+
/** Кто делает ход: сотрудник в копии чата человека, иначе владелец чата. */
|
|
15
|
+
userId: string;
|
|
16
|
+
chatId: string;
|
|
17
|
+
/** Владелец чата. */
|
|
18
|
+
ownerId: string;
|
|
19
|
+
/** Ответ агента, который строит этот ход; у фонового помощника — пустая строка. */
|
|
20
|
+
messageId: string;
|
|
21
|
+
chat: {
|
|
22
|
+
data?: Record<string, unknown>;
|
|
23
|
+
};
|
|
24
|
+
subject?: ChatSubject;
|
|
25
|
+
channel: AgentChannel;
|
|
26
|
+
audience: AgentAudience;
|
|
27
|
+
staff?: StaffActor;
|
|
28
|
+
/** «Стоп» и срок хода. */
|
|
29
|
+
abortSignal: AbortSignal;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Права человека на ход: зовётся один раз в начале хода (повторная попытка и запасной провайдер не зовут снова), до
|
|
33
|
+
* блоков контекста и сборки инструментов. Итог идёт полем `viewer` в `contextBlocks` и в `AgentToolsModel`; в запрос к
|
|
34
|
+
* модели сам не попадает. Бросила или вернула не объект — ход кончается ошибкой.
|
|
35
|
+
*/
|
|
36
|
+
export type AgentViewerSource = (input: AgentViewerInput) => AgentViewer | Promise<AgentViewer>;
|
|
37
|
+
/** `agent.viewer` бросила или вернула не объект: ход кончается ошибкой, в журнал сервера — причина. */
|
|
38
|
+
export declare class AgentViewerError extends Error {
|
|
39
|
+
constructor(message: string, options?: {
|
|
40
|
+
cause?: unknown;
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
/** Зовёт `agent.viewer`: итог заморожен (правка не доходит до хода), «Стоп» обрывает ожидание. */
|
|
44
|
+
export declare function resolveAgentViewer(source: AgentViewerSource, input: AgentViewerInput): Promise<AgentViewer>;
|
|
45
|
+
/** Вход `agent.viewer` из полей хода. */
|
|
46
|
+
export declare function viewerInput(turn: {
|
|
47
|
+
chatId: string;
|
|
48
|
+
ownerId?: string;
|
|
49
|
+
messageId: string;
|
|
50
|
+
chatData?: Record<string, unknown>;
|
|
51
|
+
subject?: ChatSubject;
|
|
52
|
+
channel?: AgentChannel;
|
|
53
|
+
audience: AgentAudience;
|
|
54
|
+
staff?: StaffActor;
|
|
55
|
+
abortSignal: AbortSignal;
|
|
56
|
+
}): AgentViewerInput;
|
|
57
|
+
/** Срок `agent.viewer` вне хода человека (сжатие истории, фоновый помощник): «Стопа» там нет, зависание не держит работу. */
|
|
58
|
+
export declare const VIEWER_OUTSIDE_TURN_TIMEOUT_MS = 10000;
|
|
59
|
+
/** `agent.viewer` для сжатия и фонового помощника: те же правила, что в ходе, плюс срок ответа. */
|
|
60
|
+
export declare function resolveViewerOutsideTurn(source: AgentViewerSource, turn: Omit<Parameters<typeof viewerInput>[0], "abortSignal">, signal?: AbortSignal): Promise<AgentViewer>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { LanguageModel } from "ai";
|
|
2
|
+
import type { AgentChatMessage } from "../conversation/types.js";
|
|
3
|
+
type MessagePart = AgentChatMessage["parts"][number];
|
|
4
|
+
/**
|
|
5
|
+
* Признаки срыва ответа в кашу — модель потеряла границу своего хода и пишет дальше за обоих. Правило общее для всех
|
|
6
|
+
* моделей и смотрит каждый шаг ответа отдельно. Нужен опорный признак — он же место среза: `role` — строка с ролью сразу
|
|
7
|
+
* после законченного ответа (с начала строки, после конца предложения, без двоеточия). Пара `</think>` в шаге перед такой
|
|
8
|
+
* строкой переносит место среза на первый `</think>`. Второй признак зависит от строки с ролью:
|
|
9
|
+
* - `userНу…` (роль слиплась с репликой): `think` — служебная метка в шаге (`</think>`, `<|im_end|>`, `<|…|>`);
|
|
10
|
+
* `restart` — после неё ещё одна строка с ролью или склейка русских предложений «месяц.Считать»; `tool` — вызов
|
|
11
|
+
* инструмента в том же шаге после неё;
|
|
12
|
+
* - `user` одной строкой: `think` — служебная метка в шаге; `restart` — ещё одна строка с ролью после неё;
|
|
13
|
+
* - метка шаблона (`<|im_start|>user`, `<|User|>`): `think` — метка конца своего хода перед ней (`<|im_end|>`);
|
|
14
|
+
* `restart` — ещё одна строка с ролью или склейка после неё.
|
|
15
|
+
* Код (блоки ```, отступ в 4 пробела, `…`, ``…``) и цитаты (`> …`) не смотрятся. Роли за обоих (`user` и `assistant`
|
|
16
|
+
* строками или метками шаблона) — запись диалога или шаблона по просьбе, а не срыв. Метка в начале шага (DeepSeek
|
|
17
|
+
* ставит `</think>` перед текстом шага) — не след срыва.
|
|
18
|
+
*/
|
|
19
|
+
export type GarbleSign = "think" | "role" | "restart" | "tool";
|
|
20
|
+
/** Место срыва: часть с текстом, знак в ней, с которого начинается хвост, и найденные признаки. */
|
|
21
|
+
export interface GarbledTail {
|
|
22
|
+
partIndex: number;
|
|
23
|
+
offset: number;
|
|
24
|
+
signs: GarbleSign[];
|
|
25
|
+
}
|
|
26
|
+
/** Ищет срыв в частях ответа. `null` — срыва нет. */
|
|
27
|
+
export declare function findGarbledTail(parts: readonly MessagePart[]): GarbledTail | null;
|
|
28
|
+
/** Ответ без хвоста срыва и что отрезано. */
|
|
29
|
+
export interface GarbledTailCut {
|
|
30
|
+
parts: MessagePart[];
|
|
31
|
+
signs: GarbleSign[];
|
|
32
|
+
/** Сколько знаков текста и рассуждений отрезано. */
|
|
33
|
+
removedChars: number;
|
|
34
|
+
/** Последний оставшийся текст до места срыва: индекс один и тот же в исходных частях и в обрезанных; -1 — нет. */
|
|
35
|
+
textIndex: number;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Ответ без хвоста срыва: в шаге срыва — текст до места срыва, вызовы инструментов и служебные данные (`data-*`);
|
|
39
|
+
* текст и рассуждения после места срыва в этом шаге отброшены. Следующие шаги — как были: вызовы и запросы
|
|
40
|
+
* подтверждения не пропадают. Обрыв по длине в последнем шаге снят — ответ законченный. `null` — срыва нет или без
|
|
41
|
+
* хвоста в ответе не осталось бы текста.
|
|
42
|
+
*/
|
|
43
|
+
export declare function cutGarbledTail(parts: readonly MessagePart[]): GarbledTailCut | null;
|
|
44
|
+
/**
|
|
45
|
+
* История для модели без хвостов срыва в ответах ассистента. Только в запросе: запись не меняется. Из той же истории —
|
|
46
|
+
* те же байты (кэш модели). Нечего резать — тот же массив, сообщения без срыва — те же объекты.
|
|
47
|
+
*/
|
|
48
|
+
export declare function withoutGarbledTails(history: readonly AgentChatMessage[]): readonly AgentChatMessage[];
|
|
49
|
+
/**
|
|
50
|
+
* Срыв, пока идёт поток ответа модели (каждый шаг попытки). Решение — по тому же правилу, что при сохранении, и только
|
|
51
|
+
* на целых строках: незаконченная строка ждёт конца строки или конца текста. Сработало — текст и рассуждения дальше не
|
|
52
|
+
* идут (ни на экран, ни в запись), вызовы инструментов после этого отброшены до запуска, шаг кончается без вызовов —
|
|
53
|
+
* нового шага нет. Отброшенный вызов при сохранении учитывается через `forCut`: запись и поток решают одинаково.
|
|
54
|
+
*/
|
|
55
|
+
export declare function createGarbledTailGuard(): {
|
|
56
|
+
/** Новая попытка: шаги прежней стёрты — всё сначала. */
|
|
57
|
+
reset(): void;
|
|
58
|
+
/** Модель попытки под присмотром: каждый запрос — новый шаг. */
|
|
59
|
+
wrap(model: LanguageModel): LanguageModel;
|
|
60
|
+
/** Срыв уже замечен в этой попытке: нового шага не нужно. */
|
|
61
|
+
garbled: () => boolean;
|
|
62
|
+
/** Сколько вызовов отброшено до запуска. */
|
|
63
|
+
droppedCalls: () => number;
|
|
64
|
+
/** Части ответа для правила при сохранении: отброшенный вызов — на своём месте, в конце. */
|
|
65
|
+
forCut: (saved: readonly MessagePart[]) => readonly MessagePart[];
|
|
66
|
+
/** Заглушка отброшенного вызова из `forCut`: в запись не идёт. */
|
|
67
|
+
isDroppedCall: (part: MessagePart) => boolean;
|
|
68
|
+
};
|
|
69
|
+
export {};
|
|
@@ -86,7 +86,9 @@ export declare function withTextAt(parts: readonly MessagePart[], index: number,
|
|
|
86
86
|
export declare function createShownSteps(): {
|
|
87
87
|
observe(chunk: UIMessageChunk): void;
|
|
88
88
|
/** Сколько шагов стереть с конца, чтобы убрать шаг последнего текста; `null` — текста на экране нет. */
|
|
89
|
-
resetsToLastText()
|
|
89
|
+
resetsToLastText: () => number | null;
|
|
90
|
+
/** То же для текста `fromEnd`-го с конца (0 — последний); `null` — такого текста на экране нет. */
|
|
91
|
+
resetsToText: (fromEnd: number) => number | null;
|
|
90
92
|
};
|
|
91
93
|
/**
|
|
92
94
|
* Куски, которыми экран заменяет показанное исправленным (`web: "replace"`): сброс `resets` шагов с конца и шаги от
|
|
@@ -24,6 +24,7 @@ import type { PreparedTurn } from "../turn-hooks/turn-preparation.js";
|
|
|
24
24
|
import type { ChatClosure } from "../access/chat-closure.js";
|
|
25
25
|
import type { HiddenStateKey } from "../app-state/state-messages.js";
|
|
26
26
|
import type { ContextBlocksSource } from "../frozen-context/context-blocks.js";
|
|
27
|
+
import type { AgentViewer, AgentViewerSource } from "./agent-viewer.js";
|
|
27
28
|
import type { AppStateAccess, AppStateValues } from "../app-state/app-state.js";
|
|
28
29
|
/** Выбранная модель: по ней приложение решает, какие инструменты ей дать. */
|
|
29
30
|
export interface AgentToolsModel {
|
|
@@ -89,6 +90,11 @@ export interface AgentToolsModel {
|
|
|
89
90
|
* Нет — вызов вне хода (сжатие, фоновая задача): вне хода приложение пишет в данные чата.
|
|
90
91
|
*/
|
|
91
92
|
state?: AppStateAccess;
|
|
93
|
+
/**
|
|
94
|
+
* Права человека на ход (`agent.viewer`): один объект на все попытки и шаги хода, тот же, что у `contextBlocks`.
|
|
95
|
+
* Нет `agent.viewer` (или модель собрана вне хода) — поля нет.
|
|
96
|
+
*/
|
|
97
|
+
viewer?: AgentViewer;
|
|
92
98
|
}
|
|
93
99
|
/** Инструменты агента: один набор для всех моделей или набор, зависящий от выбранной модели. */
|
|
94
100
|
export type AgentToolsSource = ToolSet | ((model: AgentToolsModel) => ToolSet);
|
|
@@ -174,6 +180,8 @@ export interface AgentTurnInput {
|
|
|
174
180
|
stepContext?: StepContext;
|
|
175
181
|
/** Срок ответа `stepContext`, мс, уже проверенный; `Infinity` — без срока. Нет — 5 с. */
|
|
176
182
|
stepContextTimeoutMs?: number;
|
|
183
|
+
/** Права человека: один вызов в начале хода, до блоков и инструментов (`agent.viewer`). */
|
|
184
|
+
viewer?: AgentViewerSource;
|
|
177
185
|
/** Блоки приложения для вопроса хода: один опрос в начале хода, дальше застывают в ответе (`agent.contextBlocks`). */
|
|
178
186
|
contextBlocks?: ContextBlocksSource;
|
|
179
187
|
/** Срок ответа `contextBlocks`, мс, уже проверенный; `Infinity` — без срока. Нет — 5 с. */
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AgentChatMessage } from "../conversation/types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Реплика сотрудника — не вопрос к агенту и не его ход: в запросе она идёт в одном сообщении с соседними словами человека
|
|
4
|
+
* (до неё и после неё), через пустую строку. Иначе два сообщения человека подряд получили бы между собой пометку
|
|
5
|
+
* «ход оборвался» (`withUnansweredMarked`), хотя хода не было. Склейка зависит только от истории: на каждом ходе те же байты.
|
|
6
|
+
*/
|
|
7
|
+
export declare function withStaffRepliesJoined(history: readonly AgentChatMessage[]): readonly AgentChatMessage[];
|
|
@@ -16,6 +16,16 @@ export declare const MAX_BURST_QUIET_MS = 600000;
|
|
|
16
16
|
export declare const BURST_PENDING_GRACE_MS = 120000;
|
|
17
17
|
/** Чат занят другой копией сервера (её ход или аренда): через столько пачка пробует снова. */
|
|
18
18
|
export declare const BURST_BUSY_RETRY_MS = 1000;
|
|
19
|
+
/**
|
|
20
|
+
* Запись ждущей пачки в хранилище (`savePendingBurst`) удерживается копией, что ждёт окно, ещё столько после конца окна;
|
|
21
|
+
* пока копия ждёт или решает, она продлевает удержание каждые `BURST_HOLD_RENEW_MS`. Срок вышел — пачку считают
|
|
22
|
+
* брошенной (копия упала): её берёт обход другой копии или следующего запуска.
|
|
23
|
+
*/
|
|
24
|
+
export declare const BURST_HOLD_MS = 30000;
|
|
25
|
+
/** Как часто живая копия продлевает удержание своей ждущей пачки. */
|
|
26
|
+
export declare const BURST_HOLD_RENEW_MS = 10000;
|
|
27
|
+
/** Сколько брошенных пачек одна копия решает одновременно: обход берёт их порциями, пока не кончатся. */
|
|
28
|
+
export declare const BURST_SWEEP_LIMIT = 10;
|
|
19
29
|
/** `at` — время записи сообщения, мс: по нему решается, одна ли пачка у соседей (`continuesBurst`). */
|
|
20
30
|
export declare function burstPart(quietMs: number, at: number): AgentChatMessage["parts"][number];
|
|
21
31
|
export declare function isBurstMessage(message: Pick<AgentChatMessage, "role" | "parts">): boolean;
|
|
@@ -74,6 +84,8 @@ export interface BurstWaits {
|
|
|
74
84
|
wait(chatId: string, ms: number, cancelled: () => Error): Promise<void>;
|
|
75
85
|
/** Кончает ожидания чата сейчас: их пачки решают заново (например, после «Стоп»). */
|
|
76
86
|
wake(chatId: string): void;
|
|
87
|
+
/** Кончает все ожидания сейчас: пачки решают заново по часам службы (`tickBursts`). */
|
|
88
|
+
wakeAll(): void;
|
|
77
89
|
/** Ждёт ли в чате хоть одно окно или решение по пачке (`holding`). */
|
|
78
90
|
has(chatId: string): boolean;
|
|
79
91
|
/** Решение по пачке идёт: чат считается ждущим, пока `work` не кончится. */
|
|
@@ -18,6 +18,11 @@ export type ChatEvent = {
|
|
|
18
18
|
* мессенджер его не шлют. Нет — доставляют каналы, как прежде.
|
|
19
19
|
*/
|
|
20
20
|
deliveredByApp?: true;
|
|
21
|
+
/**
|
|
22
|
+
* Ход начал сервер сам, и потока у вызова нет: ответ на пачку, брошенную упавшей или перезапущенной копией
|
|
23
|
+
* (`sendBurstMessage`). Каналы провожают такой ход, как ход по служебному сообщению. Нет — поток у вызова.
|
|
24
|
+
*/
|
|
25
|
+
unattended?: true;
|
|
21
26
|
} | {
|
|
22
27
|
type: "turn-finished";
|
|
23
28
|
ownerId: string;
|
|
@@ -121,6 +126,8 @@ export interface ChatEvents {
|
|
|
121
126
|
/** Возвращает отписку. */
|
|
122
127
|
subscribe(listener: (event: ChatEvent) => void): () => void;
|
|
123
128
|
emit(event: ChatEvent): void;
|
|
129
|
+
/** Есть ли слушатели. Необязательный: своя рассылка (общая шина копий) может не знать — тогда не проверяется. */
|
|
130
|
+
hasListeners?(): boolean;
|
|
124
131
|
}
|
|
125
132
|
/** Рассылка событий в памяти процесса. Упавший слушатель не мешает остальным. */
|
|
126
133
|
export declare function createChatEvents(logError?: (error: unknown) => void): ChatEvents;
|
|
@@ -45,6 +45,7 @@ import type { BeforeCreateChatHandler } from "./access/chat-creation.js";
|
|
|
45
45
|
import type { AskModel, PrepareTurn, RecordUsage } from "./turn-hooks/prepare-turn.js";
|
|
46
46
|
import type { AfterTurnHandler, AppEvent, BeforeTurnHandler, StaffActor, TurnAction, TurnNotStartedHandler } from "./turn-hooks/turn-hooks.js";
|
|
47
47
|
import type { AgentWidgetSettings } from "./widgets/widget-types.js";
|
|
48
|
+
import type { AgentViewerSource } from "./agent-run/agent-viewer.js";
|
|
48
49
|
import type { TurnFinishedHandler } from "./agent-run/turn-finished.js";
|
|
49
50
|
export { DEFAULT_TEXT_MODE_NOTE, DEFAULT_VOICE_MODE_HINT };
|
|
50
51
|
/**
|
|
@@ -203,6 +204,16 @@ export interface AgentSettings {
|
|
|
203
204
|
* `private: true` — блок не показывается в групповом ходе. Не задано — запрос побайтно прежний.
|
|
204
205
|
*/
|
|
205
206
|
contextBlocks?: ContextBlocksSource;
|
|
207
|
+
/**
|
|
208
|
+
* Права человека из базы приложения (админ ли, тариф): зовётся один раз в начале хода, до `contextBlocks` и сборки
|
|
209
|
+
* инструментов; повторная попытка и запасной провайдер не зовут снова. Итог — полем `viewer` в `contextBlocks` и в
|
|
210
|
+
* модели инструментов (`AgentToolsModel.viewer`), в запрос к модели не идёт. Ещё зовётся перед сжатием истории и
|
|
211
|
+
* запуском фонового помощника: их инструменты те же (канал и группа — по последнему сообщению человека; срок ответа
|
|
212
|
+
* 10 с). Бросила или вернула не объект — ход кончается ошибкой
|
|
213
|
+
* (`other`), в журнал сервера — `[agent.viewer] …`. `isAdmin` читают пакеты (серверы MCP, навыки). Не задано —
|
|
214
|
+
* поля `viewer` нет.
|
|
215
|
+
*/
|
|
216
|
+
viewer?: AgentViewerSource;
|
|
206
217
|
/**
|
|
207
218
|
* Срок ответа `contextBlocks`, мс. Не ответила или бросила — блоки приложения берутся из прошлого ответа чата, в
|
|
208
219
|
* журнал сервера — строка `[блоки контекста] …`; функция получает отмену через `abortSignal`. «Стоп» отменяет её
|
|
@@ -245,6 +256,15 @@ export interface SendBurstOptions extends SendMessageOptions {
|
|
|
245
256
|
* умолчанию 3000; целое от 0 до 600 000. 0 — без ожидания, но сообщения, пришедшие во время хода, всё равно склеиваются.
|
|
246
257
|
*/
|
|
247
258
|
quietMs?: number;
|
|
259
|
+
/**
|
|
260
|
+
* Пачка переживает перезапуск без повтора вызова: хранилище с ждущими пачками (`savePendingBurst`) помнит её до
|
|
261
|
+
* ответа, а упала или перезапустилась копия посреди окна — ответит обход (`recoverInterruptedAnswers`, `tickBursts`).
|
|
262
|
+
* Такой ход приходит событием `turn-started` с `unattended: true`; поток — `subscribe` (ход уже кончился — `null`:
|
|
263
|
+
* ответ в истории). Запись пачки не удалась — вызов отказывает до записи сообщения. Своей очереди повторов каналу
|
|
264
|
+
* не нужно. Нет — как раньше: после перезапуска канал повторяет вызов с тем же `message.id` (так делают мессенджеры
|
|
265
|
+
* SDK; обход и повтор вместе дали бы ответ, который повтор не напечатает).
|
|
266
|
+
*/
|
|
267
|
+
answerAfterRestart?: boolean;
|
|
248
268
|
}
|
|
249
269
|
/** Сообщение пачки записано; ход на пачку — позже. */
|
|
250
270
|
export interface BurstMessageResult {
|
|
@@ -781,10 +801,19 @@ export interface ChatService {
|
|
|
781
801
|
* на пачку с `burstMessageIds`, касания сценария (`deliverNotices` с `wakeAgent`) пока ждут (`busy`). Решение — по
|
|
782
802
|
* общей истории: годится для нескольких копий сервера. Повтор с тем же id (сервер упал посреди окна) не пишет копию,
|
|
783
803
|
* а доводит ход. Права и закрытый чат — до записи, как у `sendMessage`; лимит расходов — при начале хода.
|
|
804
|
+
* Окно идёт по часам службы (`now`, ручной обход — `tickBursts`). С `answerAfterRestart` пачка переживает перезапуск
|
|
805
|
+
* без повтора вызова: ответит обход, событием `turn-started` с `unattended: true`.
|
|
784
806
|
* @throws ChatBusyError ответ идёт на другой копии сервера или служба останавливается.
|
|
785
807
|
* @throws TypeError неверное `quietMs`.
|
|
786
808
|
*/
|
|
787
809
|
sendBurstMessage(userId: string, chatId: string, message: IncomingUserMessage, options?: SendBurstOptions): Promise<BurstMessageResult>;
|
|
810
|
+
/**
|
|
811
|
+
* Обход пачек сейчас: окна этой копии решают заново по часам службы (`now`), брошенные пачки из хранилища (их копия
|
|
812
|
+
* упала, срок удержания `BURST_HOLD_MS` после окна вышел) получают ход. Сам идёт раз в `STALE_ANSWERS_SWEEP_EVERY_MS`
|
|
813
|
+
* после `recoverInterruptedAnswers`. Звать вручную — когда часы службы подменены (тесты, прогон сценариев): сдвинули
|
|
814
|
+
* часы, вызвали, ход начался без настоящего ожидания.
|
|
815
|
+
*/
|
|
816
|
+
tickBursts(): Promise<void>;
|
|
788
817
|
/**
|
|
789
818
|
* Поправка, пока агент отвечает: сообщение человека пишется сразу (ребёнок идущего ответа или прошлой поправки), агент
|
|
790
819
|
* берёт его на ближайшей границе шага — ответ закрывается, и ход продолжается новым ответом на поправки (поток —
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { MessageAttachments } from "../attachments/message-attachments.js";
|
|
2
2
|
import type { AgentToolsSource } from "../agent-run/run-agent-turn.js";
|
|
3
|
+
import type { AgentViewerSource } from "../agent-run/agent-viewer.js";
|
|
3
4
|
import type { TurnObserver } from "../agent-run/turn-observer.js";
|
|
4
5
|
import type { AgentChatMessage, Chat, StoredMessage } from "../conversation/types.js";
|
|
5
6
|
import type { ModelCatalog } from "../models/model-catalog.js";
|
|
@@ -92,9 +93,11 @@ export interface CompactionServiceOptions {
|
|
|
92
93
|
storage: ChatStorage;
|
|
93
94
|
catalog: ModelCatalog;
|
|
94
95
|
env: Record<string, string | undefined>;
|
|
96
|
+
/** `viewer` — права человека: инструменты сжатия те же, что у хода (кэш). */
|
|
95
97
|
agent: {
|
|
96
98
|
instructions: string;
|
|
97
99
|
tools?: AgentToolsSource;
|
|
100
|
+
viewer?: AgentViewerSource;
|
|
98
101
|
};
|
|
99
102
|
attachments?: MessageAttachments;
|
|
100
103
|
/** `null` — автосжатие выключено; записи, уже сделанные, всё равно применяются к истории. */
|
|
@@ -188,8 +188,10 @@ export interface StoredBranchSelection {
|
|
|
188
188
|
* `first_message` — ход, в котором агент пишет первым (касание, утро сценария): `deliverNotices({ usageSource })`.
|
|
189
189
|
* `voice_input` — диктовка через сервер (`@ykurilov/agent-chat-voice`, режим `relay`): `inputTokens` — секунды звука,
|
|
190
190
|
* `modelId` — модель распознавания. `voice_polish` — чистка надиктованного текста моделью.
|
|
191
|
+
* `mcp_tool` — вызов инструмента сервера MCP (`@ykurilov/agent-chat-mcp`), если сервер сообщил стоимость: `modelId` —
|
|
192
|
+
* имя инструмента `<префикс>__<имя>`, `providerId` — `mcp`, токенов нет.
|
|
191
193
|
*/
|
|
192
|
-
export declare const BUILT_IN_USAGE_SOURCES: readonly ["answer", "answer_correction", "compaction", "memory", "speech", "sounds", "web_tools", "images", "helper", "background", "judge", "first_message", "voice_input", "voice_polish"];
|
|
194
|
+
export declare const BUILT_IN_USAGE_SOURCES: readonly ["answer", "answer_correction", "compaction", "memory", "speech", "sounds", "web_tools", "images", "helper", "background", "judge", "first_message", "voice_input", "voice_polish", "mcp_tool"];
|
|
193
195
|
/** Штатные виды расхода SDK. */
|
|
194
196
|
export type BuiltInUsageSource = (typeof BUILT_IN_USAGE_SOURCES)[number];
|
|
195
197
|
/**
|
|
@@ -2,6 +2,7 @@ import type { AgentAudience, AgentChannel, ChatSubject } from "../conversation/t
|
|
|
2
2
|
import type { AppStateValues } from "../app-state/app-state.js";
|
|
3
3
|
import type { AgentNotice } from "../notices/agent-notice.js";
|
|
4
4
|
import type { StaffActor } from "../turn-hooks/turn-hooks.js";
|
|
5
|
+
import type { AgentViewer } from "../agent-run/agent-viewer.js";
|
|
5
6
|
/** Тип служебной части ответа с застывшими блоками хода. Клиенту не уходит, модели — только текстом блоков. */
|
|
6
7
|
export declare const FROZEN_CONTEXT_PART_TYPE = "data-frozen-context";
|
|
7
8
|
/** Блок контекста: ключ уникален в наборе, текст идёт модели как есть. Личный блок не показывается в групповом ходе. */
|
|
@@ -44,6 +45,8 @@ export interface ContextBlocksInput {
|
|
|
44
45
|
audience: AgentAudience;
|
|
45
46
|
/** Сотрудник, который делает ход в копии чата человека; нет — ход делает сам владелец. */
|
|
46
47
|
staff?: StaffActor;
|
|
48
|
+
/** Права человека на ход (`agent.viewer`), тот же объект, что у инструментов. Нет `agent.viewer` — поля нет. */
|
|
49
|
+
viewer?: AgentViewer;
|
|
47
50
|
modelId: string;
|
|
48
51
|
providerId: string;
|
|
49
52
|
/** Отмена: «Стоп», срок функции (`agent.contextBlocksTimeoutMs`), срок хода. */
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AgentTurnInput } from "../agent-run/run-agent-turn.js";
|
|
2
|
+
import type { AgentViewer } from "../agent-run/agent-viewer.js";
|
|
2
3
|
import type { AppStateValues } from "../app-state/app-state.js";
|
|
3
4
|
import type { AgentChatMessage } from "../conversation/types.js";
|
|
4
5
|
import type { FrozenContextData } from "./context-blocks.js";
|
|
@@ -10,6 +11,7 @@ export declare function frozenContextForTurn(input: AgentTurnInput, turn: {
|
|
|
10
11
|
history: readonly AgentChatMessage[];
|
|
11
12
|
answer?: AgentChatMessage;
|
|
12
13
|
state: AppStateValues;
|
|
14
|
+
viewer?: AgentViewer;
|
|
13
15
|
}): Promise<{
|
|
14
16
|
data: FrozenContextData;
|
|
15
17
|
fresh: boolean;
|
|
@@ -23,4 +25,5 @@ export declare function resumeNoteForTurn(input: AgentTurnInput, turn: {
|
|
|
23
25
|
history: readonly AgentChatMessage[];
|
|
24
26
|
answer: AgentChatMessage;
|
|
25
27
|
state: AppStateValues;
|
|
28
|
+
viewer?: AgentViewer;
|
|
26
29
|
}): Promise<FrozenContextData | undefined>;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -39,7 +39,7 @@ export type { OpenRouterMaxPrice, OpenRouterProviderOptions } from "./models/pro
|
|
|
39
39
|
export { aliveAtByLocalClock, assertKnownFormatVersion, ChatAlreadyExistsError, DuplicateChatSubjectError, DuplicateMessageError, MessageAlreadyFinishedError, MessageNotAwaitingError, RELEASED_ANSWER_ALIVE_AT, sameMessageParts, silenceBoundaryByLocalClock, UnsupportedFormatVersionError, } from "./storage/chat-storage.js";
|
|
40
40
|
export { assertChatData, assertChatDataFilter, CHAT_DATA_MAX_BYTES, ChatDataTooLargeError, chatDataFields, chatDataMatches, chatDataOfInput, mergeChatData } from "./storage/chat-data.js";
|
|
41
41
|
export type { ChatDataFilter, ChatDataFilterValue } from "./storage/chat-data.js";
|
|
42
|
-
export type { AwaitingMessagePatch, ChatListOptions, ChatStorage, ChatSubjectFilter, FinishedMessagePatch, ImportedChat, MessageLink, MessagePartsLink, NewChatInput, SpendingLimitStateRecord, StreamingMessagePatch, } from "./storage/chat-storage.js";
|
|
42
|
+
export type { AwaitingMessagePatch, ChatListOptions, ChatStorage, ChatSubjectFilter, FinishedMessagePatch, ImportedChat, MessageLink, MessagePartsLink, NewChatInput, SpendingLimitStateRecord, StoredPendingBurst, StreamingMessagePatch, } from "./storage/chat-storage.js";
|
|
43
43
|
export { SpendingLimitReachedError } from "./spending/spending-limits.js";
|
|
44
44
|
export { APP_EVENT_DATA_MAX_BYTES, APP_EVENT_TYPE_MAX_LENGTH, isTurnRefusalCode, normalizeTurnRefusal, TURN_REFUSAL_MESSAGE_MAX_LENGTH, TurnRefusedError, validateAppEvent, } from "./turn-hooks/turn-hooks.js";
|
|
45
45
|
export { DEFAULT_AFTER_TURN_ATTEMPTS, DEFAULT_AFTER_TURN_TIMEOUT_MS } from "./turn-hooks/after-turn.js";
|
|
@@ -71,13 +71,15 @@ export type { RunningAnswerRegistry } from "./running-answers/running-answer-reg
|
|
|
71
71
|
export { ChatNotFoundError, createChatService, InvalidMessageError, InvalidModelChoiceError, NothingToRetryError, } from "./chat-service.js";
|
|
72
72
|
export type { ActionOptions, AgentSettings, ChatBranchForks, ChatCompactions, ChatHistory, ChatListItem, ChatService, ChatInstructions, ChatInstructionsTurn, ChatState, CompactionMarker, CompactionOutcome, CompactionSettings, FlushOptions, CheckReplyInput, CheckReplyResult, IncomingUserMessage, ModelChoice, ModelChosenHandler, ModelMenu, RecoverInterruptedAnswersOptions, SendMessageOptions, SendBurstOptions, BurstMessageResult, SteerResult, } from "./chat-service.js";
|
|
73
73
|
export { STEER_PART_TYPE, isCorrection } from "./steering/turn-steering.js";
|
|
74
|
-
export { BURST_PART_TYPE, DEFAULT_BURST_QUIET_MS, isBurstMessage } from "./bursts/message-bursts.js";
|
|
74
|
+
export { BURST_HOLD_MS, BURST_PART_TYPE, BURST_SWEEP_LIMIT, DEFAULT_BURST_QUIET_MS, isBurstMessage } from "./bursts/message-bursts.js";
|
|
75
75
|
export { GREETING_LEAD } from "./agent-run/greeting-lead.js";
|
|
76
76
|
export { GREETING_MAX_LENGTH, IMPORT_MAX_IMAGE_BYTES, IMPORT_MAX_IMAGES, IMPORT_MAX_MESSAGES, SUBJECT_DATA_MAX_BYTES, SUBJECT_KEY_MAX_LENGTH } from "./subject-chats/subject-chats.js";
|
|
77
77
|
export type { ChatGreeting, ImportChatInput, ImportedImagePart, ImportedMessageInput, OpenSubjectChatInput, OpenSubjectChatResult, } from "./subject-chats/subject-chats.js";
|
|
78
78
|
export { AttachmentRejectedError, withoutFileParts } from "./attachments/message-attachments.js";
|
|
79
79
|
export type { IncomingFilePart, MessageAttachments, StoredFilePart } from "./attachments/message-attachments.js";
|
|
80
80
|
export type { AgentToolsModel, AgentToolsSource } from "./agent-run/run-agent-turn.js";
|
|
81
|
+
export { AgentViewerError } from "./agent-run/agent-viewer.js";
|
|
82
|
+
export type { AgentViewer, AgentViewerInput, AgentViewerSource } from "./agent-run/agent-viewer.js";
|
|
81
83
|
export type { StepContext, StepContextInput } from "./agent-run/step-context.js";
|
|
82
84
|
export type { ContextBlock, ContextBlockEntry, ContextBlocksInput, ContextBlocksSource, HiddenContextBlock } from "./frozen-context/context-blocks.js";
|
|
83
85
|
export { FROZEN_CONTEXT_PART_TYPE } from "./frozen-context/context-blocks.js";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ChatClosure } from "../access/chat-closure.js";
|
|
2
2
|
import type { ModelChoice } from "../models/model-choice.js";
|
|
3
3
|
import type { ChatDataFilter } from "./chat-data.js";
|
|
4
|
+
import type { TurnRequestContext } from "../page-context/page-context.js";
|
|
4
5
|
import type { MemoryError, MemoryNote, MemoryNoteInput, MemoryRevertGuard, MemoryWriteGuard } from "../memory/long-term-memory.js";
|
|
5
6
|
import type { ExternalChannel } from "../conversation/types.js";
|
|
6
7
|
import type { MemoryMergeApplyResult, MemoryMergeInput, MemoryMergeRevertResult, MemoryOptimizationOutcome, MemoryOptimizationRules, MemoryOptimizationRun, MemoryOptimizationStatus, MergedMemoryNote } from "../memory/memory-optimization.js";
|
|
@@ -208,6 +209,38 @@ export interface ChatStorage {
|
|
|
208
209
|
* Необязательный: без него такие чаты свободны через срок аренды.
|
|
209
210
|
*/
|
|
210
211
|
clearChatTurnLeases?(): Promise<void>;
|
|
212
|
+
/**
|
|
213
|
+
* Пачка сообщений человека ждёт хода (`sendBurstMessage`): запись на чат, новая заменяет прежнюю. Нужна, чтобы пачку,
|
|
214
|
+
* чьё окно тишины ждала упавшая или перезапущенная копия сервера, ответила другая копия или следующий запуск.
|
|
215
|
+
*
|
|
216
|
+
* Необязательный (вместе с `takeAbandonedBursts`, `renewPendingBurst` и `removePendingBurst`): без него окно тишины живёт только в памяти
|
|
217
|
+
* копии — после перезапуска пачка ответит, только когда приложение повторит вызов.
|
|
218
|
+
*/
|
|
219
|
+
savePendingBurst?(burst: StoredPendingBurst): Promise<void>;
|
|
220
|
+
/**
|
|
221
|
+
* Брошенные пачки — `heldUntil` не позже `abandonedBefore` — атомарно переходят к `holder` до `heldUntil` и
|
|
222
|
+
* возвращаются, не больше `limit`, ранние `dueAt` первыми. Две копии разом одну пачку не получат. `exceptHolder` —
|
|
223
|
+
* записи этого держателя не берутся, каким бы ни был их срок.
|
|
224
|
+
*/
|
|
225
|
+
takeAbandonedBursts?(take: {
|
|
226
|
+
abandonedBefore: string;
|
|
227
|
+
holder: string;
|
|
228
|
+
heldUntil: string;
|
|
229
|
+
limit: number;
|
|
230
|
+
exceptHolder?: string;
|
|
231
|
+
}): Promise<StoredPendingBurst[]>;
|
|
232
|
+
/**
|
|
233
|
+
* Продлить удержание до `heldUntil`, если запись чата всё ещё о сообщении `messageId` и держит её `holder`. Продлено —
|
|
234
|
+
* `true`; запись другая, взята другой копией или снята — `false`, ничего не меняется.
|
|
235
|
+
*/
|
|
236
|
+
renewPendingBurst?(renew: {
|
|
237
|
+
chatId: string;
|
|
238
|
+
messageId: string;
|
|
239
|
+
holder: string;
|
|
240
|
+
heldUntil: string;
|
|
241
|
+
}): Promise<boolean>;
|
|
242
|
+
/** Снять запись пачки чата, если она всё ещё о сообщении `messageId`; о другом сообщении или нет — тихо ничего. */
|
|
243
|
+
removePendingBurst?(chatId: string, messageId: string): Promise<void>;
|
|
211
244
|
/** Новая запись сжатия. Записи только добавляются; сообщения не меняются. Повтор id — ошибка. */
|
|
212
245
|
insertCompaction(record: StoredCompaction): Promise<void>;
|
|
213
246
|
/** Записи сжатия чата в порядке добавления. */
|
|
@@ -370,3 +403,17 @@ export declare class UnsupportedFormatVersionError extends Error {
|
|
|
370
403
|
}
|
|
371
404
|
/** Для адаптеров: сообщение из будущей версии формата прочитать нельзя. */
|
|
372
405
|
export declare function assertKnownFormatVersion(message: Pick<StoredMessage, "id" | "formatVersion">): void;
|
|
406
|
+
/** Пачка, ждущая хода (`ChatStorage.savePendingBurst`). Время — ISO 8601 по часам службы. */
|
|
407
|
+
export interface StoredPendingBurst {
|
|
408
|
+
chatId: string;
|
|
409
|
+
ownerId: string;
|
|
410
|
+
/** Последнее сообщение пачки: на него ход. */
|
|
411
|
+
messageId: string;
|
|
412
|
+
/** Конец окна тишины: раньше ход не начинается. */
|
|
413
|
+
dueAt: string;
|
|
414
|
+
/** Копия сервера, которая ждёт окно, и до какого времени её ожидание считается живым. Прошло — пачка брошена. */
|
|
415
|
+
holder: string;
|
|
416
|
+
heldUntil: string;
|
|
417
|
+
/** Страница хода (`SendMessageOptions.page`): ход после перезапуска идёт с ней. */
|
|
418
|
+
page?: TurnRequestContext;
|
|
419
|
+
}
|
|
@@ -33,4 +33,9 @@ export declare function describeChatStorageContract(createStorage: () => ChatSto
|
|
|
33
33
|
* `clearChatTurnLeases`) обязана быть.
|
|
34
34
|
*/
|
|
35
35
|
chatTurnLease?: "required";
|
|
36
|
+
/**
|
|
37
|
+
* `pendingBursts: "required"` — ждущие пачки (`savePendingBurst`, `takeAbandonedBursts`, `renewPendingBurst`,
|
|
38
|
+
* `removePendingBurst`) обязаны быть.
|
|
39
|
+
*/
|
|
40
|
+
pendingBursts?: "required";
|
|
36
41
|
}): void;
|
|
@@ -9,6 +9,8 @@ export interface TurnGateInput extends AbandonedAnswers {
|
|
|
9
9
|
messageId?: string;
|
|
10
10
|
/** Ход на пачку сообщений (`sendBurstMessage`): их id по порядку, последнее — `messageId`. */
|
|
11
11
|
burstMessageIds?: string[];
|
|
12
|
+
/** Ход на пачку без вызова (обход ждущих пачек). */
|
|
13
|
+
unattended?: true;
|
|
12
14
|
}
|
|
13
15
|
/**
|
|
14
16
|
* С чем пропущен ход: обычный ход модели, «записать и промолчать» (`hold`) или готовый текст без модели (`reply`).
|
|
@@ -49,6 +49,11 @@ export type BeforeTurnHandler = (input: {
|
|
|
49
49
|
* `text` — их тексты через пустую строку. Одно решение на всю пачку; счёт сообщений — по длине списка.
|
|
50
50
|
*/
|
|
51
51
|
burstMessageIds?: string[];
|
|
52
|
+
/**
|
|
53
|
+
* Ход на пачку начал сам сервер после перезапуска или за упавшую копию (`answerAfterRestart`): вызова, который ждёт
|
|
54
|
+
* ответ или отказ, нет. Отказ дойдёт только до журнала — сообщить человеку может само приложение.
|
|
55
|
+
*/
|
|
56
|
+
unattended?: true;
|
|
52
57
|
} & AbandonedAnswers) => Promise<BeforeTurnDecision | void> | BeforeTurnDecision | void;
|
|
53
58
|
/**
|
|
54
59
|
* Ответы агента, которые уйдут с текущей ветки, если ход начнётся (`regenerate`, `edit`, `retry`): по ним приложение откатывает
|
|
@@ -94,6 +99,8 @@ export type TurnNotStartedHandler = (input: {
|
|
|
94
99
|
text?: string;
|
|
95
100
|
/** Пачка сообщений, как в `beforeTurn`. */
|
|
96
101
|
burstMessageIds?: string[];
|
|
102
|
+
/** Ход без вызова, как в `beforeTurn`: об отказе (например, `spending_limit`) человеку может сказать только приложение. */
|
|
103
|
+
unattended?: true;
|
|
97
104
|
reason: TurnNotStartedReason;
|
|
98
105
|
} & AbandonedAnswers) => Promise<void> | void;
|
|
99
106
|
/** Кто остановил ход: человек («Стоп»), общий срок хода (`agent.turnTimeoutMs`) или остановка службы (`flush`). */
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/turn-hooks/turn-hooks.ts"],"sourcesContent":["import type { AgentChannel, Chat, ChatErrorKind, ChatSubject, FinalAnswerStatus, StoredMessage, TokenUsage } from \"../conversation/types\";\n\n/** Что человек делает с чатом, когда приложение решает, пускать ли ход. */\nexport type TurnAction = \"send\" | \"edit\" | \"retry\" | \"regenerate\" | \"approval\" | \"widget\" | \"notice\";\n\n/** Отказ приложения в ходе: код для программы, текст для человека. */\nexport interface TurnRefusal {\n /** Латиница в нижнем регистре, цифры и `.`, `_`, `-`; от 1 до 64 знаков. */\n code: string;\n /** До 300 знаков; длиннее — обрезается. */\n message: string;\n}\n\n/**\n * Сотрудник, который действует в чужом чате (копия чата человека в песочнице). Пока он есть у хода (`model.staff`),\n * инструменты приложения данные `ownerId` только читают: любая запись в них запрещена — это обязанность инструментов.\n */\nexport interface StaffActor {\n id: string;\n}\n\n/** Событие приложения в ответе агента: `type` — 1–100 знаков, `data` — JSON до 16 КБ. */\nexport interface AppEvent {\n type: string;\n data?: unknown;\n}\n\n/** Записать сообщение человека без хода: агент промолчит (рамки дня, пауза сценария). */\nexport interface TurnHold {\n hold: true;\n}\n\n/** Ход с готовым текстом, без модели: путь `reply` из `deliverNotices`. */\nexport interface TurnReply {\n reply: { text: string };\n}\n\n/** Что `beforeTurn` может решить, кроме «пускать»: отказ, «записать без хода» или «ответить готовым текстом». */\nexport type BeforeTurnDecision = TurnRefusal | TurnHold | TurnReply;\n\n/**\n * Проверка приложения перед ходом: вернуть отказ — ход не начнётся и ничего не запишется. `{ hold: true }` и\n * `{ reply }` — с 0.49 (сценарии): сообщение записывается, хода модели нет.\n */\nexport type BeforeTurnHandler = (input: {\n userId: string;\n chat: Chat;\n action: TurnAction;\n /** Текст сообщения человека (`send`, `edit`). */\n text?: string;\n /** Id сообщения человека (`send`, `edit`): по нему приложение свяжет свой резерв с ходом. */\n messageId?: string;\n /**\n * Ход на пачку сообщений человека (`sendBurstMessage`): id всех её сообщений по порядку, последнее — `messageId`, а\n * `text` — их тексты через пустую строку. Одно решение на всю пачку; счёт сообщений — по длине списка.\n */\n burstMessageIds?: string[];\n} & AbandonedAnswers) => Promise<BeforeTurnDecision | void> | BeforeTurnDecision | void;\n\n/**\n * Ответы агента, которые уйдут с текущей ветки, если ход начнётся (`regenerate`, `edit`, `retry`): по ним приложение откатывает\n * побочные дела брошенных ответов (правки формы и т. п.). Ход не начался (`turnNotStarted`) — ветка прежняя, ничего не\n * ушло: тот хук получает те же поля. Посчитаны по ветке на миг проверки; сообщения нет на ветке — полей нет.\n */\nexport interface AbandonedAnswers {\n /** Ответы агента, которые уходят с ветки, сверху вниз; у `edit` без ответов ниже — пустой список. */\n abandonedAnswerIds?: string[];\n /**\n * Ответ, который заменит новый: переделываемый (`regenerate`), ответ на правленый вопрос (`edit`), если был, или\n * оборванный ответ, который «Повторить» (`retry`) заменяет соседом. У «Повторить» вопроса без ответа полей нет.\n */\n previousAnswerId?: string;\n}\n\n/** Почему ход не начался после того, как `beforeTurn` его пропустил. */\nexport type TurnNotStartedReason =\n /** В чате уже идёт ответ, или служба останавливается (`flush`). */\n | \"busy\"\n /** У человека уже идёт предел одновременных ходов. */\n | \"too_many_turns\"\n /** Лимит расходов исчерпан. */\n | \"spending_limit\"\n /** Сообщение с таким id уже есть (повтор запроса). */\n | \"duplicate_message\"\n /** Запрос неверен: сообщение, файл, решение, ответ на виджет. */\n | \"invalid\"\n /** Чат закрыли, пока ход запускался (`closeChat` между проверкой и началом хода). */\n | \"chat_closed\"\n /** Хода и не будет: проверка без хода (`assertTurnAllowed`), ответ ждёт ещё решений, служебное сообщение без ответа. */\n | \"no_turn\"\n /** Сбой (база, сеть): ход не начат. */\n | \"error\";\n\n/**\n * Ход не начался, хотя `beforeTurn` его пропустил: приложение снимает свой резерв. Каждый пропуск `beforeTurn`\n * закрывается ровно одним: ходом (`afterTurn`) или этим хуком. Ошибка хода наружу прежняя; бросок хука — в журнал.\n */\nexport type TurnNotStartedHandler = (input: {\n userId: string;\n chat: Chat;\n action: TurnAction;\n messageId?: string;\n text?: string;\n /** Пачка сообщений, как в `beforeTurn`. */\n burstMessageIds?: string[];\n reason: TurnNotStartedReason;\n} & AbandonedAnswers) => Promise<void> | void;\n\n/** Кто остановил ход: человек («Стоп»), общий срок хода (`agent.turnTimeoutMs`) или остановка службы (`flush`). */\nexport type TurnStopReason = \"user\" | \"timeout\" | \"shutdown\";\n\n/** Итог законченного хода для приложения. */\nexport interface TurnResult {\n ownerId: string;\n /** Кто сделал ход: владелец чата, а в копии у сотрудника — сотрудник. */\n actorId: string;\n chatId: string;\n messageId: string;\n status: FinalAnswerStatus;\n /** Кто остановил ход; только у `status: \"stopped\"` живого хода (у доделанного после перезапуска нет). */\n stopReason?: TurnStopReason;\n /** Ответ упёрся в предел токенов (`finishReason: \"length\"`, см. `agent.maxOutputTokens`) и оборван. */\n lengthCut?: true;\n /**\n * Ход дошёл до предела шагов (`agent.maxSteps`): последний шаг был итоговым, без инструментов. Это «дошли до потолка»,\n * а не обязательно «недоделали»: модель могла закончить дело как раз на последнем шаге. Статус обычный (`complete`).\n * В журнале SDK — строка `[предел шагов]`.\n */\n stepLimit?: true;\n /** Текстовые части ответа подряд. */\n text: string;\n /** Части ответа, как они записаны, кроме служебной части с блоками контекста (`data-frozen-context`). */\n parts: StoredMessage[\"parts\"];\n usage: TokenUsage | null;\n /** Цена хода в долларах; `null` — у модели нет цены. */\n costUsd: number | null;\n error?: { kind: ChatErrorKind };\n /**\n * Каким действием начат ход (см. `TurnAction`). `null` — итог доделан после перезапуска (`recovered`), а ядро не\n * помнит действие; ход по служебному сообщению и тогда — `notice`.\n */\n trigger: TurnAction | null;\n channel: AgentChannel;\n /**\n * Ход сделал сотрудник в копии чата человека (песочница): `ownerId` и `subject` — человека и его исходного чата.\n * При `staff` в данные человека не писать.\n */\n staff?: StaffActor;\n /** Объект чата (урок, заказ…): ключ и данные чата объекта. Нет — обычный чат. */\n subject?: ChatSubject;\n /** Данные чата (`Chat.data`) на момент итога; нет данных — поля нет. */\n chatData?: Record<string, unknown>;\n /** Итог доделан после перезапуска сервера, а не сразу после хода. */\n recovered: boolean;\n}\n\n/**\n * Хук итога хода: зовётся после записи итога и расхода, до `finish` клиенту; пока он идёт, чат занят. Получает любой\n * итог: законченный, остановленный, с ошибкой, прерванный (в том числе восстановлением после перезапуска) и ждущий\n * человека. Ждущий ответ (`awaiting_approval`) после решения человека продолжается тем же сообщением — хук позовут\n * второй раз, с новым статусом. Хук может быть вызван повторно (повтор после сбоя, доделка после перезапуска):\n * ключ повтора — `messageId` + `status`. Возвращённые `events` ложатся в конец ответа частями `data-app-event`.\n */\nexport type AfterTurnHandler = (\n result: TurnResult,\n) => Promise<{ events?: AppEvent[] } | void> | { events?: AppEvent[] } | void;\n\nexport const TURN_REFUSAL_MESSAGE_MAX_LENGTH = 300;\nexport const APP_EVENT_TYPE_MAX_LENGTH = 100;\nexport const APP_EVENT_DATA_MAX_BYTES = 16 * 1024;\n\nconst TURN_REFUSAL_CODE = /^[a-z0-9_.-]{1,64}$/;\n\nexport function isTurnRefusalCode(code: unknown): code is string {\n return typeof code === \"string\" && TURN_REFUSAL_CODE.test(code);\n}\n\n/** Отказ по правилам: код проверен (иначе `TypeError`), текст обрезан до 300 знаков. */\nexport function normalizeTurnRefusal(refusal: TurnRefusal): TurnRefusal {\n if (!isTurnRefusalCode(refusal?.code)) throw new TypeError(\"Код отказа — [a-z0-9_.-], от 1 до 64 знаков\");\n if (typeof refusal.message !== \"string\") throw new TypeError(\"Текст отказа — строка\");\n // По знакам, а не по половинкам: эмодзи на границе не рвётся.\n const message = Array.from(refusal.message).slice(0, TURN_REFUSAL_MESSAGE_MAX_LENGTH).join(\"\");\n return { code: refusal.code, message };\n}\n\n/** Приложение отказало в ходе: ход не начат, ничего не записано. */\nexport class TurnRefusedError extends Error {\n readonly code: string;\n\n constructor(refusal: TurnRefusal) {\n const normalized = normalizeTurnRefusal(refusal);\n super(normalized.message);\n this.name = \"TurnRefusedError\";\n this.code = normalized.code;\n }\n\n get refusal(): TurnRefusal {\n return { code: this.code, message: this.message };\n }\n}\n\n/** Событие по правилам или `TypeError`. Отдаёт копию: данные прошли через JSON. */\nexport function validateAppEvent(event: unknown): AppEvent {\n if (event === null || typeof event !== \"object\") throw new TypeError(\"Событие приложения — объект { type, data? }\");\n const { type, data } = event as { type?: unknown; data?: unknown };\n if (typeof type !== \"string\" || type.length < 1 || type.length > APP_EVENT_TYPE_MAX_LENGTH) {\n throw new TypeError(`Тип события приложения — строка от 1 до ${APP_EVENT_TYPE_MAX_LENGTH} знаков`);\n }\n if (data === undefined) return { type };\n let json: string | undefined;\n try {\n json = JSON.stringify(data);\n } catch {\n json = undefined;\n }\n if (json === undefined) throw new TypeError(\"Данные события приложения — значение JSON\");\n if (new TextEncoder().encode(json).length > APP_EVENT_DATA_MAX_BYTES) {\n throw new TypeError(`Данные события приложения больше ${APP_EVENT_DATA_MAX_BYTES} байт`);\n }\n return { type, data: JSON.parse(json) };\n}\n"],"mappings":";AAuKO,IAAM,kCAAkC;AACxC,IAAM,4BAA4B;AAClC,IAAM,2BAA2B,KAAK;AAE7C,IAAM,oBAAoB;AAEnB,SAAS,kBAAkB,MAA+B;AAC/D,SAAO,OAAO,SAAS,YAAY,kBAAkB,KAAK,IAAI;AAChE;AAGO,SAAS,qBAAqB,SAAmC;AACtE,MAAI,CAAC,kBAAkB,SAAS,IAAI,EAAG,OAAM,IAAI,UAAU,iJAA6C;AACxG,MAAI,OAAO,QAAQ,YAAY,SAAU,OAAM,IAAI,UAAU,iHAAuB;AAEpF,QAAM,UAAU,MAAM,KAAK,QAAQ,OAAO,EAAE,MAAM,GAAG,+BAA+B,EAAE,KAAK,EAAE;AAC7F,SAAO,EAAE,MAAM,QAAQ,MAAM,QAAQ;AACvC;AAGO,IAAM,mBAAN,cAA+B,MAAM;AAAA,EACjC;AAAA,EAET,YAAY,SAAsB;AAChC,UAAM,aAAa,qBAAqB,OAAO;AAC/C,UAAM,WAAW,OAAO;AACxB,SAAK,OAAO;AACZ,SAAK,OAAO,WAAW;AAAA,EACzB;AAAA,EAEA,IAAI,UAAuB;AACzB,WAAO,EAAE,MAAM,KAAK,MAAM,SAAS,KAAK,QAAQ;AAAA,EAClD;AACF;AAGO,SAAS,iBAAiB,OAA0B;AACzD,MAAI,UAAU,QAAQ,OAAO,UAAU,SAAU,OAAM,IAAI,UAAU,qKAA6C;AAClH,QAAM,EAAE,MAAM,KAAK,IAAI;AACvB,MAAI,OAAO,SAAS,YAAY,KAAK,SAAS,KAAK,KAAK,SAAS,2BAA2B;AAC1F,UAAM,IAAI,UAAU,sMAA2C,yBAAyB,uCAAS;AAAA,EACnG;AACA,MAAI,SAAS,OAAW,QAAO,EAAE,KAAK;AACtC,MAAI;AACJ,MAAI;AACF,WAAO,KAAK,UAAU,IAAI;AAAA,EAC5B,QAAQ;AACN,WAAO;AAAA,EACT;AACA,MAAI,SAAS,OAAW,OAAM,IAAI,UAAU,2MAA2C;AACvF,MAAI,IAAI,YAAY,EAAE,OAAO,IAAI,EAAE,SAAS,0BAA0B;AACpE,UAAM,IAAI,UAAU,qLAAoC,wBAAwB,2BAAO;AAAA,EACzF;AACA,SAAO,EAAE,MAAM,MAAM,KAAK,MAAM,IAAI,EAAE;AACxC;","names":[]}
|