@ykurilov/agent-chat-react 0.0.0-stage → 0.55.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.
Files changed (106) hide show
  1. package/CHANGELOG.md +1670 -0
  2. package/LICENSE +21 -0
  3. package/README.md +128 -2
  4. package/dist/chunk-JZQ7ZOJI.js +6959 -0
  5. package/dist/chunk-JZQ7ZOJI.js.map +1 -0
  6. package/dist/index.js +501 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/panel.js +399 -0
  9. package/dist/panel.js.map +1 -0
  10. package/dist/types/agent-chat-extensions.d.ts +76 -0
  11. package/dist/types/agent-chat.d.ts +103 -0
  12. package/dist/types/agent-panel/agent-panel-hooks.d.ts +14 -0
  13. package/dist/types/agent-panel/agent-panel-provider.d.ts +24 -0
  14. package/dist/types/agent-panel/agent-panel-skin.d.ts +41 -0
  15. package/dist/types/agent-panel/agent-panel.d.ts +13 -0
  16. package/dist/types/agent-panel/index.d.ts +8 -0
  17. package/dist/types/agent-panel/panel-context.d.ts +46 -0
  18. package/dist/types/agent-panel/panel-state.d.ts +12 -0
  19. package/dist/types/answer-editing/answer-editing.d.ts +25 -0
  20. package/dist/types/app-bridge/app-bridge.d.ts +50 -0
  21. package/dist/types/app-bridge/browser-calls.d.ts +24 -0
  22. package/dist/types/app-bridge/create-app-bridge.d.ts +35 -0
  23. package/dist/types/app-bridge/remembered-ids.d.ts +6 -0
  24. package/dist/types/app-bridge/tab-id.d.ts +10 -0
  25. package/dist/types/app-events.d.ts +39 -0
  26. package/dist/types/app-refusal.d.ts +21 -0
  27. package/dist/types/attachments-contract.d.ts +82 -0
  28. package/dist/types/background-tasks/use-background-tasks.d.ts +53 -0
  29. package/dist/types/chat-access.d.ts +33 -0
  30. package/dist/types/chat-list/active-chat-memory.d.ts +5 -0
  31. package/dist/types/chat-list/chat-list-client.d.ts +35 -0
  32. package/dist/types/chat-list/chat-list-skin.d.ts +63 -0
  33. package/dist/types/chat-list/chat-workspace.d.ts +17 -0
  34. package/dist/types/chat-list/use-chat-list-view.d.ts +12 -0
  35. package/dist/types/chat-list/use-chat-list.d.ts +47 -0
  36. package/dist/types/chat-list/use-workspace-list-sync.d.ts +7 -0
  37. package/dist/types/chat-state/chat-state.d.ts +27 -0
  38. package/dist/types/chat-types.d.ts +85 -0
  39. package/dist/types/chat-watch/chat-watch.d.ts +57 -0
  40. package/dist/types/close-stopped-tool-calls.d.ts +8 -0
  41. package/dist/types/composer-actions.d.ts +29 -0
  42. package/dist/types/diagrams/diagram-renders.d.ts +16 -0
  43. package/dist/types/diagrams/mermaid-blocks.d.ts +18 -0
  44. package/dist/types/diagrams/unique-svg-ids.d.ts +6 -0
  45. package/dist/types/diagrams/use-diagram-blocks.d.ts +25 -0
  46. package/dist/types/diagrams/use-diagram-colors.d.ts +8 -0
  47. package/dist/types/dictation-field.d.ts +19 -0
  48. package/dist/types/expanded-parts.d.ts +11 -0
  49. package/dist/types/feed-announcement.d.ts +7 -0
  50. package/dist/types/helpers/helper-call.d.ts +57 -0
  51. package/dist/types/index.d.ts +53 -0
  52. package/dist/types/labels.d.ts +468 -0
  53. package/dist/types/live-demos/answer-fences.d.ts +25 -0
  54. package/dist/types/live-demos/live-demo-document.d.ts +42 -0
  55. package/dist/types/live-demos/live-demo-frame.d.ts +14 -0
  56. package/dist/types/live-demos/use-live-demo-blocks.d.ts +18 -0
  57. package/dist/types/liveness/connection-errors.d.ts +15 -0
  58. package/dist/types/liveness/reconnect-attempts.d.ts +14 -0
  59. package/dist/types/liveness/request-deadline.d.ts +16 -0
  60. package/dist/types/liveness/silence-watchdog.d.ts +21 -0
  61. package/dist/types/media-viewer/answer-file-images.d.ts +18 -0
  62. package/dist/types/media-viewer/collect-viewer-items.d.ts +16 -0
  63. package/dist/types/media-viewer/generated-images.d.ts +18 -0
  64. package/dist/types/media-viewer/media-viewer-types.d.ts +17 -0
  65. package/dist/types/media-viewer/open-attachments.d.ts +9 -0
  66. package/dist/types/media-viewer/use-media-viewer.d.ts +23 -0
  67. package/dist/types/memory/agent-memory.d.ts +13 -0
  68. package/dist/types/memory/memory-client.d.ts +19 -0
  69. package/dist/types/memory/memory-skin.d.ts +103 -0
  70. package/dist/types/memory/memory-tree.d.ts +17 -0
  71. package/dist/types/memory/memory-types.d.ts +77 -0
  72. package/dist/types/memory/use-memory-notes.d.ts +23 -0
  73. package/dist/types/message-actions.d.ts +45 -0
  74. package/dist/types/message-feed.d.ts +27 -0
  75. package/dist/types/notices/agent-notices.d.ts +40 -0
  76. package/dist/types/pause.d.ts +2 -0
  77. package/dist/types/server-refusal.d.ts +16 -0
  78. package/dist/types/skin-contract.d.ts +673 -0
  79. package/dist/types/spending-status.d.ts +76 -0
  80. package/dist/types/steering/corrections.d.ts +19 -0
  81. package/dist/types/step-reset.d.ts +15 -0
  82. package/dist/types/subject-chat/subject-chat-loading.d.ts +8 -0
  83. package/dist/types/subject-chat/use-subject-chat-id.d.ts +21 -0
  84. package/dist/types/thinking-status/thinking-status.d.ts +33 -0
  85. package/dist/types/turn-finished.d.ts +14 -0
  86. package/dist/types/use-agent-chat.d.ts +228 -0
  87. package/dist/types/use-model-choice.d.ts +31 -0
  88. package/dist/types/use-voice-dialog.d.ts +18 -0
  89. package/dist/types/use-voice-dictation.d.ts +17 -0
  90. package/dist/types/voice-dialog.d.ts +103 -0
  91. package/dist/types/voice-input.d.ts +47 -0
  92. package/dist/types/voice-tags.d.ts +18 -0
  93. package/dist/types/voice-turn.d.ts +11 -0
  94. package/dist/types/widgets/custom-widget-guard.d.ts +21 -0
  95. package/dist/types/widgets/form-check.d.ts +29 -0
  96. package/dist/types/widgets/suggested-replies.d.ts +6 -0
  97. package/dist/types/widgets/use-widget-answer.d.ts +54 -0
  98. package/dist/types/widgets/widget-answer-keys.d.ts +14 -0
  99. package/dist/types/widgets/widget-messages.d.ts +14 -0
  100. package/dist/types/widgets/widget-part.d.ts +17 -0
  101. package/dist/types/widgets/widget-view.d.ts +50 -0
  102. package/dist/types/without-markdown.d.ts +1 -0
  103. package/dist/types/work-view/step-phrases.d.ts +37 -0
  104. package/dist/types/work-view/work-layout.d.ts +57 -0
  105. package/dist/types/work-view/work-steps-part.d.ts +15 -0
  106. package/package.json +53 -3
