cortena-ui 1.5.0 → 1.7.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.
@@ -9,10 +9,11 @@ import { Textarea } from "./textarea.js";
9
9
  import { Markdown } from "./markdown.js";
10
10
  import { A2UIErrorCard } from "../a2ui/views.js";
11
11
  import { A2UIRenderer } from "../a2ui/renderer.js";
12
+ import { isGenericLabel, truncate } from "../agent-chat/step-label.js";
12
13
  import { isHiddenMessage } from "../agent-chat/session.js";
13
14
  import { useAgentChat } from "../agent-chat/store.js";
14
15
  import { sessionTitle } from "../agent-chat/types.js";
15
- import { ChevronDown, ChevronRight, MessageSquarePlus, Send, ShieldCheck, ShieldX, Sparkles, Square } from "lucide-react";
16
+ import { Check, ChevronDown, ChevronRight, MessageSquarePlus, Send, ShieldCheck, ShieldX, Sparkles, Square, X } from "lucide-react";
16
17
  import { jsx, jsxs } from "react/jsx-runtime";
17
18
  import * as React from "react";
18
19
  //#region src/components/agent-chat.tsx
@@ -36,6 +37,13 @@ import * as React from "react";
36
37
  * and it replaces the card, return nothing and the default card is drawn.
37
38
  */
