@ykurilov/agent-chat-core 0.63.1 → 0.64.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 +12 -0
- package/README.md +2 -2
- package/dist/access.js +2 -2
- package/dist/{chunk-FBULM4V6.js → chunk-5UUWSY6V.js} +2 -2
- package/dist/{chunk-FOAIG4DY.js → chunk-CVRDB3NM.js} +1 -1
- package/dist/chunk-CVRDB3NM.js.map +1 -0
- package/dist/{chunk-XQ5FJ2VT.js → chunk-EWEEYX6U.js} +160 -15
- package/dist/chunk-EWEEYX6U.js.map +1 -0
- package/dist/index.js +3 -3
- package/dist/storage-contract.js +13 -2
- package/dist/storage-contract.js.map +1 -1
- package/dist/types/access/chat-capabilities.d.ts +12 -8
- package/dist/types/access/client-visibility.d.ts +2 -2
- package/dist/types/agent-run/run-agent-turn.d.ts +2 -4
- package/dist/types/conversation/types.d.ts +19 -6
- package/dist/types/index.d.ts +1 -1
- package/dist/types/provider-errors/classify-provider-error.d.ts +2 -0
- package/dist/types/provider-errors/provider-error-details.d.ts +18 -0
- package/dist/types/provider-errors/provider-outages.d.ts +3 -1
- package/dist/types/text/scrub-secrets.d.ts +3 -1
- package/dist/types/turn-hooks/turn-hooks.d.ts +3 -4
- package/package.json +1 -1
- package/dist/chunk-FOAIG4DY.js.map +0 -1
- package/dist/chunk-XQ5FJ2VT.js.map +0 -1
- /package/dist/{chunk-FBULM4V6.js.map → chunk-5UUWSY6V.js.map} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @ykurilov/agent-chat-core
|
|
2
2
|
|
|
3
|
+
## 0.64.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 99cded9: Подробности сбоя провайдера для админа. Ответ, упавший на провайдере, хранит `error.details`: провайдер, модель, код, причина одной строкой (до 300 знаков; ключи окружения, `Bearer`, значения `key=`/`token`/`api-key`/`password`, маски ключей со `*`, параметры адресов, `org-…`/`proj_…` и e-mail вычищены; модерация может повторять текст человека) и `resetsAt` — когда провайдер снимет предел (OpenRouter «resets 2026-11-01»; только будущее до двух лет, без пояса — UTC). В браузер подробности уходят только по новому праву `chatCapabilities` → `errorDetails: true` (по умолчанию `false`, при `modelDetails: false` тоже `false`) — в истории, потоке и при переподключении; остальным — только `error.kind`. `afterTurn` получает их всегда. Экран: подпись-функция `labels.errorText(error, plain)`, без неё или если она бросила исключение — прежний текст `labels.errors[kind]`; типы `ChatError`, `ChatErrorDetails`, помощник `customErrorText`. Хранилища: тип колонки `error` — `ChatError`, миграций нет.
|
|
8
|
+
|
|
9
|
+
## 0.63.2
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- c772243: Исчерпанный предел расходов ключа (OpenRouter 403 «Key limit exceeded», `limit_source: openrouter_key_limit`; бюджеты ключа у шлюзов — «ExceededBudget», «Budget has been exceeded») — теперь `insufficient_funds`, а не `invalid_key`: переход к запасному, пауза основного, человеку «кончились средства», громкая строка «исчерпан предел расходов ключа». Обычный 403 — как раньше.
|
|
14
|
+
|
|
3
15
|
## 0.63.1
|
|
4
16
|
|
|
5
17
|
No changes in this release.
|
package/README.md
CHANGED
|
@@ -82,8 +82,8 @@ import type { ChatStorage } from "@ykurilov/agent-chat-core";
|
|
|
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).
|
|
85
|
-
- Права человека в чате: `chatCapabilities({ userId, chat })` возвращает запреты (`edit`, `regenerate`, `retry`, `branches`, `modelChoice`, `answerEditing`, `chatList`, `compact`, `attachments`, `voice`, `reasoning`, `toolDetails`, `steer`, `modelDetails`; по умолчанию всё `true`; `modelDetails: false` выключает и `modelChoice`). Неизвестное право или не булево значение — `TypeError`. Запрещённое действие — `ActionForbiddenError(action)` до любой записи и до `beforeTurn`. За один вызов службы права спрашиваются не больше раза. История несёт `capabilities`. Вручную — `service.assertCapability(userId, chatId, capability)`.
|
|
86
|
-
- Скрытие частей ответа: `reasoning: false` — мысли модели, `toolDetails: false` — служебные инструменты не уходят в историю, поток и повтор кусков после переподключения; хранилище, модель и `afterTurn` получают всё. Виджеты, браузерные инструменты, вызовы с запросом подтверждения и инструменты с `metadata: { showOnScreen: true }` в описании видны всегда. `modelDetails: false` убирает у чата истории `ownerId`, `modelId`, `providerId`, `reasoning`, у сообщений — `metadata.modelId`, `providerId`, `usage`, у частей и кусков — `providerMetadata`/`callProviderMetadata`/`resultProviderMetadata`, у `data-step-reset` — провайдера; отметки сжатия идут без текста сводки (`summary: ""`, `summaryTokens: 0`) в истории и `getCompactions`. Меню моделей — опция `modelMenu({ userId })`: `false` — `getModelMenu(userId)` бросает `ActionForbiddenError("modelDetails")`. История несёт `modelAcceptsImages` (видит ли модель чата картинки). Права — на того, кто открыл поток.
|
|
85
|
+
- Права человека в чате: `chatCapabilities({ userId, chat })` возвращает запреты (`edit`, `regenerate`, `retry`, `branches`, `modelChoice`, `answerEditing`, `chatList`, `compact`, `attachments`, `voice`, `reasoning`, `toolDetails`, `steer`, `modelDetails`, `errorDetails`; по умолчанию всё `true`, кроме `errorDetails`; `modelDetails: false` выключает и `modelChoice`, и `errorDetails`). Неизвестное право или не булево значение — `TypeError`. Запрещённое действие — `ActionForbiddenError(action)` до любой записи и до `beforeTurn`. За один вызов службы права спрашиваются не больше раза. История несёт `capabilities`. Вручную — `service.assertCapability(userId, chatId, capability)`.
|
|
86
|
+
- Скрытие частей ответа: `reasoning: false` — мысли модели, `toolDetails: false` — служебные инструменты не уходят в историю, поток и повтор кусков после переподключения; хранилище, модель и `afterTurn` получают всё. Виджеты, браузерные инструменты, вызовы с запросом подтверждения и инструменты с `metadata: { showOnScreen: true }` в описании видны всегда. `modelDetails: false` убирает у чата истории `ownerId`, `modelId`, `providerId`, `reasoning`, у сообщений — `metadata.modelId`, `providerId`, `usage`, у частей и кусков — `providerMetadata`/`callProviderMetadata`/`resultProviderMetadata`, у `data-step-reset` — провайдера; отметки сжатия идут без текста сводки (`summary: ""`, `summaryTokens: 0`) в истории и `getCompactions`. Подробности сбоя провайдера (`metadata.error.details`: `providerId`, `modelId`, `statusCode`, `reason` — одна строка до 300 знаков без секретов, параметров адресов, `org-…`/`proj_…` и e-mail, `resetsAt` — ISO, только будущее не дальше двух лет) хранятся всегда, а в историю и поток идут только при `errorDetails: true` (например, админу); без права — только `error.kind`. Ответ модерации в `reason` может повторять текст человека — отдавайте право только тому, кому этот текст можно видеть. Меню моделей — опция `modelMenu({ userId })`: `false` — `getModelMenu(userId)` бросает `ActionForbiddenError("modelDetails")`. История несёт `modelAcceptsImages` (видит ли модель чата картинки). Права — на того, кто открыл поток.
|
|
87
87
|
- Закрытый чат: `service.closeChat({ ownerId, chatId, code, message })` и `reopenChat({ ownerId, chatId })` (события `chat-closed`, `chat-reopened`), из инструмента — `model.closeChat({ code, message })`. Закрытие — запись `$closed` на ветке: из инструмента — в ответе сразу при вызове, из кода — служебной записью в конце ветки (идёт ход — после него). Правка, «Переделать», выбор ветки и правка ответа до точки закрытия открывают чат; закрыть навсегда — правами (`chatCapabilities` видит `chat.closed`). Закрытие побеждает паузу: ждущие подтверждения и виджеты закрываются отказом, как при «Стопе». В закрытом чате отправка, «Повторить», подтверждения и виджеты — `ChatClosedError`; служебные сообщения пишутся без хода. `listChats` отдаёт `closed` — копию закрытия текущей ветки (`setChatClosed`); права и история берут закрытие с ветки, не записавшуюся копию дописывает следующее открытие истории. Закрытие чатов 0.27 переносится в запись лениво, при первой правке или смене ветки.
|
|
88
88
|
- `beforeCreateChat({ userId, origin? })` — можно ли человеку завести новый чат (`createChat`): `{ code, message }` → `ChatCreationRefusedError`. Чаты объектов и импорт хук не спрашивают. У чата `closed?: { code, message }`, хранилище — необязательный `setChatClosed(chatId, closed | null)`.
|
|
89
89
|
- Чат объекта: `service.openSubjectChat({ ownerId, key, data?, title?, greeting? })` → `{ chat, created }`, один чат на ключ у человека. Инструменты получают `model.subject`, итог хода — `turn.subject`. `listChats(userId, { subjects })` — по умолчанию без чатов объектов.
|
package/dist/access.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
normalizeTurnRefusal
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-CVRDB3NM.js";
|
|
4
4
|
|
|
5
5
|
// src/access/resolved-user.ts
|
|
6
6
|
function allowedUser(resolved) {
|
|
@@ -17,4 +17,4 @@ export {
|
|
|
17
17
|
allowedUser,
|
|
18
18
|
accessRefusedResponse
|
|
19
19
|
};
|
|
20
|
-
//# sourceMappingURL=chunk-
|
|
20
|
+
//# sourceMappingURL=chunk-5UUWSY6V.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/turn-hooks/turn-hooks.ts"],"sourcesContent":["import type { AgentChannel, Chat, ChatError, 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 /**\n * Ход на пачку начал сам сервер после перезапуска или за упавшую копию (`answerAfterRestart`): вызова, который ждёт\n * ответ или отказ, нет. Отказ дойдёт только до журнала — сообщить человеку может само приложение.\n */\n unattended?: true;\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 /** Ход без вызова, как в `beforeTurn`: об отказе (например, `spending_limit`) человеку может сказать только приложение. */\n unattended?: true;\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 /** Вид сбоя и подробности провайдера (`details`): хук на сервере получает их всегда. */\n error?: ChatError;\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":";AA+KO,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":[]}
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
TurnRefusedError,
|
|
4
4
|
normalizeTurnRefusal,
|
|
5
5
|
validateAppEvent
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-CVRDB3NM.js";
|
|
7
7
|
|
|
8
8
|
// src/conversation/types.ts
|
|
9
9
|
var CURRENT_FORMAT_VERSION = 1;
|
|
@@ -525,7 +525,10 @@ var MIN_SECRET_LENGTH = 8;
|
|
|
525
525
|
var HIDDEN = "[\u0441\u043A\u0440\u044B\u0442\u043E]";
|
|
526
526
|
var SECRET_FIELD = String.raw`[\w-]*(?:password|passwd|secret|token|api[_-]?key|(?:secret|private|access)[_-]?key)(?![\w-])`;
|
|
527
527
|
var FIELD_VALUE = String.raw`(?!\d+(?![^&\s"',;]))(?:"[^"]*"|'[^']*'|[^&\s"',;]+)`;
|
|
528
|
+
var SECRET_LIKE = String.raw`(?=[^\s"',;]*\d)(?=[^\s"',;]*[A-Za-z])[^\s"',;]{8,}`;
|
|
528
529
|
var KNOWN_KEY_PATTERNS = [
|
|
530
|
+
// Маска ключа целиком: провайдер показывает начало и последние знаки ключа.
|
|
531
|
+
/[\w-]*\*{4,}[\w-]*/g,
|
|
529
532
|
/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g,
|
|
530
533
|
/\bsk-[A-Za-z0-9_-]{8,}/g,
|
|
531
534
|
/\b[spr]k_(?:live|test)_[A-Za-z0-9]{8,}/g,
|
|
@@ -547,7 +550,7 @@ function scrubSecrets(text, knownSecrets = []) {
|
|
|
547
550
|
if (secret && secret.length >= MIN_SECRET_LENGTH) scrubbed = scrubbed.split(secret).join(HIDDEN);
|
|
548
551
|
}
|
|
549
552
|
for (const pattern of KNOWN_KEY_PATTERNS) scrubbed = scrubbed.replace(pattern, HIDDEN);
|
|
550
|
-
return scrubbed.replace(URL_PASSWORD, `$1:${HIDDEN}@`).replace(/\b((?:Proxy-)?Authorization\s*[:=]\s*)(?:(Basic|Bearer|Token|Digest)\s+)?[^\s"',;]+/gi, (_, head, scheme) => `${head}${scheme ? `${scheme} ` : ""}${HIDDEN}`).replace(/\
|
|
553
|
+
return scrubbed.replace(URL_PASSWORD, `$1:${HIDDEN}@`).replace(/\b((?:Proxy-)?Authorization\s*[:=]\s*)(?:(Basic|Bearer|Token|Digest)\s+)?[^\s"',;]+/gi, (_, head, scheme) => `${head}${scheme ? `${scheme} ` : ""}${HIDDEN}`).replace(/\b(Bearer\s+)(?!\[скрыто\])[^\s"',;]+/gi, `$1${HIDDEN}`).replace(/([?&;]key=)[^&\s"]+/g, `$1${HIDDEN}`).replace(new RegExp(String.raw`(?<![\w-])(key\s*[:=]\s*)${SECRET_LIKE}`, "gi"), `$1${HIDDEN}`).replace(new RegExp(String.raw`(?<![\w-])((?:token|api[_-]?key|secret|password|passwd|key)\s+)${SECRET_LIKE}`, "gi"), `$1${HIDDEN}`).replace(new RegExp(String.raw`("${SECRET_FIELD}"\s*:\s*)"[^"]*"`, "gi"), `$1"${HIDDEN}"`).replace(
|
|
551
554
|
new RegExp(String.raw`(?<![\w-])(${SECRET_FIELD}\s*[:=]\s*)(?!"\[скрыто\]")(${FIELD_VALUE})`, "gi"),
|
|
552
555
|
(_, head, value) => /^["']/.test(value) ? `${head}${value[0]}${HIDDEN}${value[0]}` : `${head}${HIDDEN}`
|
|
553
556
|
);
|
|
@@ -3331,7 +3334,8 @@ function sleep(ms, signal) {
|
|
|
3331
3334
|
|
|
3332
3335
|
// src/provider-errors/classify-provider-error.ts
|
|
3333
3336
|
var KEY_SIGNS = /api[ _-]?key|authenticat|unauthori[sz]ed|credential|access[ _-]?token|key (?:is )?(?:invalid|revoked|expired|disabled)/i;
|
|
3334
|
-
var
|
|
3337
|
+
var KEY_LIMIT_SIGNS = /key limit exceeded|openrouter_key_limit|ExceededBudget|budget has been exceeded|(?:key|token) has (?:reached|exceeded) its (?:spend(?:ing)?|budget|usage) limit/i;
|
|
3338
|
+
var FUNDS_SIGNS = /insufficient (?:credits|funds|balance)/i;
|
|
3335
3339
|
var MODERATION_SIGNS = /flagged|moderation|content[ _-]?policy|safety/i;
|
|
3336
3340
|
var RATE_LIMIT_SIGNS = /rate.?limit|too many requests|throttl|please wait|ResourceExhausted/i;
|
|
3337
3341
|
function classifyProviderError(error, provider) {
|
|
@@ -3342,6 +3346,7 @@ function classifyProviderError(error, provider) {
|
|
|
3342
3346
|
for (const hint of provider?.errorHints ?? []) {
|
|
3343
3347
|
if (hint.match.test(text)) return hint.kind;
|
|
3344
3348
|
}
|
|
3349
|
+
if (KEY_LIMIT_SIGNS.test(text)) return "insufficient_funds";
|
|
3345
3350
|
if (status === 401) return "invalid_key";
|
|
3346
3351
|
if (status === 403) {
|
|
3347
3352
|
if (KEY_SIGNS.test(text)) return "invalid_key";
|
|
@@ -3359,6 +3364,9 @@ function isAccessRefusal(error) {
|
|
|
3359
3364
|
const cause = underlyingError(error);
|
|
3360
3365
|
return errorStatusCode(cause) === 403 && !MODERATION_SIGNS.test(errorText(cause));
|
|
3361
3366
|
}
|
|
3367
|
+
function isKeyLimit(error) {
|
|
3368
|
+
return KEY_LIMIT_SIGNS.test(errorText(underlyingError(error)));
|
|
3369
|
+
}
|
|
3362
3370
|
|
|
3363
3371
|
// src/provider-errors/provider-failure-log.ts
|
|
3364
3372
|
import { APICallError as APICallError3 } from "ai";
|
|
@@ -3405,14 +3413,15 @@ function createProviderOutages(options2) {
|
|
|
3405
3413
|
return time < outage.pausedUntil || time < (outage.probeUntil ?? 0) ? outage.kind : void 0;
|
|
3406
3414
|
};
|
|
3407
3415
|
return {
|
|
3408
|
-
report({ providerId, kind, fallbackProviderId, fallbacksPaused }) {
|
|
3416
|
+
report({ providerId, kind, fallbackProviderId, fallbacksPaused, keyLimit }) {
|
|
3409
3417
|
const time = options2.now();
|
|
3410
3418
|
outages.set(providerId, { kind, pausedUntil: time + PROVIDER_OUTAGE_PAUSE_MS });
|
|
3411
3419
|
const last = alertedAt.get(providerId);
|
|
3412
3420
|
if (last !== void 0 && time - last < PROVIDER_OUTAGE_ALERT_EVERY_MS) return;
|
|
3413
3421
|
alertedAt.set(providerId, time);
|
|
3414
3422
|
const route = fallbackProviderId !== void 0 ? `\u0445\u043E\u0434\u044B \u0438\u0434\u0443\u0442 \u0447\u0435\u0440\u0435\u0437 \u0437\u0430\u043F\u0430\u0441\u043D\u043E\u0439 ${fallbackProviderId}` : `${fallbacksPaused ? "\u0437\u0430\u043F\u0430\u0441\u043D\u044B\u0435 \u0442\u043E\u0436\u0435 \u043D\u0430 \u043F\u0430\u0443\u0437\u0435" : "\u0437\u0430\u043F\u0430\u0441\u043D\u043E\u0433\u043E \u043D\u0435\u0442"}, \u0445\u043E\u0434\u044B \u043F\u0430\u0434\u0430\u044E\u0442 \u0441 \u043E\u0448\u0438\u0431\u043A\u043E\u0439`;
|
|
3415
|
-
|
|
3423
|
+
const said = keyLimit && kind === "insufficient_funds" ? "\u0438\u0441\u0447\u0435\u0440\u043F\u0430\u043D \u043F\u0440\u0435\u0434\u0435\u043B \u0440\u0430\u0441\u0445\u043E\u0434\u043E\u0432 \u043A\u043B\u044E\u0447\u0430" : SAID[kind];
|
|
3424
|
+
options2.logError(`[\u0412\u041D\u0418\u041C\u0410\u041D\u0418\u0415 \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440] ${providerId}: ${said} \u2014 ${route}`);
|
|
3416
3425
|
},
|
|
3417
3426
|
pausedKind,
|
|
3418
3427
|
admit(providerId) {
|
|
@@ -5320,6 +5329,100 @@ ${summary}
|
|
|
5320
5329
|
</answer_progress>`;
|
|
5321
5330
|
}
|
|
5322
5331
|
|
|
5332
|
+
// src/provider-errors/provider-error-details.ts
|
|
5333
|
+
import { APICallError as APICallError4 } from "ai";
|
|
5334
|
+
var MAX_REASON_LENGTH = 300;
|
|
5335
|
+
var MAX_RESET_AHEAD_MS = 2 * 366 * 24 * 60 * 60 * 1e3;
|
|
5336
|
+
function providerErrorDetails(error, context) {
|
|
5337
|
+
const cause = underlyingError(error);
|
|
5338
|
+
const statusCode = errorStatusCode(cause);
|
|
5339
|
+
const { message, metadata } = providerMessageOf(cause);
|
|
5340
|
+
const reason = message === void 0 ? void 0 : shownReason(message, environmentSecrets(context.env, context.catalog));
|
|
5341
|
+
const nowMs = (context.now ?? Date.now)();
|
|
5342
|
+
const found = resetFromMetadata(metadata, nowMs) ?? (message === void 0 ? void 0 : resetFromText(message));
|
|
5343
|
+
const resetsAt = found !== void 0 && found > nowMs && found - nowMs <= MAX_RESET_AHEAD_MS ? new Date(found).toISOString() : void 0;
|
|
5344
|
+
return {
|
|
5345
|
+
providerId: context.providerId,
|
|
5346
|
+
modelId: context.modelId,
|
|
5347
|
+
...statusCode !== void 0 && { statusCode },
|
|
5348
|
+
...reason !== void 0 && reason !== "" && { reason },
|
|
5349
|
+
...resetsAt !== void 0 && { resetsAt }
|
|
5350
|
+
};
|
|
5351
|
+
}
|
|
5352
|
+
function providerMessageOf(error) {
|
|
5353
|
+
if (APICallError4.isInstance(error)) {
|
|
5354
|
+
const fromBody = typeof error.responseBody === "string" ? fromJson(error.responseBody) : void 0;
|
|
5355
|
+
if (fromBody?.message !== void 0) return fromBody;
|
|
5356
|
+
const raw = [error.responseBody, error.message].find(isText);
|
|
5357
|
+
return { ...raw !== void 0 && { message: raw }, metadata: fromBody?.metadata };
|
|
5358
|
+
}
|
|
5359
|
+
if (typeof error === "string") return isText(error) ? { message: error } : {};
|
|
5360
|
+
return fromObject(error) ?? {};
|
|
5361
|
+
}
|
|
5362
|
+
function fromJson(body) {
|
|
5363
|
+
try {
|
|
5364
|
+
return fromObject(JSON.parse(body));
|
|
5365
|
+
} catch {
|
|
5366
|
+
return void 0;
|
|
5367
|
+
}
|
|
5368
|
+
}
|
|
5369
|
+
function fromObject(value) {
|
|
5370
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
5371
|
+
const fields = value;
|
|
5372
|
+
if (typeof fields.error === "object" && fields.error !== null) {
|
|
5373
|
+
const nested = fields.error;
|
|
5374
|
+
if (isText(nested.message)) return { message: nested.message, metadata: nested.metadata };
|
|
5375
|
+
}
|
|
5376
|
+
if (isText(fields.error)) return { message: fields.error, metadata: fields.metadata };
|
|
5377
|
+
if (isText(fields.message)) return { message: fields.message, metadata: fields.metadata };
|
|
5378
|
+
return void 0;
|
|
5379
|
+
}
|
|
5380
|
+
var isText = (value) => typeof value === "string" && value.trim() !== "";
|
|
5381
|
+
var URL_WITH_QUERY = /(https?:\/\/[^\s?#"'<>]+)[?#][^\s"'<>]*/g;
|
|
5382
|
+
var ACCOUNT_IDS = [/\borg-[A-Za-z0-9]{6,}/g, /\bproj_[A-Za-z0-9]{6,}/g, /[\w.+-]+@[\w-]+(?:\.[\w-]+)+/g];
|
|
5383
|
+
function shownReason(message, knownSecrets) {
|
|
5384
|
+
const oneLine2 = message.replace(/\s*[\r\n]+\s*/g, " ").trim();
|
|
5385
|
+
let shown = scrubSecrets(oneLine2.replace(URL_WITH_QUERY, "$1"), knownSecrets);
|
|
5386
|
+
for (const pattern of ACCOUNT_IDS) shown = shown.replace(pattern, "[\u0441\u043A\u0440\u044B\u0442\u043E]");
|
|
5387
|
+
return cutAtCharBoundary(shown, MAX_REASON_LENGTH);
|
|
5388
|
+
}
|
|
5389
|
+
var RESET_FIELD = /reset/i;
|
|
5390
|
+
var RELATIVE_FIELD = /sec|after|in$|_in_|ms$|_ms|Ms$/i;
|
|
5391
|
+
var MS_FIELD = /ms$|_ms|Ms$/;
|
|
5392
|
+
var MIN_UNIX_SECONDS = 1e9;
|
|
5393
|
+
function resetFromMetadata(metadata, nowMs) {
|
|
5394
|
+
if (typeof metadata !== "object" || metadata === null) return void 0;
|
|
5395
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
5396
|
+
if (!RESET_FIELD.test(key)) continue;
|
|
5397
|
+
const parsed = typeof value === "number" ? fromNumber(key, value, nowMs) : typeof value === "string" ? fromDateText(value) : void 0;
|
|
5398
|
+
if (parsed !== void 0) return parsed;
|
|
5399
|
+
}
|
|
5400
|
+
return void 0;
|
|
5401
|
+
}
|
|
5402
|
+
function fromNumber(key, value, nowMs) {
|
|
5403
|
+
if (!Number.isFinite(value) || value <= 0) return void 0;
|
|
5404
|
+
if (RELATIVE_FIELD.test(key) || value < MIN_UNIX_SECONDS) return nowMs + (MS_FIELD.test(key) ? value : value * 1e3);
|
|
5405
|
+
return value < 1e12 ? value * 1e3 : value;
|
|
5406
|
+
}
|
|
5407
|
+
var LIMIT_MESSAGE_START = /^[^'"«“]{0,80}?\b(?:limit|budget|quota)\b/i;
|
|
5408
|
+
var QUOTED = /'[^']*'|"[^"]*"|«[^»]*»|“[^”]*”/g;
|
|
5409
|
+
var RESET_IN_TEXT = /\bresets?\s+(?:at\s+|on\s+)?(\d{4}-\d{2}-\d{2}(?:[T ]\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:?\d{2})?)?)/i;
|
|
5410
|
+
function resetFromText(message) {
|
|
5411
|
+
if (!LIMIT_MESSAGE_START.test(message)) return void 0;
|
|
5412
|
+
const found = RESET_IN_TEXT.exec(message.replace(QUOTED, " "))?.[1];
|
|
5413
|
+
return found === void 0 ? void 0 : fromDateText(found);
|
|
5414
|
+
}
|
|
5415
|
+
function fromDateText(text) {
|
|
5416
|
+
const trimmed = text.trim();
|
|
5417
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})(?:[T ](\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?)(Z|[+-]\d{2}:?\d{2})?)?$/i.exec(trimmed);
|
|
5418
|
+
if (!match) return void 0;
|
|
5419
|
+
const [, year, month, day, time, zone] = match;
|
|
5420
|
+
const calendarDay = new Date(Date.UTC(Number(year), Number(month) - 1, Number(day)));
|
|
5421
|
+
if (calendarDay.getUTCMonth() !== Number(month) - 1 || calendarDay.getUTCDate() !== Number(day)) return void 0;
|
|
5422
|
+
const parsed = Date.parse(`${year}-${month}-${day}${time === void 0 ? "" : `T${time}${zone ?? "Z"}`}`);
|
|
5423
|
+
return Number.isNaN(parsed) ? void 0 : parsed;
|
|
5424
|
+
}
|
|
5425
|
+
|
|
5323
5426
|
// src/agent-run/answer-saver.ts
|
|
5324
5427
|
import { readUIMessageStream } from "ai";
|
|
5325
5428
|
|
|
@@ -6602,6 +6705,14 @@ async function runTurn(turnInput, emitRaw) {
|
|
|
6602
6705
|
let errors = [];
|
|
6603
6706
|
let terminal = [];
|
|
6604
6707
|
let errorKind;
|
|
6708
|
+
let errorDetails;
|
|
6709
|
+
const detailsOf2 = (error2, failedProviderId) => providerErrorDetails(error2, {
|
|
6710
|
+
providerId: failedProviderId,
|
|
6711
|
+
modelId,
|
|
6712
|
+
env: input.env,
|
|
6713
|
+
catalog: input.catalog,
|
|
6714
|
+
now: () => (input.now ?? (() => /* @__PURE__ */ new Date()))().getTime()
|
|
6715
|
+
});
|
|
6605
6716
|
let providerId = input.providerId;
|
|
6606
6717
|
let answeredProviderId = input.providerId;
|
|
6607
6718
|
let viewer;
|
|
@@ -6644,6 +6755,7 @@ async function runTurn(turnInput, emitRaw) {
|
|
|
6644
6755
|
});
|
|
6645
6756
|
const fallbackProviderIds = allFallbackIds.filter((candidate) => outages.pausedKind(candidate) === void 0);
|
|
6646
6757
|
let firstOutage;
|
|
6758
|
+
let firstOutageDetails;
|
|
6647
6759
|
const primaryPaused = outages.admit(input.providerId);
|
|
6648
6760
|
if (primaryPaused !== void 0 && fallbackProviderIds[0] !== void 0) {
|
|
6649
6761
|
providerId = fallbackProviderIds[0];
|
|
@@ -6740,7 +6852,9 @@ async function runTurn(turnInput, emitRaw) {
|
|
|
6740
6852
|
} catch (error2) {
|
|
6741
6853
|
if (error2 instanceof MissingProviderKeyError) logError(error2.message);
|
|
6742
6854
|
if (!input.abortSignal.aborted) {
|
|
6743
|
-
|
|
6855
|
+
const kind = classify(error2);
|
|
6856
|
+
errorKind = errorKindForPerson(kind, firstOutage);
|
|
6857
|
+
errorDetails = errorKind === firstOutage ? firstOutageDetails : detailsOf2(error2, providerId);
|
|
6744
6858
|
errors = [{ type: "error", errorText: errorKind }];
|
|
6745
6859
|
}
|
|
6746
6860
|
break;
|
|
@@ -6784,10 +6898,14 @@ async function runTurn(turnInput, emitRaw) {
|
|
|
6784
6898
|
let next = stepsDone < maxSteps ? decideNextAttempt(result, nextState()) : { action: "fail" };
|
|
6785
6899
|
const outage = isProviderOutage(result.kind) ? result.kind : void 0;
|
|
6786
6900
|
if (outage) {
|
|
6787
|
-
if (providerId === input.providerId
|
|
6901
|
+
if (providerId === input.providerId && firstOutage === void 0) {
|
|
6902
|
+
firstOutage = outage;
|
|
6903
|
+
firstOutageDetails = detailsOf2(result.error, providerId);
|
|
6904
|
+
}
|
|
6788
6905
|
outages.report({
|
|
6789
6906
|
providerId,
|
|
6790
6907
|
kind: outage,
|
|
6908
|
+
...isKeyLimit(result.error) && { keyLimit: true },
|
|
6791
6909
|
...next.action === "fallback" ? { fallbackProviderId: next.providerId } : { fallbacksPaused: allFallbackIds.some((candidate) => candidate !== providerId && outages.pausedKind(candidate) !== void 0) }
|
|
6792
6910
|
});
|
|
6793
6911
|
}
|
|
@@ -6872,6 +6990,7 @@ async function runTurn(turnInput, emitRaw) {
|
|
|
6872
6990
|
continue;
|
|
6873
6991
|
}
|
|
6874
6992
|
errorKind = errorKindForPerson(result.kind, firstOutage);
|
|
6993
|
+
errorDetails = errorKind === firstOutage ? firstOutageDetails : detailsOf2(result.error, providerId);
|
|
6875
6994
|
errors = errorKind === result.kind ? attempt.errors : [{ type: "error", errorText: errorKind }];
|
|
6876
6995
|
terminal = attempt.terminal;
|
|
6877
6996
|
logError(
|
|
@@ -6909,6 +7028,7 @@ async function runTurn(turnInput, emitRaw) {
|
|
|
6909
7028
|
if (!input.abortSignal.aborted) {
|
|
6910
7029
|
if (error2 instanceof AgentViewerError) logError(`[agent.viewer] \u0447\u0430\u0442=${input.chatId} \u0445\u043E\u0434 \u043D\u0435 \u043D\u0430\u0447\u0430\u0442: ${error2.message}`);
|
|
6911
7030
|
errorKind = error2 instanceof AgentViewerError ? "other" : classifyProviderError(error2, void 0);
|
|
7031
|
+
errorDetails = void 0;
|
|
6912
7032
|
errors = [{ type: "error", errorText: errorKind }];
|
|
6913
7033
|
}
|
|
6914
7034
|
}
|
|
@@ -6979,7 +7099,7 @@ async function runTurn(turnInput, emitRaw) {
|
|
|
6979
7099
|
for (const chunk of heldText?.release() ?? []) emitShown(chunk);
|
|
6980
7100
|
turnUsage.closeAttempt();
|
|
6981
7101
|
const usage = messageUsage();
|
|
6982
|
-
const error = status === "error" ? { kind: errorKind ?? "other" } : null;
|
|
7102
|
+
const error = status === "error" ? { kind: errorKind ?? "other", ...errorDetails && { details: errorDetails } } : null;
|
|
6983
7103
|
const answeredByBackup = answeredProviderId !== input.providerId && { providerId: answeredProviderId };
|
|
6984
7104
|
let finished = false;
|
|
6985
7105
|
if (!takeover.signal.aborted) {
|
|
@@ -7780,6 +7900,7 @@ async function* helperSteps(input) {
|
|
|
7780
7900
|
outages.report({
|
|
7781
7901
|
providerId,
|
|
7782
7902
|
kind: outage,
|
|
7903
|
+
...isKeyLimit(error) && { keyLimit: true },
|
|
7783
7904
|
...next.action === "fallback" ? { fallbackProviderId: next.providerId } : { fallbacksPaused: allFallbackIds.some((candidate) => candidate !== providerId && outages.pausedKind(candidate) !== void 0) }
|
|
7784
7905
|
});
|
|
7785
7906
|
}
|
|
@@ -9411,10 +9532,12 @@ var CHAT_CAPABILITIES = [
|
|
|
9411
9532
|
"reasoning",
|
|
9412
9533
|
"toolDetails",
|
|
9413
9534
|
"steer",
|
|
9414
|
-
"modelDetails"
|
|
9535
|
+
"modelDetails",
|
|
9536
|
+
"errorDetails"
|
|
9415
9537
|
];
|
|
9538
|
+
var OFF_BY_DEFAULT = /* @__PURE__ */ new Set(["errorDetails"]);
|
|
9416
9539
|
function resolveChatCapabilities(given) {
|
|
9417
|
-
const resolved = Object.fromEntries(CHAT_CAPABILITIES.map((capability) => [capability,
|
|
9540
|
+
const resolved = Object.fromEntries(CHAT_CAPABILITIES.map((capability) => [capability, !OFF_BY_DEFAULT.has(capability)]));
|
|
9418
9541
|
if (given === void 0 || given === null) return resolved;
|
|
9419
9542
|
if (typeof given !== "object") throw new TypeError("\u041F\u0440\u0430\u0432\u0430 \u0447\u0430\u0442\u0430 \u2014 \u043E\u0431\u044A\u0435\u043A\u0442 { \u043F\u0440\u0430\u0432\u043E: boolean }");
|
|
9420
9543
|
const unknown = Object.keys(given).find((key) => !CHAT_CAPABILITIES.includes(key));
|
|
@@ -9425,7 +9548,10 @@ function resolveChatCapabilities(given) {
|
|
|
9425
9548
|
if (typeof value !== "boolean") throw new TypeError(`\u041F\u0440\u0430\u0432\u043E \xAB${capability}\xBB \u2014 true \u0438\u043B\u0438 false`);
|
|
9426
9549
|
resolved[capability] = value;
|
|
9427
9550
|
}
|
|
9428
|
-
if (!resolved.modelDetails)
|
|
9551
|
+
if (!resolved.modelDetails) {
|
|
9552
|
+
resolved.modelChoice = false;
|
|
9553
|
+
resolved.errorDetails = false;
|
|
9554
|
+
}
|
|
9429
9555
|
return resolved;
|
|
9430
9556
|
}
|
|
9431
9557
|
var ActionForbiddenError = class extends Error {
|
|
@@ -11457,6 +11583,12 @@ var SHOWS_EVERYTHING = {
|
|
|
11457
11583
|
};
|
|
11458
11584
|
var MODEL_METADATA_KEYS = ["modelId", "providerId", "usage"];
|
|
11459
11585
|
var MODEL_CHAT_KEYS = ["ownerId", "modelId", "providerId", "reasoning"];
|
|
11586
|
+
var withoutErrorDetails = (metadata) => {
|
|
11587
|
+
const error = metadata.error;
|
|
11588
|
+
if (typeof error !== "object" || error === null || !("details" in error)) return metadata;
|
|
11589
|
+
const { details: _details, ...rest } = error;
|
|
11590
|
+
return { ...metadata, error: rest };
|
|
11591
|
+
};
|
|
11460
11592
|
var without = (carrier, keys) => {
|
|
11461
11593
|
if (!keys.some((key) => key in carrier)) return carrier;
|
|
11462
11594
|
const rest = { ...carrier };
|
|
@@ -11494,10 +11626,15 @@ function clientVisibilityOf(capabilities, sdkScreenTool) {
|
|
|
11494
11626
|
const hidesReasoning = !capabilities.reasoning;
|
|
11495
11627
|
const hidesTools = !capabilities.toolDetails;
|
|
11496
11628
|
const hidesModel = capabilities.modelDetails === false;
|
|
11497
|
-
|
|
11629
|
+
const hidesErrorDetails = hidesModel || capabilities.errorDetails !== true;
|
|
11630
|
+
if (!hidesReasoning && !hidesTools && !hidesModel && !hidesErrorDetails) return SHOWS_EVERYTHING;
|
|
11498
11631
|
const hidesCall = (toolName, call) => hidesTools && !sdkScreenTool(toolName) && !markedForScreen(call);
|
|
11499
11632
|
const hiddenMetadataKeys = [...hidesReasoning ? ["reasoningDurationsMs"] : [], ...hidesModel ? MODEL_METADATA_KEYS : []];
|
|
11500
|
-
const visibleMetadata = (metadata) =>
|
|
11633
|
+
const visibleMetadata = (metadata) => {
|
|
11634
|
+
if (typeof metadata !== "object" || metadata === null) return metadata;
|
|
11635
|
+
const shown = without(metadata, hiddenMetadataKeys);
|
|
11636
|
+
return hidesErrorDetails ? withoutErrorDetails(shown) : shown;
|
|
11637
|
+
};
|
|
11501
11638
|
const withoutProviderData2 = (carrier) => hidesModel ? without(carrier, PROVIDER_METADATA_KEYS) : hidesReasoning ? withoutReasoningDetails(carrier) : carrier;
|
|
11502
11639
|
return {
|
|
11503
11640
|
showsEverything: false,
|
|
@@ -12208,10 +12345,18 @@ function createChatService(input) {
|
|
|
12208
12345
|
scope.capabilities ??= askCapabilities(userId, chat);
|
|
12209
12346
|
return scope.capabilities;
|
|
12210
12347
|
}
|
|
12348
|
+
let warnedErrorDetailsWithoutModel = false;
|
|
12349
|
+
function warnErrorDetailsWithoutModel(given) {
|
|
12350
|
+
if (warnedErrorDetailsWithoutModel || given?.errorDetails !== true || given.modelDetails !== false) return;
|
|
12351
|
+
warnedErrorDetailsWithoutModel = true;
|
|
12352
|
+
logError("chatCapabilities: errorDetails: true \u043F\u0440\u0438 modelDetails: false \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D\u043E \u2014 \u043F\u043E\u0434\u0440\u043E\u0431\u043D\u043E\u0441\u0442\u0438 \u0441\u0431\u043E\u044F \u043D\u0430\u0437\u044B\u0432\u0430\u044E\u0442 \u043C\u043E\u0434\u0435\u043B\u044C \u0438 \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440\u0430.");
|
|
12353
|
+
}
|
|
12211
12354
|
async function askCapabilities(userId, chat) {
|
|
12212
12355
|
if (!options2.chatCapabilities) return resolveChatCapabilities(void 0);
|
|
12213
12356
|
try {
|
|
12214
|
-
|
|
12357
|
+
const given = await options2.chatCapabilities({ userId, chat });
|
|
12358
|
+
warnErrorDetailsWithoutModel(given);
|
|
12359
|
+
return resolveChatCapabilities(given);
|
|
12215
12360
|
} catch (error) {
|
|
12216
12361
|
logError(`chatCapabilities \u0443\u043F\u0430\u043B \u0438\u043B\u0438 \u0432\u0435\u0440\u043D\u0443\u043B \u043D\u0435\u0432\u0435\u0440\u043D\u044B\u0435 \u043F\u0440\u0430\u0432\u0430 (\u0447\u0430\u0442 ${chat.id}): ${error instanceof Error ? error.message : String(error)}`);
|
|
12217
12362
|
throw error;
|
|
@@ -14276,4 +14421,4 @@ export {
|
|
|
14276
14421
|
InvalidWidgetAnswerError,
|
|
14277
14422
|
createChatService
|
|
14278
14423
|
};
|
|
14279
|
-
//# sourceMappingURL=chunk-
|
|
14424
|
+
//# sourceMappingURL=chunk-EWEEYX6U.js.map
|