@@ -0,0 +1,76 @@
1
+ /** Чей лимит и за какой срок; как в ответе сервера. */
2
+ export type SpendingScope = "user" | "chat" | "service";
3
+ /** `rolling` — окно на `hours` часов от первого запроса. */
4
+ export type SpendingPeriod = "month" | "week" | "day" | "rolling" | "none";
5
+ /** Почему ход не начнётся: лимит исчерпан, человек заблокирован или остаток меньше запаса на новый ответ. */
6
+ export type SpendingRefusalReason = "limit" | "blocked" | "reserve";
7
+ /** Одно окно лимита: доля и срок, без долларов. У окна сообщений — и сами числа. */
8
+ export interface SpendingWindowInfo {
9
+ id: string;
10
+ scope: SpendingScope;
11
+ period: SpendingPeriod;
12
+ hours?: number;
13
+ state: "ok" | "warning" | "exhausted";
14
+ share: number;
15
+ resetsAt: Date | null;
16
+ /** Окно считает сообщения человека; нет — деньги. */
17
+ unit?: "messages";
18
+ /** Окно сообщений: лимит, отправлено и осталось. */
19
+ limit?: number;
20
+ used?: number;
21
+ remaining?: number;
22
+ }
23
+ /** Что известно о лимите: из этого слой поведения собирает надпись. Долларов здесь нет. */
24
+ export interface SpendingInfo {
25
+ scope: SpendingScope;
26
+ period: SpendingPeriod;
27
+ /** Длина скользящего окна в часах. */
28
+ hours?: number;
29
+ /** Доля израсходованного, 0…1 и больше при перерасходе; `null` — сервер её не назвал (отказ хода). */
30
+ share: number | null;
31
+ /** Когда расход обнулится; `null` — никогда. */
32
+ resetsAt: Date | null;
33
+ /** Пояс лимитов (IANA), по которому показывать дату обновления; нет — UTC. */
34
+ timeZone?: string;
35
+ /** Почему ход не начнётся (лимиты с окнами); нет — исчерпан лимит. */
36
+ reason?: SpendingRefusalReason;
37
+ /** Имя самого тесного окна (лимиты с окнами). */
38
+ windowId?: string;
39
+ /** Все окна с лимитом по отдельности (лимиты с окнами): приложение может показать их сами. */
40
+ windows?: SpendingWindowInfo[];
41
+ /** Самое тесное окно считает сообщения человека; нет — деньги. */
42
+ unit?: "messages";
43
+ /** Сколько сообщений осталось в этом окне; в отказе 429 числа нет. */
44
+ remaining?: number;
45
+ }
46
+ /** Лимит, о котором надо сказать собеседнику: пока всё в пределах, показывать нечего. */
47
+ export interface SpendingView {
48
+ state: "warning" | "exhausted";
49
+ info: SpendingInfo;
50
+ }
51
+ /** Ответ сервера на `GET …/spending` и тело отказа 429 (в отказе `state` нет). */
52
+ interface WireSpending {
53
+ state?: string;
54
+ scope?: string;
55
+ period?: string;
56
+ hours?: number;
57
+ share?: number;
58
+ resetsAt?: string | null;
59
+ timeZone?: string;
60
+ reason?: string;
61
+ windowId?: string;
62
+ blocked?: boolean;
63
+ windows?: unknown;
64
+ unit?: string;
65
+ remaining?: number;
66
+ }
67
+ /** Не дальше, чем умеет `setTimeout`: месяц с лишним в него не влезает. */
68
+ export declare const MAX_TIMER_MS = 2147483647;
69
+ /** Как часто спрашивать сервер, если срок обновления уже прошёл, а лимит ещё стоит (часы вкладки и сервера расходятся). */
70
+ export declare const RESET_RECHECK_MS = 5000;
71
+ export declare function spendingInfoOf(wire: WireSpending): SpendingInfo;
72
+ /** `null` — лимит в пределах или ответ не разобрать: надписи нет. */
73
+ export declare function spendingViewOf(wire: WireSpending): SpendingView | null;
74
+ /** Через сколько спросить сервер снова, чтобы снять исчерпание сразу после обновления. */
75
+ export declare function untilResetCheckMs(info: SpendingInfo, now: number): number | null;
76
+ export {};
@@ -0,0 +1,19 @@
1
+ import type { FileUIPart } from "ai";
2
+ import type { AgentChatMessage } from "../chat-types.js";
3
+ /** Отметка поправки в частях сообщения: написано во время ответа (как у сервера, core: steering/turn-steering.ts). */
4
+ export declare const STEER_PART_TYPE = "data-agent-steer";
5
+ /** Поправка на экране: текст и файлы человека с отметкой поправки. */
6
+ export declare function correctionMessage(id: string, text: string, files: readonly FileUIPart[]): AgentChatMessage;
7
+ export declare function isCorrection(message: Pick<AgentChatMessage, "role" | "parts">): boolean;
8
+ /**
9
+ * Ответы, за которыми идёт поправка: агент закончил их на границе шага, чтобы взять поправку. Пустой такой ответ —
10
+ * не «модель не дала ответа». Строка номеров через пробел: не меняется, пока не меняется состав.
11
+ */
12
+ export declare function steeredAnswerIdsOf(feed: readonly AgentChatMessage[]): string;
13
+ /** Сообщение для `POST …/steer`: отметку поправки сервер ставит сам. */
14
+ export declare function outgoingCorrection(correction: AgentChatMessage): AgentChatMessage;
15
+ /**
16
+ * Поправки после чтения истории: ждущие на сервере, затем свои, что ещё идут на сервер. Своя, которая уже в ленте
17
+ * или среди ждущих, второй раз не показывается.
18
+ */
19
+ export declare function correctionsAfterHistory(waiting: readonly AgentChatMessage[], shown: readonly AgentChatMessage[], own: readonly AgentChatMessage[], sendingIds: ReadonlySet<string>): AgentChatMessage[];
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Отметка ядра «шаг начат заново у другого провайдера»: всё от последней части `step-start` до неё стёрто.
3
+ * Копия логики ядра (`withoutResetSteps`): React не зависит от ядра. Что копии совпадают, проверяет общий тест.
4
+ */
5
+ export declare const STEP_RESET_PART_TYPE = "data-step-reset";
6
+ /** Части ответа без стёртых шагов: каждая часть `data-step-reset` убирает себя и всё от последней `step-start` перед ней. Сброса нет — тот же массив. */
7
+ export declare function withoutResetSteps<Part extends {
8
+ type: string;
9
+ }>(parts: readonly Part[]): Part[];
10
+ /** Сообщения без стёртых шагов; ничего не стёрто — тот же массив. */
11
+ export declare function withoutResetMessages<Message extends {
12
+ parts: Array<{
13
+ type: string;
14
+ }>;
15
+ }>(messages: Message[]): Message[];
@@ -0,0 +1,8 @@
1
+ import type { ChatSkin } from "../skin-contract.js";
2
+ /**
3
+ * Чат объекта ещё ищется: тот же вид, что у обычного чата, пока грузится история, — пустая лента и поле ввода без
4
+ * отправки. Экран не прыгает, когда чат найден.
5
+ */
6
+ export declare function SubjectChatLoading({ skin }: {
7
+ skin: ChatSkin;
8
+ }): import("react").JSX.Element;
@@ -0,0 +1,21 @@
1
+ import type { AppRefusal } from "../app-refusal.js";
2
+ /** Чат объекта: ещё ищется, найден (id), сервер отказал с текстом или запрос не удался (можно повторить). */
3
+ export type SubjectChatState = {
4
+ state: "loading";
5
+ } | {
6
+ state: "ready";
7
+ chatId: string;
8
+ } | {
9
+ state: "refused";
10
+ text: string;
11
+ } | {
12
+ state: "failed";
13
+ };
14
+ /**
15
+ * `POST {api}/subject { key }` при открытии и смене ключа: сервер находит или создаёт чат объекта человека. Ответ
16
+ * помнится вместе с запросом, на который он пришёл: при смене ключа прежний чат не показывается ни одного кадра.
17
+ * Отказ самого приложения (`turn_refused`, `access_refused`) уходит в `onAppRefusal` — по разу на ответ сервера.
18
+ */
19
+ export declare function useSubjectChatId(api: string, subject: string, onAppRefusal?: (refusal: AppRefusal) => void): SubjectChatState & {
20
+ retry: () => void;
21
+ };
@@ -0,0 +1,33 @@
1
+ import type { ReactNode } from "react";
2
+ /** Как часто сменяется фраза ожидания. Таймер тикает раз в секунду, поэтому фраза держится 3–4 с. */
3
+ export declare const THINKING_PHRASE_MS = 3500;
4
+ /** Номер следующей фразы: любой, кроме предыдущего. */
5
+ export declare function nextThinkingPhraseIndex(count: number, previous: number, random?: () => number): number;
6
+ /** Часы всех ответов чата по id: лента выгружает ответ, ушедший из вида, а его счёт сохраняется. */
7
+ export declare function ThinkingClocksProvider({ children }: {
8
+ children: ReactNode;
9
+ }): import("react").JSX.Element;
10
+ /**
11
+ * Общие часы ожидания одного ответа: строка «думает» без текста и блок рассуждений после неё продолжают тот же счёт
12
+ * и ту же череду фраз. `AgentChat` ставит его вокруг каждого ответа агента. С `id` внутри `ThinkingClocksProvider` часы
13
+ * переживают перемонтирование; ответ дописан (`waiting: false`) — часы забываются.
14
+ */
15
+ export declare function ThinkingClockProvider({ id, waiting, children }: {
16
+ id?: string;
17
+ waiting?: boolean;
18
+ children: ReactNode;
19
+ }): import("react").JSX.Element;
20
+ export interface ThinkingStatus {
21
+ /** Текущая фраза: из `labels.thinkingPhrases`, пустой набор — `labels.reasoningRunning`. */
22
+ phrase: string;
23
+ /** Секунды с начала ожидания ответа. */
24
+ seconds: number;
25
+ /** Те же секунды словами: «12 с», «1 мин 5 с» (`labels.thinkingElapsed`). */
26
+ elapsed: string;
27
+ }
28
+ /**
29
+ * Живая надпись, пока модель думает: фраза сменяется каждые 3–4 с без повтора подряд, рядом — время ожидания.
30
+ * Неактивная (`active: false`) таймер не держит. Скин рисует фразу и время сам; для экранного диктора — постоянная
31
+ * `labels.reasoningRunning`, а меняющиеся фразы от него скрыты, чтобы не зачитывались каждые три секунды.
32
+ */
33
+ export declare function useThinkingStatus(active?: boolean): ThinkingStatus;
@@ -0,0 +1,14 @@
1
+ import type { AgentChatMessage, AnswerStatus } from "./chat-types.js";
2
+ /** Ход закончен на экране: ответ получил итог. */
3
+ export interface TurnFinishedEvent {
4
+ chatId: string;
5
+ /** Id ответа агента — тот же, что в истории на сервере. */
6
+ messageId: string;
7
+ status: Exclude<AnswerStatus, "streaming">;
8
+ }
9
+ /**
10
+ * Зовёт `onTurnFinished` один раз на ход: чат был занят ответом, освободился, и у последнего ответа есть итог
11
+ * (законченный, остановленный, с ошибкой, прерванный, ждущий человека). Открытие чата с готовой историей хода не даёт.
12
+ * Ответ, законченный ради поправки, — свой ход и на сервере: он получает событие первым, перед ответом на поправку.
13
+ */
14
+ export declare function useTurnFinishedReport(chatId: string, busy: boolean, messages: readonly AgentChatMessage[], onTurnFinished: ((turn: TurnFinishedEvent) => void) | undefined): void;
@@ -0,0 +1,228 @@
1
+ import type { FileUIPart } from "ai";
2
+ import type { AgentChatMessage, ApprovalDecision, ApprovalFailureKind, BranchFork, ChatModelChoice, CompactionMarker, VoiceCut, VoiceSound } from "./chat-types.js";
3
+ import type { SpendingView } from "./spending-status.js";
4
+ import type { AgentAppBridge } from "./app-bridge/app-bridge.js";
5
+ import type { AppRefusal } from "./app-refusal.js";
6
+ import type { ServerRefusal } from "./server-refusal.js";
7
+ import type { ChatCapabilities, ChatClosure } from "./chat-access.js";
8
+ import type { AppEvent } from "./app-events.js";
9
+ import type { WidgetFailureKind } from "./widgets/use-widget-answer.js";
10
+ import type { ChatStateValues } from "./chat-state/chat-state.js";
11
+ /** Настройки живой связи. Всё необязательно: по умолчанию сроки из договора 002. */
12
+ export interface LivenessOptions {
13
+ /** Сколько тишины терпеть в потоке ответа. По умолчанию 30 000 мс. */
14
+ silenceTimeoutMs?: number;
15
+ /** Паузы перед попытками переподключения. По умолчанию [0, 2000, 5000]. */
16
+ reconnectDelaysMs?: readonly number[];
17
+ /** Срок одной попытки и любого запроса без потока. По умолчанию 10 000 мс. */
18
+ requestTimeoutMs?: number;
19
+ }
20
+ /** Как вкладка видит связь с сервером. */
21
+ export type ConnectionState = "online" | "reconnecting" | "offline";
22
+ export interface UseAgentChatOptions {
23
+ /** Адрес обработчика чата, например `/api/chat`. */
24
+ api: string;
25
+ chatId: string;
26
+ /** Свой `fetch` — для тестов и особых сред. */
27
+ fetch?: typeof globalThis.fetch;
28
+ liveness?: LivenessOptions;
29
+ /** У сервера включены лимиты расходов: читать состояние лимита и показывать его. Отказ 429 обрабатывается всегда. */
30
+ spendingLimits?: boolean;
31
+ /** Связь с приложением: место, вкладка, сообщения об изменении данных, браузерные вызовы. Нет — чат как раньше. */
32
+ appBridge?: AgentAppBridge;
33
+ /** Текст в поле ввода при открытии чата. */
34
+ initialDraft?: string;
35
+ /** Приложение отказало ходу или человеку в доступе: код для программы и слова для человека. Зовётся на каждый такой отказ. */
36
+ onTurnRefused?: (refusal: AppRefusal) => void;
37
+ /**
38
+ * Событие приложения пришло по каналу наблюдения: вне хода (`publishAppEvent` на сервере, без `messageId`) или от
39
+ * хука итога, доделанного после «Стопа» (`messageId` — ответ, куда оно уже дописано; история перечитывается сама).
40
+ * Канал открыт, пока чат свободен: событие, опубликованное во время хода, вкладка не получит. События в ответах
41
+ * читает `AgentChat.onAppEvent`.
42
+ */
43
+ onAppEvent?: (event: AppEvent, messageId?: string) => void;
44
+ /**
45
+ * Новые события ответа `messageId` придут живыми с перечитанной историей: ответ на виджет принят без продолжения,
46
+ * события его проверки уже дописаны в ответ. `AgentChat` передаёт сюда `expectLive` учёта ленты.
47
+ */
48
+ onExpectLiveAppEvents?: (messageId: string) => void;
49
+ /**
50
+ * Поправки на лету: пока идёт ответ, `submit` отправляет текст поправкой (`POST …/steer`), агент берёт её на ближайшем
51
+ * шаге. Своя лента включает их явно (`true`) и рисует `corrections` после `messages`; по умолчанию выключено — во время
52
+ * ответа поле не отправляет, как до 0.34.
53
+ */
54
+ steering?: boolean;
55
+ /** Другое значение — история перечитывается с сервера (права, закрытие, лента): чат изменился не через экран. Первое значение ничего не перечитывает. */
56
+ reloadKey?: number;
57
+ }
58
+ export interface AgentChatState {
59
+ messages: AgentChatMessage[];
60
+ /** Записи сжатия истории на текущей ветке: на их границах лента рисует разделители. */
61
+ compactions: CompactionMarker[];
62
+ /** Развилки текущей ветки: у сообщения с соседями лента рисует переключатель «‹ 2/3 ›». */
63
+ forks: BranchFork[];
64
+ /** История загружена с сервера. */
65
+ loaded: boolean;
66
+ value: string;
67
+ setValue: (value: string) => void;
68
+ canSubmit: boolean;
69
+ /**
70
+ * Идёт ответ, и поле принимает поправки: `submit` отправит текст (или файлы) поправкой, агент возьмёт её на ближайшем
71
+ * шаге. Пустое поле `submit` не отправит, как и при `canSubmit`.
72
+ */
73
+ canSteer: boolean;
74
+ /**
75
+ * Поправки, которые агент ещё не взял: лента рисует их после сообщений, с пометкой. Взятая поправка уходит отсюда в
76
+ * `messages`.
77
+ */
78
+ corrections: AgentChatMessage[];
79
+ /** Идёт ответ или вкладка переподключается к нему. */
80
+ busy: boolean;
81
+ /** Отправляет введённый текст; `files` — готовые вложения (ссылки на загруженные файлы): тогда текст может быть пуст. */
82
+ submit: (files?: readonly FileUIPart[], onRejected?: (kind: string | undefined) => void) => boolean;
83
+ stop: () => Promise<void>;
84
+ /**
85
+ * Отправляет слова из голосового режима (с признаком `voiceMode`, чтобы ответ был коротким и разговорным).
86
+ * `false` — не ушло: пусто, чат не загружен, ответ ещё идёт, лимит исчерпан или ждёт подтверждения инструмента.
87
+ */
88
+ sendVoiceMessage: (text: string, options?: {
89
+ sounds?: readonly VoiceSound[];
90
+ }) => boolean;
91
+ /** Ставит у ответа отметку обрыва озвучки на экране; на сервер ничего не уходит. */
92
+ showVoiceCut: (messageId: string, cut: VoiceCut) => void;
93
+ /**
94
+ * Склейка голосовых фраз: останавливает ход, у которого ещё нет ничего, кроме размышления, и правкой заменяет последнее
95
+ * голосовое сообщение новым текстом (прежнее с пустым ответом уходит в соседнюю ветку); модель отвечает на новое.
96
+ * `sounds` — звуки человека, пойманные с этой частью фразы: уходят на сервер, на экране не видны.
97
+ * Выполнено, когда сервер принял правку. `false` — заменить нельзя (у ответа уже есть текст или вызов инструмента,
98
+ * сообщение не голосовое, сервер отказал): сообщение не заменено, ход мог быть остановлен.
99
+ * Вызовы идут по очереди.
100
+ */
101
+ replaceLastVoiceMessage: (text: string, options?: {
102
+ sounds?: readonly VoiceSound[];
103
+ }) => Promise<boolean>;
104
+ /** Последний ответ можно повторить: ошибка, стоп, прерывание или пустой ответ. */
105
+ canRetry: boolean;
106
+ /** Сообщение собеседника не дошло до сервера. */
107
+ sendFailed: boolean;
108
+ /** История чата не загрузилась: сервер ответил ошибкой. */
109
+ loadFailed: boolean;
110
+ /** Сервер ответил 404: чат чужой или его нет. Это окончательно, повторять нечего. */
111
+ notFound: boolean;
112
+ /** Выбор модели, сохранённый в чате (из истории); `null`, пока история не загружена и при скрытой модели. */
113
+ savedModelChoice: ChatModelChoice | null;
114
+ /** Видит ли модель чата картинки — из истории; `undefined`, пока история не пришла, модели нет в каталоге или сервер старый. */
115
+ modelAcceptsImages: boolean | undefined;
116
+ /**
117
+ * В чат попал чужой текст — из истории или куском потока посреди ответа. Раз появившись, не пропадает: чужой текст
118
+ * остаётся в истории чата. Живые демонстрации в таком чате не запускаются.
119
+ */
120
+ externalContent: boolean;
121
+ /** Связь с сервером: обычная работа, идут попытки подключиться, попытки исчерпаны. */
122
+ connection: ConnectionState;
123
+ /**
124
+ * Новый ответ на последний вопрос; если вопрос не дошёл до сервера — отправляет его заново;
125
+ * если не загрузилась история — грузит её снова; при «Нет связи» — начинает попытки заново.
126
+ */
127
+ retry: () => void;
128
+ /** Ответ человека на запрос подтверждения; когда отвечены все запросы, ход продолжается и лента дописывается. */
129
+ respondToApproval: (approvalId: string, decision: ApprovalDecision) => Promise<void>;
130
+ /** Запрос, ответ на который сейчас идёт на сервер. */
131
+ approvalPendingId: string | null;
132
+ /** Запрос, ответ на который не принят (нет связи, лимит, чат занят): кнопки снова доступны. */
133
+ approvalFailure: {
134
+ id: string;
135
+ kind: ApprovalFailureKind;
136
+ text?: string;
137
+ } | null;
138
+ /**
139
+ * Ответ человека на виджет (вызов `toolCallId`); `true` — сервер принял, ход продолжается и лента дописывается.
140
+ * `onAccepted` — сразу, как сервер принял, не дожидаясь продолжения.
141
+ */
142
+ answerWidget: (toolCallId: string, answer: unknown, onAccepted?: () => void) => Promise<boolean>;
143
+ /** Виджет, ответ на который сейчас идёт на сервер. */
144
+ widgetPendingId: string | null;
145
+ /** Виджет, ответ на который не принят: он снова активен. */
146
+ widgetFailure: {
147
+ id: string;
148
+ kind: WidgetFailureKind;
149
+ text?: string;
150
+ } | null;
151
+ /** Обычная отправка готового текста мимо поля ввода (подсказка ответа); `false` — не ушло. */
152
+ sendReply: (text: string) => boolean;
153
+ /** Переключает текущую ветку на соседнее сообщение развилки; во время ответа не делает ничего. `true` — история новой ветки показана. */
154
+ selectBranch: (messageId: string) => Promise<boolean>;
155
+ /** Последний ответ можно переделать: он законченный и непустой (у остальных — «повторить»). */
156
+ canRegenerate: boolean;
157
+ /** Новый ответ на последний вопрос; прежний остаётся соседней веткой. */
158
+ regenerate: () => void;
159
+ /** Отправленное сообщение можно править: ничего не идёт и связь есть. */
160
+ canEdit: boolean;
161
+ /**
162
+ * Правка отправленного сообщения: новая ветка от того же места с новым текстом и новым ответом.
163
+ * Отвечает, как только сервер принял правку или отказал, не дожидаясь конца ответа. При отказе лента возвращается
164
+ * к прежнему виду: тексту, который собеседник правил, есть куда вернуться.
165
+ */
166
+ edit: (messageId: string, text: string) => Promise<EditOutcome>;
167
+ /** Лимит расходов подошёл к пределу или исчерпан; `null` — всё в пределах, лимитов нет или они не включены. */
168
+ spending: SpendingView | null;
169
+ /**
170
+ * Сервер отказал не из-за связи: `signed_out` — вход истёк (401), `too_many_turns` — у человека уже идут ответы
171
+ * в других чатах, `forbidden_origin` — сервер не признал страницу своей (403, адреса нет в `allowedOrigins`).
172
+ * `null` — отказа нет. Вход снимается любым успешным запросом, остальные — принятым ходом и сменой чата.
173
+ */
174
+ refusal: ChatRefusal | null;
175
+ /**
176
+ * Приложение не пустило человека в этот чат (`resolveUser` вернул отказ) на загрузке истории: слова приложения вместо чата.
177
+ * `null` — доступ есть.
178
+ */
179
+ accessRefusal: ServerRefusal | null;
180
+ /**
181
+ * Сервер отказал ходу 4xx с понятным текстом (`{ error: "…" }` или `{ error: { message: "…" } }`): текст до
182
+ * 300 знаков. `final` — повтор того же запроса снова получит отказ (всё, кроме 408, 425 и 429): «Повторить» не нужна.
183
+ * Отправленное сообщение при окончательном отказе возвращается в поле. Снимается следующим ходом и сменой чата.
184
+ * Отказ правке показывает сам редактор. `null` — такого отказа нет.
185
+ */
186
+ serverRefusal: ServerRefusal | null;
187
+ /**
188
+ * Счётчик «список фоновых задач мог измениться»: растёт при каждом открытии подписки `GET {chat}/watch` и на её
189
+ * событие `tasks-changed`. Сервер без подписки — всегда 0.
190
+ */
191
+ tasksVersion: number;
192
+ /** Сервер не заявил, что фона у него нет (`backgroundTasks: false` в истории): задачи можно спрашивать. Старый сервер без заявки — `true`. */
193
+ backgroundTasksAvailable: boolean;
194
+ /** Права человека в чате (из истории); сервер до 0.27 их не присылает — всё разрешено. */
195
+ capabilities: ChatCapabilities;
196
+ /**
197
+ * Чат закрыт: отправка, «Повторить», виджеты и подтверждения недоступны. Правка, «Переделать», ветки и правка ответа
198
+ * уводят с точки закрытия и идут по правам (`capabilities`). Приходит с историей, отказом 409 `chat_closed`, куском
199
+ * потока ответа и событиями наблюдения. `null` — чат открыт.
200
+ */
201
+ closed: ChatClosure | null;
202
+ /** Состояние чата показанной ветки (`model.state` на сервере): живьём по ходу ответа и после смены ветки. */
203
+ chatState: ChatStateValues;
204
+ }
205
+ /** Отказ сервера, о котором говорит надпись над полем ввода. */
206
+ export type ChatRefusal = "signed_out" | "too_many_turns" | "forbidden_origin";
207
+ /** Причина, по которой правка не ушла на сервер. */
208
+ export type EditNotStartedReason = "busy" | "offline" | "empty" | "unknown_message";
209
+ /** Причина отказа сервера: ответ уже идёт, правка неверна, сообщения в чате нет, лимит расходов исчерпан. */
210
+ export type EditRefusalReason = "busy" | "invalid" | "not_found" | "spending_limit" | "signed_out" | "too_many_turns" | "forbidden_origin" | "server_refused";
211
+ /**
212
+ * Чем кончилась попытка правки. `started` — сервер принял, идёт новый ответ (обрыв связи после этого — обычный путь «Повторить»).
213
+ * У `server_refused` есть `text` — причина отказа словами сервера (4xx с текстом); если отказало приложение — ещё и `code`.
214
+ * У `spending_limit` из-за окна сообщений — `unit: "messages"`; у денег поля нет.
215
+ */
216
+ export type EditOutcome = {
217
+ status: "started";
218
+ } | {
219
+ status: "not_started";
220
+ reason: EditNotStartedReason;
221
+ } | {
222
+ status: "refused";
223
+ reason: EditRefusalReason;
224
+ text?: string;
225
+ code?: string;
226
+ unit?: "messages";
227
+ };
228
+ export declare function useAgentChat({ api, chatId, fetch: customFetch, liveness, spendingLimits, appBridge, initialDraft, onTurnRefused, onAppEvent, onExpectLiveAppEvents, reloadKey, steering, }: UseAgentChatOptions): AgentChatState;
@@ -0,0 +1,31 @@
1
+ import type { ChatModelChoice } from "./chat-types.js";
2
+ import type { ModelChoiceView } from "./skin-contract.js";
3
+ export interface UseModelChoiceOptions {
4
+ /** Адрес обработчика чата, например `/api/chat`. */
5
+ api: string;
6
+ chatId: string;
7
+ /** Выбор, сохранённый в чате (из истории). Пока его нет, показывается выбор по умолчанию. */
8
+ savedChoice?: ChatModelChoice | null;
9
+ /**
10
+ * История чата ещё грузится, и выбор чата не пришёл. Пока это так, выбор по умолчанию не показывается: у нового чата
11
+ * выбор человека уже запомнен на сервере, и показ выбора по умолчанию мигнул бы чужой моделью.
12
+ */
13
+ savedChoicePending?: boolean;
14
+ /**
15
+ * Нужно ли меню. `false` — меню не запрашивается: права ещё не пришли с историей или модель скрыта
16
+ * (`modelDetails: false`). По умолчанию `true`.
17
+ */
18
+ menuWanted?: boolean;
19
+ /** Свой `fetch` — для тестов и особых сред. */
20
+ fetch?: typeof globalThis.fetch;
21
+ /**
22
+ * Человек выбрал модель или уровень в меню, и сервер сохранил выбор: получает его, как он сохранён. Выбор по
23
+ * умолчанию, выбор из истории и отказ сервера его не зовут.
24
+ */
25
+ onModelChosen?: (choice: ChatModelChoice) => void;
26
+ }
27
+ /**
28
+ * Выбор модели и уровня рассуждений для поля ввода. Живёт отдельно от `useAgentChat`, чтобы смена выбора
29
+ * не перерисовывала ленту. Смена видна сразу; при отказе сервера выбор откатывается к последнему сохранённому.
30
+ */
31
+ export declare function useModelChoice({ api, chatId, savedChoice, savedChoicePending, menuWanted, fetch: customFetch, onModelChosen }: UseModelChoiceOptions): ModelChoiceView;
@@ -0,0 +1,18 @@
1
+ import type { VideosClient } from "./agent-chat-extensions.js";
2
+ import type { AgentChatState } from "./use-agent-chat.js";
3
+ import type { VoiceDialogEngine, VoiceDialogView } from "./voice-dialog.js";
4
+ type DialogChat = Pick<AgentChatState, "messages" | "busy" | "stop" | "sendVoiceMessage" | "replaceLastVoiceMessage" | "showVoiceCut">;
5
+ export interface UseVoiceDialogOptions {
6
+ /** Голосовой режим приложения; нет — кнопки нет и `useVoiceDialog` возвращает `undefined`. */
7
+ engine: VoiceDialogEngine | undefined;
8
+ chatId: string;
9
+ chat: DialogChat;
10
+ /** Видео агента: на месте показанного видео звучит `labels.voice.videoOnScreen`. */
11
+ videos?: VideosClient;
12
+ }
13
+ /**
14
+ * Связывает голосовой режим с чатом: даёт движку мост (отправка слов, остановка, текст ответа, конец ответа, отметка
15
+ * обрыва) и возвращает вид для кнопки в поле ввода. Мост не знает, как режим слушает и говорит; движок не знает, как устроен чат.
16
+ */
17
+ export declare function useVoiceDialog({ engine, chatId, chat, videos }: UseVoiceDialogOptions): VoiceDialogView | undefined;
18
+ export {};
@@ -0,0 +1,17 @@
1
+ import type { VoiceInputView } from "./skin-contract.js";
2
+ import type { VoiceInput } from "./voice-input.js";
3
+ export interface UseVoiceDictationOptions {
4
+ /** Нет голоса — нет и кнопки. */
5
+ voice: VoiceInput | undefined;
6
+ value: string;
7
+ setValue: (value: string) => void;
8
+ chatId: string;
9
+ }
10
+ export interface VoiceDictation {
11
+ /** Что отдать полю ввода скина; `undefined` — голоса нет. */
12
+ view: VoiceInputView | undefined;
13
+ /** Бросить идущую диктовку (при отправке): поздние слова в поле не попадут. Без диктовки ничего не делает. */
14
+ cancel: () => void;
15
+ }
16
+ /** Диктовка в поле ввода: состояние кнопки микрофона и запись слов в поле. */
17
+ export declare function useVoiceDictation({ voice, value, setValue, chatId }: UseVoiceDictationOptions): VoiceDictation;
@@ -0,0 +1,103 @@
1
+ import type { AnswerStatus, VoiceCut, VoiceSound } from "./chat-types.js";
2
+ /** Состояние голосового режима глазами человека. `interrupted` — человек только что перебил, агент замолчал. */
3
+ export type VoiceDialogState = "off" | "connecting" | "listening" | "thinking" | "speaking" | "interrupted" | "error";
4
+ /** Что пошло не так с режимом, в терминах человека; готовые надписи живут в словаре чата (`voiceDialog.errors`). */
5
+ export type VoiceDialogError = "microphone_denied" | "microphone_missing" | "network" | "service_unavailable" | "recognition_lost" | "speech_unavailable" | "limit" | "rate_limited" | "idle" | "backgrounded";
6
+ export interface VoiceDialogVoice {
7
+ id: string;
8
+ name: string;
9
+ }
10
+ /** Голосовой режим в поле ввода (договор скина, версия 10). */
11
+ export interface VoiceDialogView {
12
+ state: VoiceDialogState;
13
+ error?: VoiceDialogError;
14
+ /** `false` — слушаем и пишем, но не озвучиваем. */
15
+ speechAvailable: boolean;
16
+ /** Нет или один голос — меню не показывать. */
17
+ voices?: VoiceDialogVoice[];
18
+ currentVoiceId?: string;
19
+ /** Громкость микрофона 0..1 для индикации. */
20
+ level?: number;
21
+ /** Включить или выключить режим. Вызывается из нажатия: браузер разрешает звук только так. */
22
+ onToggle: () => void;
23
+ onSelectVoice?: (id: string) => void;
24
+ }
25
+ /** Список голосов сервера и выбор человека; `persisted: false` — сервер выбор не запоминает. */
26
+ export interface VoiceDialogVoices {
27
+ voices: VoiceDialogVoice[];
28
+ current: string;
29
+ persisted: boolean;
30
+ }
31
+ /** Приращение текста ответа агента, идущего сейчас. `partIndex` — номер текстовой части в `parts` сообщения. */
32
+ export interface AssistantTextDelta {
33
+ messageId: string;
34
+ partIndex: number;
35
+ text: string;
36
+ }
37
+ /** Ответ агента закончился; `status` — чем именно (остановлен, ошибка и так далее). */
38
+ export interface AnswerEnd {
39
+ messageId: string;
40
+ status: AnswerStatus;
41
+ }
42
+ /**
43
+ * Модель размышляет или вызывает инструмент: часть `reasoning` или `tool-*` ответа началась (`start`) или закончилась (`end`).
44
+ * Для `end` при ошибке инструмента и при конце ответа тоже приходит, так что пары всегда замкнуты.
45
+ */
46
+ export interface AssistantActivity {
47
+ messageId: string;
48
+ /** Номер части в `parts` сообщения: по нему начало и конец сопоставляются. */
49
+ partIndex: number;
50
+ kind: "reasoning" | "tool";
51
+ /** Имя инструмента; только у `tool`. */
52
+ toolName?: string;
53
+ phase: "start" | "end";
54
+ }
55
+ /**
56
+ * Что движок режима знает о чате: отправить слова, остановить ответ и следить за текстом ответа.
57
+ * Настоящий мост делает хук `useVoiceDialog`; отправка идёт с признаком голосового режима (`voiceMode`).
58
+ */
59
+ export interface DialogChatBridge {
60
+ /**
61
+ * Отправить слова агенту. Если отправить нельзя (пусто, ответ ещё идёт, чат не загружен),
62
+ * мост сообщает об этом через `subscribeAnswerEnd` как об ответе со статусом `error` и пустым `messageId`.
63
+ */
64
+ send(text: string, options?: {
65
+ sounds?: readonly VoiceSound[];
66
+ }): void;
67
+ /** Остановить идущий ответ; выполнено, когда ход остановлен. */
68
+ stop(): Promise<void>;
69
+ /**
70
+ * Склейка (`options.sounds` — звуки человека, ещё не отправленные модели): вместо последнего сообщения человека отправить `text` (прежние слова вместе с новыми) и остановить ход, у которого ещё нет
71
+ * ни текста, ни звука. Прежнего сообщения и пустого оборванного ответа в чате не остаётся, модель отвечает один раз.
72
+ * `true` — склеено, идёт ответ на `text`. `false` — не вышло (у ответа уже есть текст, сообщение набрано не голосом, сервер отказал):
73
+ * ход мог быть остановлен, текст не отправлен. Необязательный: мост без него — новые слова в ожидании ответа идут отдельным сообщением.
74
+ */
75
+ replaceLastMessage?(text: string, options?: {
76
+ sounds?: readonly VoiceSound[];
77
+ }): Promise<boolean>;
78
+ /** Приращения текста ответа. Возвращает отписку. */
79
+ subscribeAssistantText(listener: (delta: AssistantTextDelta) => void): () => void;
80
+ /** Конец ответа. Возвращает отписку. */
81
+ subscribeAnswerEnd(listener: (end: AnswerEnd) => void): () => void;
82
+ /**
83
+ * Размышление и вызовы инструментов: по ним голос говорит заполнители («Сейчас поищу…»). Необязательный: мост без него —
84
+ * режим работает, заполнителей нет. Возвращает отписку.
85
+ */
86
+ subscribeAssistantActivity?(listener: (activity: AssistantActivity) => void): () => void;
87
+ /** Показать отметку обрыва у ответа сразу, до записи на сервере (у хранилища может не быть `setVoiceCut`). */
88
+ showCut(messageId: string, cut: VoiceCut): void;
89
+ }
90
+ export interface VoiceDialogOpenOptions {
91
+ chatId: string;
92
+ chat: DialogChatBridge;
93
+ /** Вызывается при каждом изменении вида режима. */
94
+ onView(view: VoiceDialogView): void;
95
+ }
96
+ /** Голосовой режим для `AgentChat`; настоящий делает `createVoiceDialogEngine` из `@ykurilov/agent-chat-voice-dialog`. */
97
+ export interface VoiceDialogEngine {
98
+ /** Включить режим. Звать внутри нажатия пользователя. Дальше состояние приходит через `onView`. */
99
+ open(options: VoiceDialogOpenOptions): void;
100
+ close(): void;
101
+ selectVoice(id: string): Promise<void>;
102
+ loadVoices(): Promise<VoiceDialogVoices>;
103
+ }