38
39
  /**
40
+ * `useLayoutEffect` in a browser, `useEffect` on a server render, where React
41
+ * warns about the former. The scroll restore after a prepend has to run
42
+ * before the paint: an effect after it shows one frame at the old `scrollTop`,
43
+ * with the page that just landed pushing the reader's place off the screen.
44
+ */
45
+ const useLayoutEffectSafe = typeof window === "undefined" ? React.useEffect : React.useLayoutEffect;
46
+ /**
39
47
  * Should the list jump back to the bottom after its content grew?
40
48
  *
41
49
  * Judged against the height BEFORE the growth. Measuring afterwards asks "is
@@ -74,6 +82,98 @@ function AgentThinking({ text, defaultOpen, streaming }) {
74
82
  });
75
83
  }
76
84
  /**
85
+ * How long a step runs before it starts saying how long it has been running.
86
+ *
87
+ * Under this, a timer is noise: every call takes a second or two and a number
88
+ * ticking beside each one is a fidget spinner. Over it, the user has begun to
89
+ * wonder whether anything is happening at all, and the number is the answer.
90
+ */
91
+ const ELAPSED_AFTER_MS = 8e3;
92
+ /**
93
+ * `Date.now()`, re-read once a second while `active`.
94
+ *
95
+ * The interval only exists while something is running, so a finished chat sits
96
+ * idle rather than re-rendering the whole transcript once a second for ever.
97
+ */
98
+ function useTickingNow(active) {
99
+ const [now, setNow] = React.useState(() => Date.now());
100
+ React.useEffect(() => {
101
+ if (!active) return;
102
+ setNow(Date.now());
103
+ const id = setInterval(() => setNow(Date.now()), 1e3);
104
+ return () => clearInterval(id);
105
+ }, [active]);
106
+ return now;
107
+ }
108
+ /**
109
+ * What the agent is doing, one plain sentence per tool call (SKILLS-9).
110
+ *
111
+ * This is the surface a person watches while they wait, and it is deliberately
112
+ * not the tool strip: the strip is the record — names, arguments, output, the
113
+ * MCP App seam — and reading it requires knowing what `broker_invoke` is. The
114
+ * strip stays at the foot for anyone who wants the detail.
115
+ *
116
+ * NOT a live region of its own. The strip is drawn inside the transcript, and
117
+ * the transcript is already `role="log" aria-live="polite"` — two nested live
118
+ * regions is one announcement per change from the inner one and another from
119
+ * the outer, which is how a screen reader ends up saying every step twice.
120
+ * `aria-label` stays: it names the group for anyone who navigates to it.
121
+ */
122
+ function AgentSteps({ steps }) {
123
+ const now = useTickingNow(steps.some((step) => step.status === "running"));
124
+ if (steps.length === 0) return null;
125
+ return /* @__PURE__ */ jsx("div", {
126
+ "data-slot": "agent-chat-steps",
127
+ "aria-label": "Progress",
128
+ className: "mt-1 flex flex-col gap-0.5",
129
+ children: steps.map((step) => {
130
+ const elapsed = step.status === "running" ? now - step.startedAt : 0;
131
+ const seconds = elapsed >= ELAPSED_AFTER_MS ? Math.floor(elapsed / 1e3) : 0;
132
+ const held = step.status === "running" && isGenericLabel(step.toolName, step.label);
133
+ return /* @__PURE__ */ jsxs("p", {
134
+ "data-slot": "agent-chat-step",
135
+ "data-status": step.status,
136
+ "data-tool": truncate(step.toolName, 80),
137
+ ...held ? { "aria-hidden": true } : {},
138
+ className: cn("flex items-start gap-2 text-[length:var(--ds-text-caption-lg)]", step.status === "error" ? "text-[color:var(--ds-destructive)]" : "text-[color:var(--ds-muted-foreground)]"),
139
+ children: [/* @__PURE__ */ jsx("span", {
140
+ "aria-hidden": true,
141
+ className: "mt-0.5 inline-flex size-3.5 shrink-0 items-center justify-center",
142
+ children: step.status === "running" ? /* @__PURE__ */ jsx(Spinner, {
143
+ size: 12,
144
+ "aria-hidden": true
145
+ }) : step.status === "error" ? /* @__PURE__ */ jsx(X, { className: "size-3" }) : step.status === "stopped" ? /* @__PURE__ */ jsx(Square, { className: "size-2.5" }) : step.status === "paused" ? /* @__PURE__ */ jsx("span", { className: "size-1.5 rounded-full bg-[color:var(--ds-muted-foreground)]" }) : /* @__PURE__ */ jsx(Check, { className: "size-3 text-[color:var(--ds-success)]" })
146
+ }), /* @__PURE__ */ jsxs("span", {
147
+ className: "min-w-0",
148
+ children: [
149
+ /* @__PURE__ */ jsx("span", {
150
+ "data-slot": "agent-chat-step-label",
151
+ children: step.label
152
+ }, step.label),
153
+ seconds > 0 ? /* @__PURE__ */ jsx("span", {
154
+ "aria-hidden": true,
155
+ "data-slot": "agent-chat-step-elapsed",
156
+ children: ` · ${seconds}s`
157
+ }) : null,
158
+ step.status === "stopped" ? /* @__PURE__ */ jsx("span", {
159
+ "data-slot": "agent-chat-step-note",
160
+ children: " — Stopped"
161
+ }) : null,
162
+ step.status === "paused" ? /* @__PURE__ */ jsx("span", {
163
+ "data-slot": "agent-chat-step-note",
164
+ children: " — Waiting for your approval"
165
+ }) : null,
166
+ step.status === "error" && step.errorMessage ? /* @__PURE__ */ jsx("span", {
167
+ "data-slot": "agent-chat-step-error",
168
+ children: ` — ${step.errorMessage}`
169
+ }) : null
170
+ ]
171
+ })]
172
+ }, step.id);
173
+ })
174
+ });
175
+ }
176
+ /**
77
177
  * Every block of one message, in arrival order.
78
178
  *
79
179
  * A block carrying a transport `error` — an unclosed fence, a body that did not
@@ -162,7 +262,7 @@ function AgentMessageBubble({ message, agentName, streaming, onAction }) {
162
262
  * (`**bo`) looks worse than its raw form — so the streaming view is plain text
163
263
  * under `whitespace-pre-wrap` and `Markdown` takes over on `final`.
164
264
  */
