@filigran/chatbot 3.13.0 → 3.15.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/dist/index.d.ts CHANGED
@@ -1,6 +1,13 @@
1
+ import * as react from 'react';
1
2
  import { FunctionComponent } from 'react';
2
3
 
4
+ /** The modes the header's view switcher offers. */
3
5
  type ChatMode = 'sidebar' | 'floating' | 'fullscreen';
6
+ /**
7
+ * Every mode the panel renders in. `inline` fills the host's container instead
8
+ * of floating over the page, for a host that gives the chat a page of its own.
9
+ */
10
+ type ChatPanelMode = ChatMode | 'inline';
4
11
  /**
5
12
  * The host's translation lookup, passed down to every component that renders
6
13
  * text. Key in, translated string out — the package never owns a dictionary of
@@ -71,6 +78,9 @@ interface ApiEndpoints {
71
78
  * No default, for the same reason as {@link ApiEndpoints.approve}: a proxied
72
79
  * host has to expose the route before the panel starts calling it. Unset, the
73
80
  * live flow still works and only reload recovery is absent.
81
+ *
82
+ * A voice call also reads its `turn` here when the turn its question waits
83
+ * on has been silent for 120 s: `running` keeps the wait.
74
84
  */
75
85
  pendingApprovals?: string | null;
76
86
  /** Path for fetching agents. Default: '/chat/agents'. Set to null to disable. */
@@ -140,6 +150,108 @@ interface ApiEndpoints {
140
150
  * REST backend only, and not in `singleEndpoint` mode.
141
151
  */
142
152
  feedback?: string | null;
153
+ /**
154
+ * Path of the caller's workspaces, which the conversation list is grouped
155
+ * by (XTM One serves it at `/chat/workspaces`). The panel lists them with
156
+ * `GET {workspaces}`, creates one with `POST`, renames one with
157
+ * `PATCH {workspaces}/{id}` and deletes one with `DELETE {workspaces}/{id}`;
158
+ * it files a conversation with `PATCH {history}/{conversation_id}` and
159
+ * `{ workspace_id }`, and starts one inside a workspace by sending
160
+ * `workspace_id` with the session `POST`.
161
+ *
162
+ * No default, for the same reason as {@link ApiEndpoints.approve}: a
163
+ * proxied host has to expose all of those routes before the panel offers
164
+ * the feature. Unset (or a backend that refuses the list), the history is
165
+ * the flat list it always was.
166
+ *
167
+ * REST backend only, and not in `singleEndpoint` mode.
168
+ */
169
+ workspaces?: string | null;
170
+ /**
171
+ * Path of the conversations a message can reference with `@` in the
172
+ * composer (XTM One serves it at `/chat/conversation-references`). Typing
173
+ * `@` opens a menu searched with
174
+ * `GET {conversationReferences}?q=<typed text>&limit=8&exclude=<current conversation id>`,
175
+ * answered with `{ conversations: [{ id, title, key, updated_at, is_own }] }`:
176
+ * the conversations the user can open, most recent first. A pick inserts
177
+ * `@<key>`, and the message is sent with the ids of the picks it still
178
+ * holds as `referenced_conversation_ids` (at most 5); a restored user
179
+ * message lists them under `conversation_refs`.
180
+ *
181
+ * No default, for the same reason as {@link ApiEndpoints.approve}: a proxied
182
+ * host has to expose the route, and forward the field, before the composer
183
+ * offers the menu. Unset, `@` is plain text and the message body is
184
+ * unchanged.
185
+ *
186
+ * REST backend only, and not in `singleEndpoint` mode.
187
+ */
188
+ conversationReferences?: string | null;
189
+ /**
190
+ * Base path of the decks, reports and videos an answer made (XTM One serves
191
+ * it at `/chat/artifacts`). Their cards download one through
192
+ * `POST {apiBaseUrl}{artifacts}/{kind}/{id}/download` with `{ format }`, and
193
+ * follow a video still rendering through `GET {apiBaseUrl}{artifacts}/{kind}/{id}`.
194
+ *
195
+ * No default, for the same reason as {@link ApiEndpoints.approve}: a proxied
196
+ * host has to forward both routes before the cards offer them. Unset, the
197
+ * cards still render, with `Open in XTM One` and no download.
198
+ *
199
+ * REST backend only.
200
+ */
201
+ artifacts?: string | null;
202
+ /**
203
+ * Path that stops the running turn on the backend, POSTed `{ conversation_id }`
204
+ * by Stop. For a backend whose turns outlive their stream: the panel then waits
205
+ * for the stream's `cancelled` frame rather than dropping the stream. Unset,
206
+ * Stop drops the stream.
207
+ *
208
+ * No default. REST backend only.
209
+ */
210
+ cancel?: string | null;
211
+ /**
212
+ * Path that ends an agent's announced wait (`status: wait_start`) early,
213
+ * POSTed `{ conversation_id }` by the waiting indicator's "Continue now".
214
+ *
215
+ * No default. REST backend only.
216
+ */
217
+ skipWait?: string | null;
218
+ /**
219
+ * Base path of a conversation's event stream, read as
220
+ * `GET {apiBaseUrl}{events}/{conversation_id}/events?client_id=<panel>`: an
221
+ * SSE stream of the turns this panel did not start (another tab, a reload
222
+ * mid-turn), `user_message`, `conversation_title` and `approval_resolved`.
223
+ * The message `POST` then carries the same id in `X-Chat-Client-Id`, so the
224
+ * backend never relays a panel's own turn back to it.
225
+ *
226
+ * No default. REST backend only.
227
+ */
228
+ events?: string | null;
229
+ /**
230
+ * Base path of a conversation's older messages, read as
231
+ * `GET {messagesPage}/{conversation_id}/messages?before=<oldest message id>&limit=50&supports_artifacts=true`
232
+ * and answered with `{ messages, has_more }` (entries shaped like a restore's).
233
+ * The restore's own `has_more` says whether there are any; "Load earlier
234
+ * messages" then fetches them a page at a time.
235
+ *
236
+ * No default. REST backend only.
237
+ */
238
+ messagesPage?: string | null;
239
+ /**
240
+ * Path that deletes every conversation of the history, as `DELETE {clearAll}`:
241
+ * the conversation list then offers "Clear all".
242
+ *
243
+ * No default. REST backend only.
244
+ */
245
+ clearAll?: string | null;
246
+ /**
247
+ * Path that reads text aloud: `POST {tts}` with `{ text, format: 'mp3' }`,
248
+ * answered with the audio. Read aloud and the voice call
249
+ * (`ChatPanelHandle.startVoiceCall`) then use the backend's voice, and the
250
+ * browser's when the route fails.
251
+ *
252
+ * No default. REST backend only.
253
+ */
254
+ tts?: string | null;
143
255
  }
