@ykurilov/agent-chat-core 0.72.0 → 0.73.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 +8 -0
- package/README.md +1 -1
- package/dist/{chunk-YMUK7P4S.js → chunk-6KHSUGHE.js} +24 -9
- package/dist/chunk-6KHSUGHE.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/storage-contract.js +1 -1
- package/dist/types/agent-run/end-turn.d.ts +6 -2
- package/package.json +1 -1
- package/dist/chunk-YMUK7P4S.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @ykurilov/agent-chat-core
|
|
2
2
|
|
|
3
|
+
## 0.73.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 34fec8f: Опциональный endTurn(output, { requireText: true }) завершает ход только при пользовательском тексте текущего ответа; иначе возвращает обычный output и даёт модели продолжить. Проверка по сборке потока в памяти, без чтения БД и flush. Whitespace, форматные невидимые Unicode, reasoning, tool output, ошибки и предыдущие ответы не считаются. SDK не добавляет собственную реплику. Прежний silent endTurn и явно переданный options.text сохранены.
|
|
8
|
+
|
|
9
|
+
Как обновить приложение: обновить все пакеты SDK одной версией и включить requireText у нужных инструментов вместо проверки текста через хранилище. Инструкцию модели писать перед вызовом сохранить. Guard не отменяет сохранённые данные, не повторяет инструмент сам и не гарантирует текст при отмене, сбое или лимите. Миграций нет.
|
|
10
|
+
|
|
3
11
|
## 0.72.0
|
|
4
12
|
|
|
5
13
|
No changes in this release.
|
package/README.md
CHANGED
|
@@ -78,7 +78,7 @@ import type { ChatStorage } from "@ykurilov/agent-chat-core";
|
|
|
78
78
|
- Снимок перед каждым шагом: `agent.stepContext: ({ state, stepNumber, chat, subject, … }) => string | null` — текст ложится сообщением человека в самый конец запроса шага, после результатов инструментов, в историю не пишется. Снимок на шаге один, всё до него от шага к шагу те же байты (кэш модели). `null` — шаг без снимка, бросок — без снимка и строка в журнал. Функция видит те же `channel`, `audience`, `staff`, что инструменты (в группе личного не показывайте), данные чата — копиями. Срок `agent.stepContextTimeoutMs` (по умолчанию 5 с): не ответила — шаг без снимка, `abortSignal` отменён. Типы `StepContext`, `StepContextInput`.
|
|
79
79
|
- Застывающий контекст: память, сегодняшняя дата, место на экране и блоки приложения вписываются в вопрос человека один раз за ход и дальше не меняются ни на байт. Следующий ход побайтно начинается с последнего шага прошлого (с рассуждениями и инструментами), поэтому всё прошлое читается из кэша модели. В новый вопрос идут только изменившиеся блоки и строка `[Больше не действует: «ключ»]` про пропавшие. Источник может вернуть `{ key, hidden: true }` — блок есть, но не показан ради места: вместо «больше не действует» модель, видевшая его, получит `[Не показано ради места, но по-прежнему действует: «ключ»]`. Набор хода хранится служебной частью ответа `data-frozen-context` (`FROZEN_CONTEXT_PART_TYPE`): в поток, историю, `afterTurn` и поиск по истории она не уходит. Продолжение после подтверждения, автопродолжение, запасной провайдер и «Повторить» берут блоки из ответа и источники заново не опрашивают. Память строже остальных блоков: читается один раз на чат — в первом ходе, после сжатия истории, при смене начала истории (правка первого сообщения, «Переделать» первого ответа), в первом личном ходе после группового и после сбоя чтения — и в идущем чате не перечитывается и не досылается (ни изменения, ни новые записи, ни «Больше не действует»); `snapshot()` и `blocks()` в остальных ходах не зовутся. Своя долгая память может отдавать блоки по записям: `LongTermMemory.blocks?({ userId, chatId })`; без него — один блок из `snapshot()`.
|
|
80
80
|
- Блоки приложения в вопросе хода: `agent.contextBlocks: ({ chatId, ownerId, chat, state, subject, channel, audience, … }) => [{ key, text, private? }] | null` — список скиллов, роль, правила. Зовётся один раз в начале хода; текст вписывается в вопрос после памяти и даты и дальше не меняется ни на байт (кэш модели), ядро его не оборачивает. На следующих ходах в вопрос идут только изменённые блоки целиком и строка `[Больше не действует: «ключ»]` про убранные. Ключи `memory`, `memory:*`, `today`, `screen_place` заняты ядром: такой блок отбрасывается со строкой в журнал. `private: true` — в групповом ходе блок не показывается. Срок `agent.contextBlocksTimeoutMs` (по умолчанию 5 с; 0 или `Infinity` — без срока, иначе от 1000): не ответила или бросила — блоки приложения берутся из прошлого ответа, в журнал строка `[блоки контекста] …`, `abortSignal` отменён; «Стоп» — без строки. Блоки хранятся служебной частью ответа: в потоке, истории, `afterTurn` и поиске по истории её нет. Типы `ContextBlock`, `ContextBlocksInput`, `ContextBlocksSource`, `HiddenContextBlock`, `ContextBlockEntry`.
|
|
81
|
-
- Конец хода сразу после инструмента: `execute` возвращает `endTurn(output, { text? })` — модель следующего шага не делает, готовый `text` идёт в ответ обычной текстовой частью без запроса к провайдеру и без расхода; `output` модель и запись видят как обычно. Тип `EndTurnOptions`.
|
|
81
|
+
- Конец хода сразу после инструмента: `execute` возвращает `endTurn(output, { text? })` — модель следующего шага не делает, готовый `text` идёт в ответ обычной текстовой частью без запроса к провайдеру и без расхода; `output` модель и запись видят как обычно. Тип `EndTurnOptions`. С `endTurn(output, { requireText: true })` остановка принимается только при непустом пользовательском text-part текущего ответа; без него результат обычный и модель продолжает. Проверка в памяти потока, без чтения БД/flush. Пробелы и чисто форматные невидимые Unicode, reasoning, tool output, ошибки и прежняя история не считаются; Markdown/HTML по смыслу не разбирается. Явный непустой `options.text` сохраняет приоритет и прежний путь готового текста. Это не автоподтверждение и не гарантия ответа при отмене, лимите или сбое. [Полная семантика и переход](../../docs/in-product-agent.md#14-конец-хода-сразу-после-инструмента).
|
|
82
82
|
- Данные чата без записи: `service.getChatData(userId, chatId)` — копия, проверки владения как у `updateChatData`.
|
|
83
83
|
- Проверка ответа виджета на сервере: `checkAnswer(answer, ctx)` у своего виджета и общая `agent.widgets.checkAnswer(call, answer, ctx)` (для всех виджетов без своей). Зовётся после схемы под замком чата. Исход: `undefined` — принять, `{ answer }` — принять с заменой, `{ refuse: "текст" }` — отказать (`WidgetAnswerRefusedError`, сервер — 409 `widget_answer_refused`, виджет остаётся открытым). `ctx` — `ownerId`, `chatId`, `messageId`, `toolName`, `toolCallId`, `input`, `subject?`, `chatData?`, `state` (записи ложатся в ответ вместе с результатом виджета, хук `onStateChanged` — с причиной `widgetAnswer`), `emitAppEvent` (событие приложения вместе с ответом виджета), `signal` (отмена по сроку или «Стопу»: приложение слушает его само). Срок — `agent.widgets.checkAnswerTimeoutMs` (5 000 мс, заметно меньше `requestTimeoutMs` страницы). Исключение — 500, ничего не записано, в журнале только имя ошибки; срок вышел — `WidgetAnswerCheckTimeoutError`, сервер — 504 `widget_check_timeout`. Чат закрыли во время проверки — `ChatClosedError`, ответ не пишется.
|
|
84
84
|
- Набор виджетов по чату: `agent.widgets.enabledFor({ chat: { id, ownerId, data?, subject? } })` → имена включённых. Выключенных модель не видит (нет в инструментах запроса), их прошлые вызовы идут модели текстом. Синхронная, те же данные — тот же набор; смена набора сбрасывает кэш модели. Неизвестное имя — строка в журнал, исключение — ошибка хода. Подробно — [docs/widgets.md](../../docs/widgets.md).
|
|
@@ -1342,13 +1342,20 @@ function endTurnNoteForModel(part) {
|
|
|
1342
1342
|
function endTurn(output, options2) {
|
|
1343
1343
|
const text = options2?.text;
|
|
1344
1344
|
if (text !== void 0 && typeof text !== "string") throw new TypeError("endTurn: text \u2014 \u0441\u0442\u0440\u043E\u043A\u0430");
|
|
1345
|
-
|
|
1345
|
+
if (options2?.requireText !== void 0 && typeof options2.requireText !== "boolean") throw new TypeError("endTurn: requireText must be a boolean");
|
|
1346
|
+
const mark = { [END_TURN]: true, output, requireText: options2?.requireText === true, text: text !== void 0 && text.trim().length > 0 ? text : void 0 };
|
|
1346
1347
|
return mark;
|
|
1347
1348
|
}
|
|
1348
1349
|
function isEndTurnMark(value) {
|
|
1349
1350
|
return typeof value === "object" && value !== null && value[END_TURN] === true;
|
|
1350
1351
|
}
|
|
1351
|
-
function
|
|
1352
|
+
function hasUserFacingText(parts) {
|
|
1353
|
+
return parts.some((part) => part.type === "text" && typeof part.text === "string" && hasText(part.text));
|
|
1354
|
+
}
|
|
1355
|
+
function hasText(text) {
|
|
1356
|
+
return text.replace(/[\s\p{Cf}\p{Default_Ignorable_Code_Point}]/gu, "").length > 0;
|
|
1357
|
+
}
|
|
1358
|
+
function createEndTurnRequests(snapshot = async () => []) {
|
|
1352
1359
|
let calls = 0;
|
|
1353
1360
|
let attempt = 0;
|
|
1354
1361
|
let silent = false;
|
|
@@ -1359,8 +1366,14 @@ function createEndTurnRequests() {
|
|
|
1359
1366
|
const own = attempt;
|
|
1360
1367
|
return withEndTurn(tools, (toolCallId) => {
|
|
1361
1368
|
const order = calls++;
|
|
1362
|
-
return (text) => {
|
|
1363
|
-
|
|
1369
|
+
return (text, requireText) => {
|
|
1370
|
+
const remember = () => {
|
|
1371
|
+
if (own === attempt) ended.set(toolCallId, { order, text, requireText });
|
|
1372
|
+
};
|
|
1373
|
+
if (!requireText || text !== void 0 && hasText(text)) return remember();
|
|
1374
|
+
return snapshot().then((parts) => {
|
|
1375
|
+
if (hasUserFacingText(parts)) remember();
|
|
1376
|
+
});
|
|
1364
1377
|
};
|
|
1365
1378
|
});
|
|
1366
1379
|
},
|
|
@@ -1381,7 +1394,9 @@ function createEndTurnRequests() {
|
|
|
1381
1394
|
attempt += 1;
|
|
1382
1395
|
silent = false;
|
|
1383
1396
|
const answered = new Set(parts.flatMap((part) => isAnsweredToolPart(part) ? [part.toolCallId] : []));
|
|
1384
|
-
for (const toolCallId of ended
|
|
1397
|
+
for (const [toolCallId, request] of ended) {
|
|
1398
|
+
if (!answered.has(toolCallId) || request.requireText && !(request.text !== void 0 && hasText(request.text)) && !hasUserFacingText(parts)) ended.delete(toolCallId);
|
|
1399
|
+
}
|
|
1385
1400
|
}
|
|
1386
1401
|
};
|
|
1387
1402
|
}
|
|
@@ -1404,8 +1419,8 @@ function withEndTurn(tools, startCall) {
|
|
|
1404
1419
|
const onEnd = startCall(options2.toolCallId);
|
|
1405
1420
|
const unwrap = (value) => {
|
|
1406
1421
|
if (!isEndTurnMark(value)) return value;
|
|
1407
|
-
onEnd(value.text);
|
|
1408
|
-
return value.output;
|
|
1422
|
+
const accepted = onEnd(value.text, value.requireText === true);
|
|
1423
|
+
return accepted ? accepted.then(() => value.output) : value.output;
|
|
1409
1424
|
};
|
|
1410
1425
|
const result = execute(input, options2);
|
|
1411
1426
|
if (isAsyncIterable(result)) return unwrapEach(result, unwrap);
|
|
@@ -7163,7 +7178,7 @@ async function runTurn(turnInput, emitRaw) {
|
|
|
7163
7178
|
...input.recordChatClosure && { record: input.recordChatClosure }
|
|
7164
7179
|
});
|
|
7165
7180
|
const toolQueue = input.toolQueue ?? createToolQueue();
|
|
7166
|
-
const endTurns = createEndTurnRequests();
|
|
7181
|
+
const endTurns = createEndTurnRequests(() => saver.snapshot());
|
|
7167
7182
|
let lastFinishReason;
|
|
7168
7183
|
const browser = input.browserTools && createTurnBrowserTools({ ...input.browserTools, tabId: input.browserTools.tabId, chatId: input.chatId, emit: emitToClient, signal: input.abortSignal });
|
|
7169
7184
|
let errors = [];
|
|
@@ -15167,4 +15182,4 @@ export {
|
|
|
15167
15182
|
InvalidWidgetAnswerError,
|
|
15168
15183
|
createChatService
|
|
15169
15184
|
};
|
|
15170
|
-
//# sourceMappingURL=chunk-
|
|
15185
|
+
//# sourceMappingURL=chunk-6KHSUGHE.js.map
|