165
- function AgentStreamingBubble({ agentName, text, thinkingText, a2ui }) {
265
+ function AgentStreamingBubble({ agentName, text, thinkingText, a2ui, steps }) {
166
266
  return /* @__PURE__ */ jsxs("div", {
167
267
  "data-slot": "agent-chat-streaming",
168
268
  className: "flex w-full gap-3",
@@ -173,6 +273,7 @@ function AgentStreamingBubble({ agentName, text, thinkingText, a2ui }) {
173
273
  className: cn("text-[length:var(--ds-text-caption)] font-semibold", "text-[color:var(--ds-foreground)]"),
174
274
  children: agentName
175
275
  }),
276
+ /* @__PURE__ */ jsx(AgentSteps, { steps: steps ?? [] }),
176
277
  thinkingText ? /* @__PURE__ */ jsx(AgentThinking, {
177
278
  text: thinkingText,
178
279
  defaultOpen: true,
@@ -326,18 +427,68 @@ function AgentApprovalCard({ request, onResolve }) {
326
427
  ]
327
428
  });
328
429
  }
329
- function AgentChat({ client, agentName, storageScope, ephemeral, thinking, attachmentsSlot, renderToolCall, headerSlot, className, ...props }) {
430
+ /**
431
+ * Two ways in, one surface. The hook is called in a component of its own so
432
+ * a host that hands over `chat` never calls it — and so a mount that switches
433
+ * from one mode to the other remounts rather than changing its hook order.
434
+ */
435
+ function AgentChat(props) {
436
+ if (props.chat) {
437
+ const { chat, client: _client, storageScope: _scope, ephemeral: _e, thinking: _t, ...rest } = props;
438
+ return /* @__PURE__ */ jsx(AgentChatSurface, {
439
+ ...rest,
440
+ chat
441
+ });
442
+ }
443
+ return /* @__PURE__ */ jsx(AgentChatOwned, { ...props });
444
+ }
445
+ function AgentChatOwned({ client, storageScope, ephemeral, thinking, chat: _chat, ...rest }) {
330
446
  const chat = useAgentChat({
331
447
  client,
332
448
  storageScope,
333
449
  ...ephemeral === void 0 ? {} : { ephemeral },
334
450
  ...thinking === void 0 ? {} : { thinking }
335
451
  });
452
+ return /* @__PURE__ */ jsx(AgentChatSurface, {
453
+ ...rest,
454
+ chat
455
+ });
456
+ }
457
+ /** The default at the top of the transcript: a button, when there is more. */
458
+ function DefaultLoadOlder({ hasMore, loading, onLoad }) {
459
+ if (!hasMore) return null;
460
+ return /* @__PURE__ */ jsx("div", {
461
+ className: "flex justify-center",
462
+ children: /* @__PURE__ */ jsx(Button, {
463
+ size: "sm",
464
+ variant: "ghost",
465
+ "data-slot": "agent-chat-load-older",
466
+ disabled: loading,
467
+ onClick: onLoad,
468
+ children: loading ? "Loading…" : "Load older messages"
469
+ })
470
+ });
471
+ }
472
+ /** Distance from the bottom, in pixels, within which new content re-pins the view. */
473
+ const NEAR_BOTTOM_PX = 100;
474
+ function AgentChatSurface({ chat, agentName, attachmentsSlot, renderToolCall, headerSlot, hideHeader, components, classNames, autoLoadOlder = false, className, ...props }) {
336
475
  const [drawerOpen, setDrawerOpen] = React.useState(false);
337
476
  const [value, setValue] = React.useState("");
338
477
  const scrollRef = React.useRef(null);
339
478
  const contentRef = React.useRef(null);
479
+ const sentinelRef = React.useRef(null);
340
480
  const prevHeightRef = React.useRef(0);
481
+ /** Whether the reader was at the bottom at the last scroll; the smooth re-pin reads it. */
482
+ const nearBottomRef = React.useRef(true);
483
+ /** The scroll height saved before a page of older history is prepended; 0 when none is in flight. */
484
+ const prependHeightRef = React.useRef(0);
485
+ /** Set by a prepend restore; the resize observer sits out the one notification the prepend produces. */
486
+ const holdRef = React.useRef(false);
487
+ const prevMessageCountRef = React.useRef(0);
488
+ const visible = chat.messages.filter((message) => !isHiddenMessage(message));
489
+ const Empty = components?.Empty;
490
+ const showEmpty = visible.length === 0 && !chat.streaming && !chat.error;
491
+ const showTranscript = !(showEmpty && Empty);
341
492
  /**
342
493
  * Re-pin to the bottom whenever the content grows or the viewport shrinks.
343
494
  * A message keeps growing after it mounts — markdown settles, the tool strip
@@ -346,12 +497,21 @@ function AgentChat({ client, agentName, storageScope, ephemeral, thinking, attac
346
497
  *
347
498
  * Jumping rather than smooth-scrolling: this fires many times a second during
348
499
  * streaming, and queued smooth animations fight each other and lose.
500
+ *
501
+ * Keyed on `showTranscript`: the scroller is unmounted while a host's Empty
502
+ * holds the region, and the observer has to attach to the one that mounts
503
+ * when the first message arrives.
349
504
  */
350
505
  React.useEffect(() => {
351
506
  const scrollEl = scrollRef.current;
352
507
  const contentEl = contentRef.current;
353
508
  if (!scrollEl || !contentEl || typeof ResizeObserver === "undefined") return;
354
509
  const observer = new ResizeObserver(() => {
510
+ if (holdRef.current) {
511
+ holdRef.current = false;
512
+ prevHeightRef.current = scrollEl.scrollHeight;
513
+ return;
514
+ }
355
515
  const { scrollTop, scrollHeight, clientHeight } = scrollEl;
356
516
  const stick = shouldStickToBottom({
357
517
  prevScrollHeight: prevHeightRef.current,
@@ -363,8 +523,84 @@ function AgentChat({ client, agentName, storageScope, ephemeral, thinking, attac
363
523
  });
364
524
  observer.observe(contentEl);
365
525
  observer.observe(scrollEl);
526
+ const onScroll = () => {
527
+ nearBottomRef.current = scrollEl.scrollHeight - scrollEl.scrollTop - scrollEl.clientHeight < NEAR_BOTTOM_PX;
528
+ };
529
+ scrollEl.addEventListener("scroll", onScroll, { passive: true });
530
+ return () => {
531
+ observer.disconnect();
532
+ scrollEl.removeEventListener("scroll", onScroll);
533
+ };
534
+ }, [showTranscript]);
535
+ /**
536
+ * Page older history as the reader reaches the top, not only on a click —
537
+ * when the host asks for it with `autoLoadOlder`. The sentinel is the first
538
+ * child of the transcript; when it scrolls into the viewport and there is
539
+ * more, the next page is asked for.
540
+ */
541
+ const loadOlderChat = chat.loadOlder;
542
+ const loadOlder = React.useCallback(() => {
543
+ const height = scrollRef.current?.scrollHeight ?? 0;
544
+ if (loadOlderChat() === true && height > 0) prependHeightRef.current = height;
545
+ }, [loadOlderChat]);
546
+ React.useEffect(() => {
547
+ const sentinel = sentinelRef.current;
548
+ const scrollEl = scrollRef.current;
549
+ if (!autoLoadOlder || !sentinel || !scrollEl || typeof IntersectionObserver === "undefined") return;
550
+ const observer = new IntersectionObserver((entries) => {
551
+ if (!entries[0]?.isIntersecting) return;
552
+ if (scrollEl.scrollHeight <= scrollEl.clientHeight) return;
553
+ if (chat.hasMoreHistory && !chat.loadingHistory) loadOlder();
554
+ }, {
555
+ root: scrollEl,
556
+ threshold: .1
557
+ });
558
+ observer.observe(sentinel);
366
559
  return () => observer.disconnect();
367
- }, []);
560
+ }, [
561
+ autoLoadOlder,
562
+ chat.hasMoreHistory,
563
+ chat.loadingHistory,
564
+ loadOlder,
565
+ showTranscript
566
+ ]);
567
+ /**
568
+ * After a prepend, put the reader back where they were; after an append,
569
+ * follow the newest message — smoothly, once per message rather than per
570
+ * resize, which is why this is separate from the observer above and why it
571
+ * is NOT keyed on the stream text: growth during a run is the observer's,
572
+ * and a smooth scroll queued per delta fought it and lost.
573
+ *
574
+ * A layout effect, so the restore lands before the paint; an effect after
575
+ * it drew one frame at the old `scrollTop` with the new page on top.
576
+ */
577
+ useLayoutEffectSafe(() => {
578
+ const scrollEl = scrollRef.current;
579
+ if (!scrollEl) return;
580
+ const prevCount = prevMessageCountRef.current;
581
+ const count = chat.messages.length;
582
+ prevMessageCountRef.current = count;
583
+ const saved = prependHeightRef.current;
584
+ if (saved > 0 && !chat.loadingHistory) {
585
+ prependHeightRef.current = 0;
586
+ if (count > prevCount && prevCount > 0) {
587
+ const delta = scrollEl.scrollHeight - saved;
588
+ if (delta > 0) {
589
+ scrollEl.scrollTop += delta;
590
+ prevHeightRef.current = scrollEl.scrollHeight;
591
+ holdRef.current = true;
592
+ return;
593
+ }
594
+ }
595
+ }
596
+ if (count !== prevCount && nearBottomRef.current) {
597
+ if (typeof scrollEl.scrollTo === "function") scrollEl.scrollTo({
598
+ top: scrollEl.scrollHeight,
599
+ behavior: "smooth"
600
+ });
601
+ else scrollEl.scrollTop = scrollEl.scrollHeight;
602
+ }
603
+ }, [chat.messages, chat.loadingHistory]);
368
604
  const submit = React.useCallback(() => {
369
605
  const trimmed = value.trim();
370
606
  if (!trimmed || chat.streaming) return;
@@ -385,15 +621,19 @@ function AgentChat({ client, agentName, storageScope, ephemeral, thinking, attac
385
621
  }, [chat, submit]);
386
622
  const toolEntries = chat.toolOrder.map((id) => chat.tools[id]).filter((entry) => entry !== void 0);
387
623
  const showTools = toolEntries.length > 0 || chat.approvals.length > 0;
388
- const visible = chat.messages.filter((message) => !isHiddenMessage(message));
624
+ const Message = components?.Message;
625
+ const Streaming = components?.Streaming;
626
+ const LoadOlder = components?.LoadOlder ?? DefaultLoadOlder;
627
+ const Tools = components?.Tools;
628
+ const Composer = components?.Composer;
389
629
  return /* @__PURE__ */ jsxs("div", {
390
630
  "data-slot": "agent-chat",
391
631
  className: cn("flex h-full min-h-0 w-full flex-col bg-[var(--ds-background)]", className),
392
632
  ...props,
393
633
  children: [
394
- /* @__PURE__ */ jsxs("div", {
634
+ hideHeader ? null : /* @__PURE__ */ jsxs("div", {
395
635
  "data-slot": "agent-chat-header",
396
- className: cn("flex shrink-0 items-center gap-2 border-b border-[var(--ds-border-subtle)] px-3 py-2"),
636
+ className: cn("flex shrink-0 items-center gap-2 border-b border-[var(--ds-border-subtle)] px-3 py-2", classNames?.header),
397
637
  children: [
398
638
  /* @__PURE__ */ jsx(Button, {
399
639
  size: "sm",
@@ -423,7 +663,7 @@ function AgentChat({ client, agentName, storageScope, ephemeral, thinking, attac
423
663
  })
424
664
  ]
425
665
  }),
426
- drawerOpen ? /* @__PURE__ */ jsx(AgentSessionDrawer, {
666
+ drawerOpen && !hideHeader ? /* @__PURE__ */ jsx(AgentSessionDrawer, {
427
667
  sessions: chat.sessions,
428
668
  current: chat.sessionKey,
429
669
  loading: chat.loadingSessions,
@@ -432,30 +672,29 @@ function AgentChat({ client, agentName, storageScope, ephemeral, thinking, attac
432
672
  setDrawerOpen(false);
433
673
  }
434
674
  }) : null,
435
- /* @__PURE__ */ jsx("div", {
675
+ showTranscript ? /* @__PURE__ */ jsx("div", {
436
676
  ref: scrollRef,
437
677
  "data-slot": "agent-chat-scroll",
438
- className: "min-h-0 flex-1 overflow-x-hidden overflow-y-auto",
678
+ className: cn("min-h-0 flex-1 overflow-x-hidden overflow-y-auto [overflow-anchor:none]", classNames?.scroll),
439
679
  children: /* @__PURE__ */ jsxs("div", {
440
680
  ref: contentRef,
441
681
  role: "log",
442
682
  "aria-live": "polite",
443
683
  "aria-label": `${agentName} chat`,
444
- className: "flex min-w-0 flex-col gap-5 px-4 py-4",
684
+ className: cn("relative flex min-w-0 flex-col", classNames?.transcript ?? "gap-5 px-4 py-4"),
445
685
  children: [
686
+ /* @__PURE__ */ jsx("div", {
687
+ ref: sentinelRef,
688
+ "data-slot": "agent-chat-sentinel",
689
+ className: classNames?.sentinel ?? "absolute inset-x-0 top-0 h-px"
690
+ }),
446
691
  chat.error ? /* @__PURE__ */ jsx(ErrorBanner, { message: chat.error }) : null,
447
- chat.hasMoreHistory ? /* @__PURE__ */ jsx("div", {
448
- className: "flex justify-center",
449
- children: /* @__PURE__ */ jsx(Button, {
450
- size: "sm",
451
- variant: "ghost",
452
- "data-slot": "agent-chat-load-older",
453
- disabled: chat.loadingHistory,
454
- onClick: chat.loadOlder,
455
- children: chat.loadingHistory ? "Loading…" : "Load older messages"
456
- })
692
+ chat.hasMoreHistory || chat.loadingHistory ? /* @__PURE__ */ jsx(LoadOlder, {
693
+ hasMore: chat.hasMoreHistory,
694
+ loading: chat.loadingHistory,
695
+ onLoad: loadOlder
457
696
  }) : null,
458
- visible.length === 0 && !chat.streaming ? /* @__PURE__ */ jsxs("p", {
697
+ showEmpty ? /* @__PURE__ */ jsxs("p", {
459
698
  "data-slot": "agent-chat-empty",
460
699
  className: "text-[length:var(--ds-text-caption-lg)] text-[color:var(--ds-muted-foreground)]",
461
700
  children: [
@@ -464,24 +703,48 @@ function AgentChat({ client, agentName, storageScope, ephemeral, thinking, attac
464
703
  " anything about this screen."
465
704
  ]
466
705
  }) : null,
467
- visible.map((message) => /* @__PURE__ */ jsx(AgentMessageBubble, {
706
+ visible.map((message, index) => Message ? /* @__PURE__ */ jsx(Message, {
707
+ message,
708
+ index,
709
+ messages: visible,
710
+ agentName,
711
+ sessionKey: chat.sessionKey,
712
+ streaming: chat.streaming,
713
+ onAction: chat.sendAction
714
+ }, message.id) : /* @__PURE__ */ jsx(AgentMessageBubble, {
468
715
  message,
469
716
  agentName,
470
717
  streaming: chat.streaming,
471
718
  onAction: chat.sendAction
472
719
  }, message.id)),
473
- chat.streaming ? /* @__PURE__ */ jsx(AgentStreamingBubble, {
720
+ chat.streaming ? Streaming ? /* @__PURE__ */ jsx(Streaming, {
474
721
  agentName,
475
722
  text: chat.streamText,
476
723
  thinkingText: chat.streamThinkingText,
477
- a2ui: chat.streamA2ui
478
- }) : null
724
+ a2ui: chat.streamA2ui,
725
+ steps: chat.steps
726
+ }) : /* @__PURE__ */ jsx(AgentStreamingBubble, {
727
+ agentName,
728
+ text: chat.streamText,
729
+ thinkingText: chat.streamThinkingText,
730
+ a2ui: chat.streamA2ui,
731
+ steps: chat.steps
732
+ }) : chat.steps.length > 0 ? /* @__PURE__ */ jsx(AgentSteps, { steps: chat.steps }) : null
479
733
  ]
480
734
  })
481
- }),
482
- showTools ? /* @__PURE__ */ jsxs("div", {
735
+ }) : Empty ? /* @__PURE__ */ jsx(Empty, {
736
+ agentName,
737
+ loadingHistory: chat.loadingHistory
738
+ }) : null,
739
+ Tools ? /* @__PURE__ */ jsx(Tools, {
740
+ entries: toolEntries,
741
+ approvals: chat.approvals,
742
+ streaming: chat.streaming,
743
+ renderToolCall,
744
+ onResolveApproval: chat.resolveApproval
745
+ }) : showTools ? /* @__PURE__ */ jsxs("div", {
483
746
  "data-slot": "agent-chat-tools",
484
- className: "flex shrink-0 flex-col gap-1.5 border-t border-[var(--ds-border-subtle)] px-3 py-2",
747
+ className: cn("flex shrink-0 flex-col gap-1.5 border-t border-[var(--ds-border-subtle)] px-3 py-2", classNames?.tools),
485
748
  children: [
486
749
  toolEntries.length > 0 ? /* @__PURE__ */ jsx(Badge, {
487
750
  variant: "neutral",
@@ -506,9 +769,14 @@ function AgentChat({ client, agentName, storageScope, ephemeral, thinking, attac
506
769
  className: "shrink-0 px-3",
507
770
  children: attachmentsSlot
508
771
  }) : null,
509
- /* @__PURE__ */ jsxs("div", {
772
+ Composer ? /* @__PURE__ */ jsx(Composer, {
773
+ agentName,
774
+ streaming: chat.streaming,
775
+ send: chat.send,
776
+ abort: chat.abort
777
+ }) : /* @__PURE__ */ jsxs("div", {
510
778
  "data-slot": "agent-chat-composer",
511
- className: "flex shrink-0 items-end gap-2 border-t border-[var(--ds-border-subtle)] p-3",
779
+ className: cn("flex shrink-0 items-end gap-2 border-t border-[var(--ds-border-subtle)] p-3", classNames?.composer),
512
780
  children: [/* @__PURE__ */ jsx(Textarea, {
513
781
  "data-slot": "agent-chat-input",
514
782
  "aria-label": `Message ${agentName}`,
@@ -573,6 +841,6 @@ function AgentSessionDrawer({ sessions, current, loading, onResume }) {
573
841
  });
574
842
  }
575
843
  //#endregion
576
- export { AgentA2UIBlocks, AgentApprovalCard, AgentChat, AgentMessageBubble, AgentSessionDrawer, AgentStreamingBubble, AgentThinking, AgentToolCard, readMcpAppUri, shouldStickToBottom };
844
+ export { AgentA2UIBlocks, AgentApprovalCard, AgentChat, AgentMessageBubble, AgentSessionDrawer, AgentSteps, AgentStreamingBubble, AgentThinking, AgentToolCard, readMcpAppUri, shouldStickToBottom };
577
845
 
578
846
  //# sourceMappingURL=agent-chat.js.map