144
256
  /** A reusable prompt the user can insert into the composer. */
145
257
  interface ChatPromptTemplate {
@@ -263,9 +375,11 @@ interface ChatContextBreakdown {
263
375
  toolResults?: number;
264
376
  }
265
377
  interface ChatPanelProps {
266
- mode: ChatMode;
267
- onClose: () => void;
268
- onModeChange: (mode: ChatMode) => void;
378
+ mode: ChatPanelMode;
379
+ /** Shows the close button; inline mode has none. */
380
+ onClose?: () => void;
381
+ /** Shows the view switcher; inline mode has none. */
382
+ onModeChange?: (mode: ChatMode) => void;
269
383
  topOffset?: number;
270
384
  apiBaseUrl: string;
271
385
  /** Custom API endpoint configuration. */
@@ -273,19 +387,31 @@ interface ChatPanelProps {
273
387
  /**
274
388
  * The XTM One URL: the header's "Browse agents" / "Create agent" links, and
275
389
  * the waiting panel's invitation to play the arcade there
276
- * (`{agentDashboardUrl}/chat?arcade=play`). Only an http(s) URL is linked.
390
+ * (`{agentDashboardUrl}/chat?arcade=play`, unless `waitingGameUrl` says
391
+ * where). Only an http(s) URL is linked.
277
392
  */
278
393
  agentDashboardUrl?: string;
394
+ /**
395
+ * Where the waiting panel's invitation to play leads, over
396
+ * `{agentDashboardUrl}/chat?arcade=play`: an http(s) URL or a path of the
397
+ * host's own origin (`/…`); anything else links nothing.
398
+ */
399
+ waitingGameUrl?: string;
279
400
  user: {
280
401
  firstName: string;
281
402
  };
403
+ /**
404
+ * The host's translations, over the panel's own catalog: a key it returns
405
+ * unchanged falls back to the catalog in `locale`, or to English when no
406
+ * `locale` is passed.
407
+ */
282
408
  t?: Translate;
283
409
  accentColor?: string;
284
410
  logoIcon?: React.ReactNode;
285
411
  promptSuggestions?: string[];
286
- /** Optional text shown as a small chip in the chat header (e.g. draft context). */
412
+ /** Accepted, and not shown. */
287
413
  draftLabel?: string;
288
- /** Optional color used for the chat header bottom border in draft context. */
414
+ /** Color of the line above the composer (e.g. a draft context). */
289
415
  draftBorderColor?: string;
290
416
  /** Enable resizable sidebar (drag to resize). Only applies when mode is 'sidebar'. */
291
417
  resizable?: boolean;
@@ -381,9 +507,11 @@ interface ChatPanelProps {
381
507
  */
382
508
  onMessageFeedback?: (messageId: string, feedback: MessageFeedback | null, message: ChatMessage) => void;
383
509
  /**
384
- * The user's language as a BCP 47 tag (`'fr'`, `'en-US'`): formats the time
385
- * under each message and is the language answers are read aloud in. Default:
386
- * the browser's own.
510
+ * The user's language as a BCP 47 tag (`'fr'`, `'en-US'`): the language the
511
+ * panel speaks (its own catalog: en, es, fr, ja) where `t` does not
512
+ * translate a key, formats the time under each message and is the language
513
+ * answers are read aloud in. Default: the browser's own, except that a host
514
+ * passing `t` and no `locale` gets no catalog.
387
515
  */
388
516
  locale?: string;
389
517
  /**
@@ -415,6 +543,318 @@ interface ChatPanelProps {
415
543
  * to keep in step, only the presence or absence of what a host provides.
416
544
  */
417
545
  composerToolbar?: React.ReactNode;
546
+ /**
547
+ * Namespaces every key the panel keeps in browser storage (conversation,
548
+ * agent, drafts, widths), for a host mounting more than one panel.
549
+ */
550
+ storageKeyPrefix?: string;
551
+ /**
552
+ * The conversation on screen, when the host owns it (a `?c=` URL): `null`
553
+ * is a new chat. Left `undefined`, the panel remembers its own.
554
+ */
555
+ conversationId?: string | null;
556
+ /** Called whenever the conversation on screen changes, whoever changed it. */
557
+ onConversationChange?: (conversationId: string | null) => void;
558
+ /** Agent selected once the list loads, by id or slug, over the remembered one. */
559
+ initialAgent?: string;
560
+ /** Called when another agent is picked. */
561
+ onAgentChange?: (agent: XtmAgent) => void;
562
+ /** Hides the composer: a conversation the user may read but not write to. */
563
+ readOnly?: boolean;
564
+ /** Shown in place of the composer while `readOnly`. */
565
+ readOnlyNotice?: React.ReactNode;
566
+ /**
567
+ * Fields merged into every REST message body, read at send time: the host's
568
+ * own per-message options (tool selection, run mode). They never override
569
+ * the panel's fields (`content`, `conversation_id`, …).
570
+ */
571
+ getRequestExtras?: () => Record<string, unknown> | undefined;
572
+ /**
573
+ * Every frame received, on the message stream (`relayed: false`) or the event
574
+ * stream (`relayed: true`), including those the panel does not render: the
575
+ * host's way to react to backend-specific events.
576
+ */
577
+ onStreamEvent?: (event: Record<string, unknown>, info: ChatStreamEventInfo) => void;
578
+ /**
579
+ * The user's session ended: a `session_expired` frame on either stream, or a
580
+ * `401` whose body's `detail.code` is `session_expired` on any request the
581
+ * panel makes. *reason* is the frame's or the `detail`'s `reason`, when it
582
+ * gives one. Called once per set of `requestHeaders`: again only once they
583
+ * change.
584
+ */
585
+ onSessionEnded?: (reason: string | undefined) => void;
586
+ /**
587
+ * Cards for the `[[KIND:args]]` markers an answer embeds, by marker name
588
+ * (`WORK_ITEM`, `INVESTIGATION`): rendered where the marker stands. Naming
589
+ * `FILE`, `PRESENTATION`, `REPORT` or `VIDEO` replaces the built-in card. A
590
+ * marker no renderer names stays text.
591
+ */
592
+ markerRenderers?: Record<string, ChatMarkerRenderer>;
593
+ /**
594
+ * Renderers for fenced code blocks, by the fence's language (```` ```chart ````,
595
+ * matched case-insensitively): the node returned stands in place of the code
596
+ * block (a chart, a diagram, a table drawn from the block's data). The
597
+ * renderer gets the block's text, and `streaming` while the answer is still
598
+ * coming, when the block may be incomplete. One that throws, or whose node
599
+ * does, shows `This item could not be loaded.` and leaves the rest of the
600
+ * answer alone. A language no renderer names stays a code block.
601
+ */
602
+ codeBlockRenderers?: Record<string, ChatCodeBlockRenderer>;
603
+ /**
604
+ * Rewrites a message's text before it is shown, copied or read aloud: the
605
+ * host's own markup (masking secrets, converting a channel's syntax). One
606
+ * that throws leaves the text as it came.
607
+ */
608
+ transformMessageText?: (text: string, message: ChatMessage) => string;
609
+ /** Opens a deck, report or video in the host itself, in place of the link to XTM One. */
610
+ onArtifactOpen?: (artifact: ChatArtifact) => void;
611
+ /** Controls added to the header, before New chat. */
612
+ headerActions?: React.ReactNode;
613
+ /** Shown above the thread: notices about the conversation as a whole. */
614
+ banner?: React.ReactNode;
615
+ /** Shown above the composer, inside it: what the next message will act on. */
616
+ composerHeader?: React.ReactNode;
617
+ /** Shown after the last message, inside the thread. */
618
+ transcriptFooter?: React.ReactNode;
619
+ /** Replaces the welcome screen of an empty conversation. */
620
+ welcome?: React.ReactNode;
621
+ /** Rendered under a message's content, before its footer; `context.streaming` while the answer is still coming. */
622
+ renderMessageExtras?: (message: ChatMessage, context: ChatMessageSlotContext) => React.ReactNode;
623
+ /** Controls added to a finished message's footer. */
624
+ messageFooterActions?: (message: ChatMessage, context: ChatMessageSlotContext) => React.ReactNode;
625
+ /** Controls added to a conversation's row in the conversation list. */
626
+ sidebarRowActions?: (conversation: ChatConversationSummary) => React.ReactNode;
627
+ /** Controls added beside New conversation, atop the conversation list. */
628
+ sidebarHeaderActions?: React.ReactNode;
629
+ /** Sections added under the conversation list (conversations shared with the user). */
630
+ sidebarSections?: React.ReactNode;
631
+ /** The status line's text for a status, over the built-in one; `undefined` keeps it. */
632
+ statusLabel?: (status: AgentStatusState) => string | undefined;
633
+ /** The rotating messages of the waiting panel, already translated. */
634
+ waitingMessages?: string[];
635
+ /** Replaces the waiting panel's content (its messages and the invitation to play). */
636
+ renderWaitingPanel?: () => React.ReactNode;
637
+ /** Font of the panel's text; the package's own otherwise. */
638
+ fontFamily?: string;
639
+ /**
640
+ * Called once, when the panel has chosen its agent and read back the
641
+ * conversation on screen. Calls made through the handle before then wait
642
+ * for it, and run in order.
643
+ */
644
+ onReady?: () => void;
645
+ /**
646
+ * Sees every send first, from the composer or the handle: `false` takes it
647
+ * over (nothing is sent, the composer keeps its text), an object rewords it
648
+ * (`text`), shows the user something other than what is sent (`displayText`)
649
+ * or adds fields to its body (`extras`).
650
+ */
651
+ onBeforeSend?: (draft: ChatSendDraft) => ChatSendDecision;
652
+ /**
653
+ * What became of a message held until the turn ended (a send that resolved
654
+ * `'held'`): sent, refused, dropped (nothing of it the user typed: Stop ended
655
+ * its turn, or `onBeforeSend` took it over as it leaves), given back to the
656
+ * composer (the same, with what the user typed for it; told once the
657
+ * composer shows it, so `getDraft` reads it), or cancelled (by the user, or
658
+ * a new conversation). A message sent after a stopped turn ended goes out
659
+ * once that turn winds down. Messages held together settle together.
660
+ */
661
+ onHeldSendSettled?: (text: string, outcome: ChatHeldOutcome) => void;
662
+ /**
663
+ * Called when a turn starts or stops running: on screen (the Stop button and
664
+ * the status line show), or a relayed turn let go of while the page was
665
+ * hidden; the state `ChatSendDraft.turnRunning` reports. True for the
666
+ * panel's own turn from its send, and for a turn the event stream relays;
667
+ * false once it ends - its stream closes after the last `done` (an
668
+ * intermediate one goes on), an `error`, `cancelled` or `session_expired`
669
+ * frame, a Stop, a new conversation - and when the panel unmounts during a
670
+ * turn. A relayed turn let go of with the event stream runs on until the
671
+ * reopened stream replays it, or the conversation read back says how it
672
+ * ended. Called on changes only, never with the value it last gave. With
673
+ * `false` comes how the turn ended (`info.ended`): answered, stopped or
674
+ * failed (see `ChatTurnOutcome`). After a provisional end (an `error` or
675
+ * `cancelled` whose stored end is still to come, from a backend whose turns
676
+ * outlive their stream) `false` comes at once, while the panel still holds
677
+ * sends as the turn winds down: until its stored end or its stream's close,
678
+ * at most 5 s.
679
+ */
680
+ onTurnRunningChange?: (running: boolean, info?: ChatTurnEnd) => void;
681
+ /**
682
+ * Uploaded files may steer the running turn: the steer route takes
683
+ * `file_ids`. Without it, a message with files waits for the turn to end.
684
+ */
685
+ steerAttachments?: boolean;
686
+ /**
687
+ * A message that cannot steer the running turn (files that cannot steer,
688
+ * references, mentions, fields of its own, a turn that just ended) is held
689
+ * and sent as the next turn once it ends,
690
+ * rather than waiting in the composer. A turn winding down after a
691
+ * provisional end (until its stored end or its stream's close, at most 5 s;
692
+ * see `onTurnRunningChange`) holds sends with or without it.
693
+ */
694
+ holdSendsDuringTurn?: boolean;
695
+ /**
696
+ * `@` in the composer also mentions the agents of the agent list (above the
697
+ * conversations, when those are offered): the message carries the ids of
698
+ * the agents it still mentions as `mentioned_agent_ids`, in mention order.
699
+ * A message that mentions an agent never steers the running turn.
700
+ */
701
+ agentMentions?: boolean;
702
+ /**
703
+ * Hides the conversation list that fullscreen and inline modes show beside
704
+ * the thread, for a panel too narrow for both (a rail, a task pane; the
705
+ * thread, header and composer hold at 320 px). The history stays: the
706
+ * header's history menu lists, opens and deletes conversations and offers
707
+ * Clear all, and a referenced conversation still opens in the panel. The
708
+ * `sidebar*` slots render nothing meanwhile. Default: false.
709
+ */
710
+ hideConversationList?: boolean;
711
+ }
712
+ /** What a marker card is drawn from. */
713
+ interface ChatMarkerContext {
714
+ message: ChatMessage;
715
+ /** The answer is still streaming. */
716
+ streaming: boolean;
717
+ }
718
+ /** How one kind of `[[KIND:args]]` marker renders. */
719
+ interface ChatMarkerRenderer {
720
+ /** The card for one marker; *args* is everything after `KIND:`. */
721
+ render: (args: string, context: ChatMarkerContext) => React.ReactNode;
722
+ /** Show it while the answer streams; by default it shows once the answer is done. */
723
+ renderWhileStreaming?: boolean;
724
+ /** Give it the thread's full width rather than the prose's. */
725
+ fullWidth?: boolean;
726
+ }
727
+ /** What a code block renderer is drawn from. */
728
+ interface ChatCodeBlockContext {
729
+ /** The answer is still streaming: the block may not be complete yet. */
730
+ streaming: boolean;
731
+ }
732
+ /** Renders one fenced code block of a language (see `ChatPanelProps.codeBlockRenderers`); *code* is the block's text. `null` keeps the block as code. */
733
+ type ChatCodeBlockRenderer = (code: string, context: ChatCodeBlockContext) => React.ReactNode;
734
+ /** What a message slot (`renderMessageExtras`, `messageFooterActions`) is drawn with. */
735
+ interface ChatMessageSlotContext {
736
+ /** The message is the answer still being written. */
737
+ streaming: boolean;
738
+ }
739
+ /** Where a frame passed to `onStreamEvent` came from, and what the panel read in it. */
740
+ interface ChatStreamEventInfo {
741
+ /** True for a turn this panel did not start, relayed by the event stream. */
742
+ relayed: boolean;
743
+ /**
744
+ * The frame ends a turn: `'provisional'` for an `error` or `cancelled` marked
745
+ * `provisional: true` (its stored end follows), `'stored'` for
746
+ * an end carrying the stored answer's `message_id` (a `done` that is not
747
+ * intermediate, a `cancelled` or an `error`). Absent on any other frame.
748
+ */
749
+ end?: 'provisional' | 'stored';
750
+ /** The frame acknowledges a turn replayed from its start (`thinking` with `resumed`). */
751
+ replayed?: boolean;
752
+ }
753
+ /** What a host can do to a mounted panel, through its `ref`. */
754
+ interface ChatPanelHandle {
755
+ /** Send *text* as the user, leaving the composer's own draft alone; resolves to what became of it. */
756
+ send: (text: string, options?: ChatSendOptions) => Promise<ChatSendOutcome>;
757
+ /** Start a new conversation, in a workspace and with an agent (id or slug) if given. */
758
+ newConversation: (options?: {
759
+ workspaceId?: string;
760
+ agent?: string;
761
+ }) => void;
762
+ selectConversation: (conversationId: string) => void;
763
+ /** Replace the composer's text. */
764
+ setDraft: (text: string) => void;
765
+ /**
766
+ * The composer's text as it stands (what the user typed, or `setDraft` set):
767
+ * e.g. for `onBeforeSend` taking over a held message (`draft.held`), to tell
768
+ * whether the composer still holds what was typed for it.
769
+ */
770
+ getDraft: () => string;
771
+ focusComposer: () => void;
772
+ /** Same as the Stop button; also cancels the question a voice call awaits, and stops it speaking. */
773
+ stop: () => void;
774
+ /**
775
+ * Start a voice call over the panel: the browser listens, a phrase is sent
776
+ * after a second of silence (`onBeforeSend` sees `source: 'voice'`), and the
777
+ * answer is spoken through {@link ApiEndpoints.tts} (the browser's voice
778
+ * without it), until the user ends the call. Only the final answer of the
779
+ * turn that answers the question is spoken, and only the addressed agent's;
780
+ * the other agents' replies are shown by name. `false` when it cannot
781
+ * start: no speech recognition in this browser (`voiceCallSupported()`), or
782
+ * `readOnly`.
783
+ */
784
+ startVoiceCall: () => boolean;
785
+ /** Read the agent list again, keeping the agent picked unless it is gone. */
786
+ reloadAgents: () => void;
787
+ /** Scroll to a message on screen (by its server id) and highlight it; false when it is not there. */
788
+ scrollToMessage: (messageId: string) => boolean;
789
+ }
790
+ /** A send about to leave, as `onBeforeSend` sees it. */
791
+ interface ChatSendDraft {
792
+ text: string;
793
+ /** Typed in the composer, sent through the handle, or spoken in a voice call. */
794
+ source: 'composer' | 'host' | 'voice';
795
+ /** A turn is running. */
796
+ turnRunning: boolean;
797
+ /**
798
+ * A message held while a turn ran, asked about again as it leaves, the turn
799
+ * over (`turnRunning: false`), each send held with it on its own. `false`
800
+ * then gives what the user typed for it (its text, files, references and
801
+ * mentions) back to the composer, after what they typed since. A held
802
+ * message every send of which is taken over is settled `'returned'` by
803
+ * `onHeldSendSettled`, or `'dropped'` when the user typed none of it (a
804
+ * host's or a voice call's).
805
+ */
806
+ held?: boolean;
807
+ }
808
+ /**
809
+ * What became of a send: the backend took it as a new turn, it joined the
810
+ * running one, it waits for that turn to end (or for one ended provisionally
811
+ * to wind down, with or without `holdSendsDuringTurn`), it went nowhere, or
812
+ * the backend refused it.
813
+ */
814
+ type ChatSendOutcome = 'sent' | 'steered' | 'held' | 'dropped' | 'failed';
815
+ /** What became of a held message once its turn ended (see `onHeldSendSettled`). */
816
+ type ChatHeldOutcome = 'sent' | 'failed' | 'dropped' | 'returned' | 'cancelled';
817
+ /**
818
+ * How a turn ended (see `onTurnRunningChange`):
819
+ * - `'answered'`: its last `done` came (not an intermediate one), and nothing
820
+ * after it said otherwise; for a relayed turn that ended out of sight (no
821
+ * replay, or an event stream that could not reopen), the conversation read
822
+ * back holds an answer stored after what was shown;
823
+ * - `'stopped'`: it ended short of its answer - a `cancelled` frame (even
824
+ * after the last `done`), a `session_expired` frame, Stop, a new
825
+ * conversation, the panel unmounting, a stream that closed before its last
826
+ * `done` with no event stream to carry the turn to its end, or a relayed
827
+ * turn that ended out of sight with no answer read back (none stored, or no
828
+ * read-back);
829
+ * - `'failed'`: an `error` frame, or a send the backend refused or could not
830
+ * be reached for, before the turn streamed anything.
831
+ */
832
+ type ChatTurnOutcome = 'answered' | 'stopped' | 'failed';
833
+ /** What `onTurnRunningChange(false, info)` says of the turn that ended. */
834
+ interface ChatTurnEnd {
835
+ ended: ChatTurnOutcome;
836
+ }
837
+ /**
838
+ * What `onBeforeSend` decides: `false` takes the send over, an object adjusts it.
839
+ * `text` is what the backend receives; `displayText` is what the user's bubble
840
+ * shows in its place (a prompt wrapped in the host's context, shown as typed).
841
+ * Without `displayText` the bubble shows the text sent. A conversation read
842
+ * back shows what the backend stored.
843
+ */
844
+ type ChatSendDecision = false | {
845
+ text?: string;
846
+ displayText?: string;
847
+ extras?: Record<string, unknown>;
848
+ } | void;
849
+ /** Per-send options of {@link ChatPanelHandle.send}. */
850
+ interface ChatSendOptions {
851
+ /** Merged into that one message's request body (see `getRequestExtras`). */
852
+ extras?: Record<string, unknown>;
853
+ /**
854
+ * What the user's bubble shows in place of the text sent, as `onBeforeSend`'s
855
+ * `displayText` does (which wins when it gives one).
856
+ */
857
+ displayText?: string;
418
858
  }
419
859
  interface ChatToggleButtonProps {
420
860
  isOpen: boolean;
@@ -422,6 +862,8 @@ interface ChatToggleButtonProps {
422
862
  /** Overrides the built-in label; already translated by the host. */
423
863
  label?: string;
424
864
  t?: Translate;
865
+ /** BCP 47 tag of the label's language when the host does not translate it (see `ChatPanelProps.locale`). */
866
+ locale?: string;
425
867
  accentColor?: string;
426
868
  icon?: React.ReactNode;
427
869
  }
@@ -459,9 +901,29 @@ interface ChatMessage {
459
901
  * back to the conversation's agent, then to the selected one.
460
902
  */
461
903
  agentName?: string;
904
+ /**
905
+ * The agent that wrote this answer, as the backend names it (`answered_by`
906
+ * of its `done` frame or restored entry): on a turn several agents answer,
907
+ * each reply names its own. Absent when the backend does not say.
908
+ */
909
+ answeredBy?: ChatAnsweredBy;
910
+ /**
911
+ * The answer is a note written in place of a mentioned agent that could not
912
+ * answer (`mention_failed`).
913
+ */
914
+ mentionFailed?: boolean;
462
915
  files?: ChatFile[];
916
+ /**
917
+ * The conversations a user message references with `@` (see
918
+ * {@link ApiEndpoints.conversationReferences}), shown as chips.
919
+ */
920
+ conversationRefs?: ChatConversationRef[];
921
+ /** The turn was stopped while writing this answer. */
922
+ stopped?: boolean;
463
923
  /** Agent-generated downloadable files attached to an assistant message. */
464
924
  attachments?: ChatAttachment[];
925
+ /** The decks, reports and videos an assistant message made, in marker order. */
926
+ artifacts?: ChatArtifact[];
465
927
  toolNames?: string[];
466
928
  toolCallCount?: number;
467
929
  iterations?: number;
@@ -486,6 +948,21 @@ interface ChatMessage {
486
948
  * reasoning-details affordance, mirroring the XTM One web chat.
487
949
  */
488
950
  isTruncated?: boolean;
951
+ /**
952
+ * The backend's own fields for the message, as it sent them: the restored
953
+ * (or paged, or relayed) entry, or the `done` frame - or the stored end -
954
+ * that closed a live answer. A plain copy, whose fields are the backend's to
955
+ * define; the panel reads none of it. Absent on a message no backend object
956
+ * describes yet (one being written, a user message sent from this panel).
957
+ */
958
+ raw?: Record<string, unknown>;
959
+ }
960
+ /** The agent an answer names as its author (`answered_by`); either field may be missing. */
961
+ interface ChatAnsweredBy {
962
+ agentId?: string;
963
+ agentName?: string;
964
+ /** An image URL or an emoji, as `XtmAgent.icon`. */
965
+ agentIcon?: string;
489
966
  }
490
967
  /** A rating as the feedback endpoint stores it. */
491
968
  interface ChatMessagePersistedFeedback {
@@ -526,6 +1003,57 @@ interface ChatAttachment {
526
1003
  */
527
1004
  fileTag?: 'download_file' | 'working_file';
528
1005
  }
1006
+ /** What an answer can make besides a file. */
1007
+ type ChatArtifactKind = 'presentation' | 'report' | 'video';
1008
+ /**
1009
+ * A deck, report or video an answer made, as the backend describes it: the
1010
+ * `artifacts` of a `done` event or of a restored answer, sent because the panel
1011
+ * says it renders them (`supports_artifacts`). Rendered as a card where its
1012
+ * `[[PRESENTATION:<id>]]`, `[[REPORT:<id>]]` or `[[VIDEO:<id>]]` marker stands.
1013
+ */
1014
+ interface ChatArtifact {
1015
+ kind: ChatArtifactKind;
1016
+ id: string;
1017
+ title: string;
1018
+ /** The page that shows it in XTM One; linked only when it is http(s). */
1019
+ url?: string;
1020
+ /** The slide a deck's marker points at. */
1021
+ slideRef?: string;
1022
+ slideCount?: number;
1023
+ sectionCount?: number;
1024
+ sceneCount?: number;
1025
+ durationMs?: number;
1026
+ /** A video's newest render: `pending`, `running`, `ready`, `failed` or `cancelled`. */
1027
+ status?: string;
1028
+ /** That render's progress, 0-100. */
1029
+ progress?: number;
1030
+ /** The formats it downloads in: `pdf`, `pptx`, `pptx_native`, `mp4`. Empty until a video is rendered. */
1031
+ downloads: string[];
1032
+ }
1033
+ /** What the agent is doing, as the status line shows it. */
1034
+ interface AgentStatusState {
1035
+ status: string;
1036
+ tools?: string[];
1037
+ thinkingContent?: string;
1038
+ /**
1039
+ * Seconds the current tool batch has been executing (from periodic
1040
+ * `tool_heartbeat` events) — rendered as a live elapsed-time indicator
1041
+ * so long executions (background tasks, consults) never look stuck.
1042
+ */
1043
+ elapsedS?: number;
1044
+ /**
1045
+ * Wall-clock instant (ms) at which the current tool batch started, derived
1046
+ * from `elapsedS` on each heartbeat. Heartbeats only arrive every ~10s, so
1047
+ * rendering `elapsedS` directly makes the timer sit still and then jump.
1048
+ * Anchoring here lets the indicator tick once a second locally and
1049
+ * re-synchronise on every beat.
1050
+ */
1051
+ elapsedStartMs?: number;
1052
+ /** `wait_start`: how long the agent pauses, why, and since when (ms). */
1053
+ waitSeconds?: number;
1054
+ waitReason?: string;
1055
+ waitStartMs?: number;
1056
+ }
529
1057
  interface ChatFile {
530
1058
  name: string;
531
1059
  type: string;
@@ -556,16 +1084,60 @@ interface ChatConversationSummary {
556
1084
  * genuinely have no agent.
557
1085
  */
558
1086
  agentName?: string;
1087
+ /**
1088
+ * The workspace the conversation is filed in (`null`: none), when the
1089
+ * backend reports it. Undefined on backends that do not.
1090
+ */
1091
+ workspaceId?: string | null;
1092
+ }
1093
+ /**
1094
+ * A conversation the composer's `@` menu offers (see
1095
+ * {@link ApiEndpoints.conversationReferences}).
1096
+ */
1097
+ interface ChatConversationReferenceCandidate {
1098
+ id: string;
1099
+ title: string;
1100
+ /** What the pick inserts after `@`: the backend's own, never one computed here. */
1101
+ key: string;
1102
+ updatedAt?: string;
1103
+ /** False for a conversation shared with the user. */
1104
+ isOwn: boolean;
1105
+ }
1106
+ /** A conversation a message references with `@<key>`. */
1107
+ interface ChatConversationRef {
1108
+ conversationId: string;
1109
+ title: string;
1110
+ key: string;
1111
+ /** As the menu offered it: `false` keeps the "Shared with you" hint. Absent reads as the user's own. */
1112
+ isOwn?: boolean;
1113
+ }
1114
+ /** A workspace the conversation list is grouped by. */
1115
+ interface ChatWorkspace {
1116
+ id: string;
1117
+ name: string;
1118
+ /** The caller owns it: a colleague's default shared with them is not theirs. */
1119
+ isOwn: boolean;
1120
+ isDefault: boolean;
1121
+ /** Whether the caller may file conversations into it, rename or delete it. */
1122
+ canManage: boolean;
559
1123
  }
560
1124
  interface XtmAgent {
561
1125
  id: string;
562
1126
  name: string;
563
1127
  slug: string | null;
1128
+ /** An image URL or an emoji; anything else shows the panel's logo. */
564
1129
  icon: string | null;
565
1130
  description: string | null;
1131
+ /** The heading the agent picker lists it under; ungrouped agents come first. */
1132
+ group?: string;
566
1133
  }
567
1134
 
568
- declare const ChatPanel: FunctionComponent<ChatPanelProps>;
1135
+ /**
1136
+ * The panel, its requests reporting the end of the user's session
1137
+ * (`onSessionEnded`): once per set of `requestHeaders`, whichever request or
1138
+ * stream said it first.
1139
+ */
1140
+ declare const ChatPanel: react.ForwardRefExoticComponent<ChatPanelProps & react.RefAttributes<ChatPanelHandle>>;
569
1141
 
570
1142
  declare const ChatToggleButton: FunctionComponent<ChatToggleButtonProps>;
571
1143
 
@@ -574,5 +1146,8 @@ interface TransferredAgent {
574
1146
  name: string;
575
1147
  }
576
1148
 
577
- export { ChatPanel, ChatToggleButton };
578
- export type { ApiEndpoints, BackendType, ChatAttachment, ChatContextBreakdown, ChatContextUsage, ChatConversationSummary, ChatFile, ChatMessage, ChatMessagePersistedFeedback, ChatMode, ChatPanelProps, ChatPromptTemplate, ChatQuotaStatus, ChatToggleButtonProps, MessageFeedback, ToolApprovalDecision, ToolApprovalProposal, ToolApprovalVerdict, TransferredAgent, Translate, XtmAgent };
1149
+ /** Whether this browser can hold a voice call: it needs speech recognition. */
1150
+ declare const voiceCallSupported: () => boolean;
1151
+
1152
+ export { ChatPanel, ChatToggleButton, voiceCallSupported };
1153
+ export type { AgentStatusState, ApiEndpoints, BackendType, ChatAnsweredBy, ChatArtifact, ChatArtifactKind, ChatAttachment, ChatCodeBlockContext, ChatCodeBlockRenderer, ChatContextBreakdown, ChatContextUsage, ChatConversationRef, ChatConversationReferenceCandidate, ChatConversationSummary, ChatFile, ChatHeldOutcome, ChatMarkerContext, ChatMarkerRenderer, ChatMessage, ChatMessagePersistedFeedback, ChatMessageSlotContext, ChatMode, ChatPanelHandle, ChatPanelMode, ChatPanelProps, ChatPromptTemplate, ChatQuotaStatus, ChatSendDecision, ChatSendDraft, ChatSendOptions, ChatSendOutcome, ChatStreamEventInfo, ChatToggleButtonProps, ChatTurnEnd, ChatTurnOutcome, ChatWorkspace, MessageFeedback, ToolApprovalDecision, ToolApprovalProposal, ToolApprovalVerdict, TransferredAgent, Translate, XtmAgent };