@agents24/chat-react 0.3.2 → 0.4.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 (58) hide show
  1. package/LICENSE +19 -0
  2. package/README.md +21 -66
  3. package/compatibility.json +54 -0
  4. package/dist/active-content.d.ts +10 -0
  5. package/dist/{chunk-EWZO4QJI.js → chunk-CHAU7FNW.js} +3 -3
  6. package/dist/chunk-CHAU7FNW.js.map +1 -0
  7. package/dist/{chunk-FFO6KEB4.js → chunk-LI67M74F.js} +98 -12
  8. package/dist/chunk-LI67M74F.js.map +1 -0
  9. package/dist/chunk-NLEOWTZG.js +1327 -0
  10. package/dist/chunk-NLEOWTZG.js.map +1 -0
  11. package/dist/index.cjs +66 -1852
  12. package/dist/index.cjs.map +1 -1
  13. package/dist/index.d.ts +0 -6
  14. package/dist/index.js +6 -1841
  15. package/dist/index.js.map +1 -1
  16. package/dist/latest-thread-scroller.d.ts +7 -7
  17. package/dist/mcp-oauth.d.ts +3 -1
  18. package/dist/renderers.d.ts +3 -3
  19. package/dist/styles.css +407 -0
  20. package/dist/templates/agent-chat-app.d.ts +18 -0
  21. package/dist/templates/agent-chat-layout.d.ts +69 -0
  22. package/dist/templates/agent-chat-shell.d.ts +1 -1
  23. package/dist/templates/index.cjs +1904 -18
  24. package/dist/templates/index.cjs.map +1 -1
  25. package/dist/templates/index.d.ts +2 -0
  26. package/dist/templates/index.js +529 -11
  27. package/dist/templates/index.js.map +1 -1
  28. package/dist/types.d.ts +12 -380
  29. package/dist/ui/agent-chat-actions.d.ts +1 -2
  30. package/dist/ui/agent-chat-composer.d.ts +2 -1
  31. package/dist/ui/agent-chat-message.d.ts +3 -3
  32. package/dist/ui/agent-response-timeline.d.ts +2 -2
  33. package/dist/ui/attachment.d.ts +9 -9
  34. package/dist/ui/bubble.d.ts +4 -4
  35. package/dist/ui/index.cjs +137 -43
  36. package/dist/ui/index.cjs.map +1 -1
  37. package/dist/ui/index.js +62 -1220
  38. package/dist/ui/index.js.map +1 -1
  39. package/dist/ui/marker.d.ts +3 -3
  40. package/dist/ui/mcp-connect-required-card.d.ts +1 -1
  41. package/dist/ui/message-response.d.ts +1 -1
  42. package/dist/ui/message.d.ts +6 -6
  43. package/package.json +27 -8
  44. package/dist/chunk-EWZO4QJI.js.map +0 -1
  45. package/dist/chunk-FFO6KEB4.js.map +0 -1
  46. package/dist/chunk-UU7OXHL3.js +0 -11
  47. package/dist/chunk-UU7OXHL3.js.map +0 -1
  48. package/dist/context-window.d.ts +0 -38
  49. package/dist/controller-actions.d.ts +0 -45
  50. package/dist/controller-helpers.d.ts +0 -27
  51. package/dist/controller-hitl.d.ts +0 -22
  52. package/dist/controller-options.d.ts +0 -15
  53. package/dist/controller-thread-events.d.ts +0 -7
  54. package/dist/controller.d.ts +0 -5
  55. package/dist/message-lifecycle.d.ts +0 -15
  56. package/dist/model.d.ts +0 -30
  57. package/dist/sse.d.ts +0 -5
  58. package/dist/transport.d.ts +0 -43
@@ -30,7 +30,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/templates/index.ts
31
31
  var templates_exports = {};
32
32
  __export(templates_exports, {
33
+ AgentChatApp: () => AgentChatApp,
34
+ AgentChatLayout: () => AgentChatLayout,
33
35
  AgentChatShell: () => AgentChatShell,
36
+ AgentChatStatePanel: () => AgentChatStatePanel,
34
37
  buildAgentChatOutlineItems: () => buildAgentChatOutlineItems,
35
38
  getAgentChatShellScrollState: () => getAgentChatShellScrollState
36
39
  });
@@ -94,7 +97,7 @@ function LatestThreadScrollerRoot({
94
97
  {
95
98
  ref: setRef,
96
99
  className: cx(
97
- "relative flex size-full min-h-0 flex-1 flex-col overflow-hidden",
100
+ "a24-scroller relative flex size-full min-h-0 flex-1 flex-col overflow-hidden",
98
101
  className
99
102
  ),
100
103
  ...props
@@ -124,7 +127,7 @@ function LatestThreadScrollerViewport({
124
127
  {
125
128
  ref: setRef,
126
129
  className: cx(
127
- "size-full min-h-0 min-w-0 flex-1 overflow-y-auto overscroll-contain",
130
+ "a24-scroller__viewport size-full min-h-0 min-w-0 flex-1 overflow-y-auto overscroll-contain",
128
131
  className
129
132
  ),
130
133
  preserveScrollOnPrepend,
@@ -154,7 +157,7 @@ function LatestThreadScrollerContent({
154
157
  {
155
158
  ref: setRef,
156
159
  "data-agents24-latest-thread-content": "",
157
- className: cx("flex h-max min-h-full flex-col gap-8 p-4", className),
160
+ className: cx("a24-scroller__content flex h-max min-h-full flex-col gap-8 p-4", className),
158
161
  ...props
159
162
  }
160
163
  );
@@ -168,7 +171,7 @@ function LatestThreadScrollerItem({
168
171
  import_message_scroller.MessageScroller.Item,
169
172
  {
170
173
  className: cx(
171
- "min-w-0 shrink-0 overflow-visible",
174
+ "a24-scroller__item min-w-0 shrink-0 overflow-visible",
172
175
  className
173
176
  ),
174
177
  messageId,
@@ -203,7 +206,7 @@ function LatestThreadScrollerButton({
203
206
  ...buttonProps,
204
207
  "aria-label": direction === "end" ? "Scroll to latest message" : "Scroll to first message",
205
208
  className: cx(
206
- "absolute bottom-4 left-1/2 z-50 flex size-9 -translate-x-1/2 items-center justify-center rounded-full border border-border bg-background text-foreground shadow-md transition-all duration-200 data-[active=false]:pointer-events-none data-[active=false]:translate-y-2 data-[active=false]:opacity-0 data-[active=true]:translate-y-0 data-[active=true]:opacity-100",
209
+ "a24-scroller__button absolute bottom-4 left-1/2 z-50 flex size-9 -translate-x-1/2 items-center justify-center rounded-full border border-border bg-background text-foreground shadow-md transition-[background-color,border-color,color,opacity,transform] duration-200 data-[active=false]:pointer-events-none data-[active=false]:translate-y-2 data-[active=false]:opacity-0 data-[active=true]:translate-y-0 data-[active=true]:opacity-100",
207
210
  className
208
211
  ),
209
212
  type: "button",
@@ -277,14 +280,16 @@ function LatestThreadScrollerOutline({
277
280
  {
278
281
  ref: navRef,
279
282
  "aria-label": "Transcript outline",
283
+ "data-side": railSide,
284
+ "data-visible": hasSideRoom ? "" : void 0,
280
285
  className: cx(
281
- "pointer-events-none absolute top-[46%] z-10 max-h-[min(18rem,calc(100%-10rem))] w-16 -translate-y-1/2 overflow-visible",
286
+ "a24-outline pointer-events-none absolute top-[46%] z-10 max-h-[min(18rem,calc(100%-10rem))] w-16 -translate-y-1/2 overflow-visible",
282
287
  hasSideRoom ? "hidden md:block" : "hidden",
283
288
  railSide === "left" ? "left-1" : "right-1",
284
289
  className
285
290
  ),
286
291
  dir,
287
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pointer-events-auto flex flex-col gap-0 py-1", children: items.map((item, index) => {
292
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "a24-outline__rail pointer-events-auto flex flex-col gap-0 py-1", children: items.map((item, index) => {
288
293
  const itemMessageIds = item.messageIds && item.messageIds.length > 0 ? item.messageIds : [item.id];
289
294
  const active = itemMessageIds.some(
290
295
  (messageId) => visibleMessageIdSet.has(messageId) || messageId === currentAnchorId
@@ -303,9 +308,10 @@ function LatestThreadScrollerOutline({
303
308
  {
304
309
  type: "button",
305
310
  "data-active": active,
311
+ "data-side": railSide,
306
312
  dir: "ltr",
307
313
  className: cx(
308
- "group relative flex h-2 w-16 items-center rounded-sm outline-none",
314
+ "a24-outline__button group relative flex h-2 w-16 items-center rounded-sm outline-none",
309
315
  railSide === "left" ? "justify-start" : "justify-end"
310
316
  ),
311
317
  "aria-label": `Jump to ${item.label || fallbackLabel}`,
@@ -321,7 +327,7 @@ function LatestThreadScrollerOutline({
321
327
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
322
328
  "span",
323
329
  {
324
- className: "h-0.5 rounded-full bg-muted-foreground/55 transition-[width,background-color,opacity] duration-150 ease-out group-hover:bg-foreground group-focus-visible:bg-foreground group-data-[active=true]:bg-foreground",
330
+ className: "a24-outline__bar h-0.5 rounded-full bg-muted-foreground/55 transition-[width,background-color,opacity] duration-150 ease-out group-hover:bg-foreground group-focus-visible:bg-foreground group-data-[active=true]:bg-foreground",
325
331
  style: {
326
332
  opacity: barOpacity,
327
333
  width: barWidth
@@ -333,7 +339,7 @@ function LatestThreadScrollerOutline({
333
339
  {
334
340
  dir,
335
341
  className: cx(
336
- "pointer-events-none absolute top-1/2 z-20 max-h-32 w-64 -translate-y-1/2 overflow-hidden rounded-lg border border-border/80 bg-background p-3 text-start opacity-0 shadow-lg transition-all duration-200 group-hover:pointer-events-auto group-hover:opacity-100 group-focus-visible:pointer-events-auto group-focus-visible:opacity-100",
342
+ "a24-outline__preview pointer-events-none absolute top-1/2 z-20 max-h-32 w-64 -translate-y-1/2 overflow-hidden rounded-lg border border-border/80 bg-background p-3 text-start opacity-0 shadow-lg transition-[opacity,transform] duration-200 group-hover:pointer-events-auto group-hover:opacity-100 group-focus-visible:pointer-events-auto group-focus-visible:opacity-100",
337
343
  railSide === "left" ? "left-full ml-2 -translate-x-1 group-hover:translate-x-0 group-focus-visible:translate-x-0" : "right-full mr-2 translate-x-1 group-hover:translate-x-0 group-focus-visible:translate-x-0",
338
344
  cardClassName
339
345
  ),
@@ -504,21 +510,21 @@ function AgentChatShell({
504
510
  const bottomPadding = hideComposer ? 32 : (composerOverlayHeight ?? measuredOverlayHeight) + 72;
505
511
  const isEmptyState = messages.length === 0;
506
512
  if (isEmptyState) {
507
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: cn("relative flex h-full min-h-0 w-full flex-col overflow-visible px-4", className), dir, children: renderEmptyState ? renderEmptyState({ composer: hideComposer ? void 0 : composer, title: emptyTitle }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "relative z-10 flex h-full flex-1 flex-col justify-center p-0 pt-13", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex w-full flex-col items-center pb-34 text-center animate-in fade-in slide-in-from-bottom-4 duration-500", children: [
513
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: cn("a24-shell a24-shell--empty relative flex h-full min-h-0 w-full flex-col overflow-visible px-4", className), dir, children: renderEmptyState ? renderEmptyState({ composer: hideComposer ? void 0 : composer, title: emptyTitle }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "a24-shell__empty relative z-10 flex h-full flex-1 flex-col justify-center p-0 pt-13", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "a24-shell__empty-content flex w-full flex-col items-center pb-34 text-center animate-in fade-in slide-in-from-bottom-4 duration-500", children: [
508
514
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
509
515
  "p",
510
516
  {
511
517
  className: cn(
512
- "pb-6 text-3xl font-semibold transition-transform duration-200",
518
+ "a24-shell__empty-title pb-6 text-3xl font-semibold transition-transform duration-200",
513
519
  emptyInputAttachmentCount > 0 && "-translate-y-12"
514
520
  ),
515
521
  children: emptyTitle
516
522
  }
517
523
  ),
518
- !hideComposer && composer ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "mx-auto w-full max-w-3xl px-4", children: composer }) : null
524
+ !hideComposer && composer ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "a24-shell__empty-composer mx-auto w-full max-w-3xl px-4", children: composer }) : null
519
525
  ] }) }) });
520
526
  }
521
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: cn("relative flex h-full min-h-0 w-full flex-col", className), dir, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(LatestThreadScroller.Provider, { children: [
527
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: cn("a24-shell relative flex h-full min-h-0 w-full flex-col", className), dir, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(LatestThreadScroller.Provider, { children: [
522
528
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(LatestThreadScroller.Root, { children: [
523
529
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
524
530
  LatestThreadScroller.Viewport,
@@ -533,7 +539,7 @@ function AgentChatShell({
533
539
  {
534
540
  ref: contentRef,
535
541
  className: cn(
536
- "mx-auto flex min-h-full w-full max-w-3xl flex-col gap-8 px-4 pt-12",
542
+ "a24-shell__content mx-auto flex min-h-full w-full max-w-3xl flex-col gap-8 px-4 pt-12",
537
543
  contentClassName
538
544
  ),
539
545
  style: { paddingBottom: bottomPadding },
@@ -567,23 +573,1903 @@ function AgentChatShell({
567
573
  {
568
574
  ref: overlayRef,
569
575
  className: cn(
570
- "pointer-events-none absolute inset-x-0 bottom-0 z-20 overflow-visible px-4 pb-4 pt-10",
576
+ "a24-shell__composer-overlay pointer-events-none absolute inset-x-0 bottom-0 z-20 overflow-visible px-4 pb-4 pt-10",
571
577
  composerOverlayClassName
572
578
  ),
573
579
  style: {
574
580
  background: "linear-gradient(to bottom, transparent 0%, color-mix(in oklch, var(--chat-background, var(--background)) 85%, transparent) 45%, var(--chat-background, var(--background)) 65%, var(--chat-background, var(--background)) 100%)"
575
581
  },
576
582
  children: [
577
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "pointer-events-auto absolute inset-x-0 top-0 flex -translate-y-full justify-center pb-3", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(LatestThreadScroller.Button, {}) }),
578
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "pointer-events-auto mx-auto w-full max-w-3xl px-4", children: composer })
583
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "a24-shell__scroll-button pointer-events-auto absolute inset-x-0 top-0 flex -translate-y-full justify-center pb-3", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(LatestThreadScroller.Button, {}) }),
584
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "a24-shell__composer-inner pointer-events-auto mx-auto w-full max-w-3xl px-4", children: composer })
579
585
  ]
580
586
  }
581
587
  ) : null
582
588
  ] }) });
583
589
  }
590
+
591
+ // src/templates/agent-chat-layout.tsx
592
+ var React8 = __toESM(require("react"), 1);
593
+ var import_lucide_react5 = require("lucide-react");
594
+
595
+ // src/ui/agent-chat-composer.tsx
596
+ var React3 = __toESM(require("react"), 1);
597
+ var import_lucide_react = require("lucide-react");
598
+ var import_jsx_runtime3 = require("react/jsx-runtime");
599
+ var createId = () => typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `file-${Date.now()}-${Math.random().toString(36).slice(2)}`;
600
+ function createComposerFile(file) {
601
+ return {
602
+ id: createId(),
603
+ type: "file",
604
+ url: URL.createObjectURL(file),
605
+ filename: file.name || "attachment",
606
+ mediaType: file.type || "application/octet-stream",
607
+ source: file
608
+ };
609
+ }
610
+ function revokeComposerFiles(files) {
611
+ files.forEach((file) => {
612
+ try {
613
+ URL.revokeObjectURL(file.url);
614
+ } catch {
615
+ }
616
+ });
617
+ }
618
+ function formatFileCount(files) {
619
+ if (files.length === 1) return files[0].filename;
620
+ return `${files.length} files`;
621
+ }
622
+ var pillButtonClass = "inline-flex size-8 shrink-0 items-center justify-center gap-2 rounded-full border-0 bg-neutral-100 text-sm text-neutral-600 shadow-none transition-colors hover:bg-neutral-200 hover:text-neutral-800 focus-visible:border-neutral-300 focus-visible:outline-none focus-visible:ring-0 disabled:pointer-events-none disabled:opacity-50 dark:bg-neutral-800 dark:text-neutral-300 dark:hover:bg-neutral-700";
623
+ var submitButtonClass = "inline-flex size-9 shrink-0 items-center justify-center gap-2 rounded-full border-0 bg-neutral-900 text-sm text-white shadow-none transition-[background-color,color,opacity,transform] duration-200 hover:bg-neutral-800 hover:text-white focus-visible:border-neutral-300 focus-visible:outline-none focus-visible:ring-0 disabled:pointer-events-none disabled:opacity-50 dark:bg-neutral-100 dark:text-neutral-900 dark:hover:bg-white";
624
+ function AgentChatComposer({
625
+ accept,
626
+ allowAttachments = true,
627
+ attachmentLayout = "flow",
628
+ className,
629
+ disabled = false,
630
+ inputToolbarContent,
631
+ isRunning = false,
632
+ onAttachmentCountChange,
633
+ onStop,
634
+ onSubmit,
635
+ placeholder = "Send follow-up\u2026",
636
+ textareaRef
637
+ }) {
638
+ const fileInputRef = React3.useRef(null);
639
+ const filesRef = React3.useRef([]);
640
+ const localTextareaRef = React3.useRef(null);
641
+ const [text, setText] = React3.useState("");
642
+ const [files, setFiles] = React3.useState([]);
643
+ const [isSubmitting, setIsSubmitting] = React3.useState(false);
644
+ const isExpanded = text.includes("\n") || text.length > 62;
645
+ const setTextareaRef = React3.useCallback(
646
+ (node) => {
647
+ localTextareaRef.current = node;
648
+ if (typeof textareaRef === "function") {
649
+ textareaRef(node);
650
+ } else if (textareaRef) {
651
+ textareaRef.current = node;
652
+ }
653
+ },
654
+ [textareaRef]
655
+ );
656
+ React3.useEffect(() => {
657
+ filesRef.current = files;
658
+ onAttachmentCountChange?.(files.length);
659
+ }, [files, onAttachmentCountChange]);
660
+ React3.useEffect(() => () => revokeComposerFiles(filesRef.current), []);
661
+ React3.useLayoutEffect(() => {
662
+ const textarea = localTextareaRef.current;
663
+ if (!textarea) return;
664
+ if (isExpanded) {
665
+ textarea.style.height = "auto";
666
+ const scrollHeight = textarea.scrollHeight;
667
+ textarea.style.height = `${Math.min(scrollHeight, 224)}px`;
668
+ textarea.style.overflowY = scrollHeight >= 224 ? "auto" : "hidden";
669
+ return;
670
+ }
671
+ textarea.style.height = "";
672
+ textarea.style.overflowY = "hidden";
673
+ }, [isExpanded, text]);
674
+ const canSubmit = !disabled && !isSubmitting && !isRunning && (text.trim().length > 0 || files.length > 0);
675
+ const handleFilesChange = (event) => {
676
+ const selected = Array.from(event.target.files || []).map(createComposerFile);
677
+ if (selected.length > 0) {
678
+ setFiles((current) => [...current, ...selected]);
679
+ }
680
+ event.currentTarget.value = "";
681
+ };
682
+ const removeFile = (fileId) => {
683
+ setFiles((current) => {
684
+ const target = current.find((file) => file.id === fileId);
685
+ if (target) revokeComposerFiles([target]);
686
+ return current.filter((file) => file.id !== fileId);
687
+ });
688
+ };
689
+ const submit = async () => {
690
+ if (!canSubmit) return;
691
+ const submittedText = text.trim();
692
+ const submittedFiles = files;
693
+ setIsSubmitting(true);
694
+ try {
695
+ await onSubmit({ text: submittedText, files: submittedFiles });
696
+ setText("");
697
+ setFiles([]);
698
+ revokeComposerFiles(submittedFiles);
699
+ } finally {
700
+ setIsSubmitting(false);
701
+ }
702
+ };
703
+ const handleKeyDown = (event) => {
704
+ if (event.key === "Enter" && !event.shiftKey) {
705
+ event.preventDefault();
706
+ void submit();
707
+ }
708
+ };
709
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: cn("a24-composer relative mx-auto w-full max-w-3xl overflow-visible", className), children: [
710
+ files.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
711
+ "div",
712
+ {
713
+ "data-testid": "agent-chat-composer-attachments",
714
+ "data-layout": attachmentLayout,
715
+ className: cn(
716
+ "a24-composer__attachments flex min-w-0 gap-2 py-1",
717
+ attachmentLayout === "overlay" ? "absolute bottom-full left-0 right-0 mb-1.5 flex-nowrap overflow-x-auto overflow-y-hidden overscroll-x-contain px-2" : "mb-2 flex-wrap px-1"
718
+ ),
719
+ children: files.map((file) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
720
+ "div",
721
+ {
722
+ className: "a24-composer__attachment group relative flex h-8 max-w-[min(20rem,calc(100vw-2rem))] cursor-default select-none items-center gap-1.5 rounded-md border border-neutral-200 bg-white px-1.5 text-sm font-medium text-foreground transition-colors hover:bg-neutral-50",
723
+ children: [
724
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "flex size-5 shrink-0 items-center justify-center rounded bg-background text-muted-foreground", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react.Paperclip, { className: "size-3" }) }),
725
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "min-w-0 flex-1 truncate", children: file.filename }),
726
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
727
+ "button",
728
+ {
729
+ "aria-label": `Remove ${file.filename}`,
730
+ className: "a24-composer__attachment-remove inline-flex size-5 shrink-0 items-center justify-center rounded text-muted-foreground opacity-70 transition-colors hover:bg-muted hover:text-foreground hover:opacity-100 focus-visible:outline-none",
731
+ onClick: () => removeFile(file.id),
732
+ type: "button",
733
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react.X, { className: "size-3" })
734
+ }
735
+ )
736
+ ]
737
+ },
738
+ file.id
739
+ ))
740
+ }
741
+ ) : null,
742
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
743
+ "div",
744
+ {
745
+ "data-expanded": isExpanded ? "" : void 0,
746
+ className: cn(
747
+ "a24-composer__surface relative w-full border border-neutral-200 bg-white shadow-sm transition-colors duration-200 focus-within:border-neutral-300 dark:border-neutral-700 dark:bg-card dark:focus-within:border-neutral-600",
748
+ isExpanded ? "rounded-[18px] p-2 pb-1.5" : "rounded-full px-2 py-[3px]"
749
+ ),
750
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
751
+ "div",
752
+ {
753
+ className: "a24-composer__grid w-full",
754
+ style: {
755
+ alignItems: "center",
756
+ display: "grid",
757
+ gridTemplateAreas: isExpanded ? `"textarea textarea textarea" "plus toolbar submit"` : `"plus textarea composer mic submit"`,
758
+ gridTemplateColumns: isExpanded ? "auto 1fr auto" : "auto 1fr auto auto auto",
759
+ gap: isExpanded ? "3px 10px" : "8px"
760
+ },
761
+ children: [
762
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { gridArea: "plus" }, className: "a24-composer__attach-slot flex shrink-0 items-center justify-center", children: [
763
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
764
+ "input",
765
+ {
766
+ ref: fileInputRef,
767
+ accept,
768
+ "aria-label": "Attach files",
769
+ className: "hidden",
770
+ multiple: true,
771
+ name: "attachments",
772
+ onChange: handleFilesChange,
773
+ type: "file"
774
+ }
775
+ ),
776
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
777
+ "button",
778
+ {
779
+ "aria-label": "Attach files",
780
+ className: cn("a24-composer__attach", pillButtonClass),
781
+ disabled: disabled || isSubmitting || isRunning || !allowAttachments,
782
+ onClick: () => fileInputRef.current?.click(),
783
+ title: "Attach files",
784
+ type: "button",
785
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react.Plus, { className: "size-4" })
786
+ }
787
+ )
788
+ ] }),
789
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { gridArea: "textarea" }, className: "a24-composer__textarea-slot min-w-0 w-full", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
790
+ "textarea",
791
+ {
792
+ ref: setTextareaRef,
793
+ "aria-label": "Message",
794
+ autoComplete: "off",
795
+ className: cn(
796
+ "a24-composer__textarea w-full resize-none border-0 bg-transparent px-1 text-[15px] leading-relaxed text-foreground shadow-none outline-none placeholder:text-neutral-400 focus-visible:ring-0 scrollbar-thin",
797
+ isExpanded ? "min-h-9 max-h-[224px] py-1" : "min-h-9 h-9 max-h-9 py-2"
798
+ ),
799
+ disabled: disabled || isSubmitting || isRunning,
800
+ name: "message",
801
+ onChange: (event) => setText(event.target.value),
802
+ onKeyDown: handleKeyDown,
803
+ placeholder,
804
+ rows: isExpanded ? 2 : 1,
805
+ value: text
806
+ }
807
+ ) }),
808
+ isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { gridArea: "toolbar" }, className: "a24-composer__toolbar flex min-w-0 items-center gap-1.5", children: inputToolbarContent }) : null,
809
+ !isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { gridArea: "composer" }, className: "flex min-w-0 items-center gap-1.5" }) : null,
810
+ !isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { gridArea: "mic" }, className: "flex shrink-0 items-center justify-end" }) : null,
811
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { gridArea: "submit" }, className: "a24-composer__submit-slot flex shrink-0 items-center justify-end", children: isRunning ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
812
+ "button",
813
+ {
814
+ "aria-label": "Stop generating",
815
+ className: cn("a24-composer__submit", submitButtonClass),
816
+ disabled: disabled || !onStop,
817
+ onClick: onStop,
818
+ title: "Stop",
819
+ type: "button",
820
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react.Square, { className: "size-3.5 fill-current" })
821
+ }
822
+ ) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
823
+ "button",
824
+ {
825
+ "aria-label": files.length > 0 ? `Send ${formatFileCount(files)}` : "Send message",
826
+ className: cn("a24-composer__submit", submitButtonClass, text.trim().length > 0 && "hover:scale-105"),
827
+ disabled: !canSubmit,
828
+ onClick: () => void submit(),
829
+ title: "Send",
830
+ type: "button",
831
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react.ArrowUp, { className: "size-4", strokeWidth: 2.5 })
832
+ }
833
+ ) })
834
+ ]
835
+ }
836
+ )
837
+ }
838
+ ),
839
+ !isExpanded && inputToolbarContent ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "a24-composer__toolbar a24-composer__toolbar--external mt-2 flex items-center gap-2 px-1", children: inputToolbarContent }) : null
840
+ ] });
841
+ }
842
+
843
+ // src/ui/agent-chat-message.tsx
844
+ var React7 = __toESM(require("react"), 1);
845
+ var import_lucide_react4 = require("lucide-react");
846
+
847
+ // src/ui/adapters.ts
848
+ var React4 = __toESM(require("react"), 1);
849
+
850
+ // src/ui/attachment.tsx
851
+ var import_class_variance_authority = require("class-variance-authority");
852
+ var import_react_slot = require("@radix-ui/react-slot");
853
+ var import_jsx_runtime4 = require("react/jsx-runtime");
854
+ var attachmentVariants = (0, import_class_variance_authority.cva)(
855
+ "group/attachment relative flex w-fit max-w-full min-w-0 shrink-0 flex-wrap rounded-xl border bg-card text-card-foreground transition-colors focus-within:border-neutral-300 focus-within:ring-0 has-[>a,>button]:hover:bg-muted/50 data-[state=error]:border-destructive/30 data-[state=idle]:border-dashed",
856
+ {
857
+ variants: {
858
+ size: {
859
+ default: "gap-2 text-sm has-data-[slot=attachment-content]:px-2.5 has-data-[slot=attachment-content]:py-2 has-data-[slot=attachment-media]:p-2",
860
+ sm: "gap-2.5 text-xs has-data-[slot=attachment-content]:px-2 has-data-[slot=attachment-content]:py-1.5 has-data-[slot=attachment-media]:p-1.5",
861
+ xs: "gap-1.5 rounded-lg text-xs has-data-[slot=attachment-content]:px-1.5 has-data-[slot=attachment-content]:py-1 has-data-[slot=attachment-media]:p-1"
862
+ },
863
+ orientation: {
864
+ horizontal: "min-w-40 items-center",
865
+ vertical: "w-24 flex-col has-data-[slot=attachment-content]:w-30"
866
+ }
867
+ }
868
+ }
869
+ );
870
+ function Attachment({
871
+ className,
872
+ state = "done",
873
+ size = "default",
874
+ orientation = "horizontal",
875
+ ...props
876
+ }) {
877
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
878
+ "div",
879
+ {
880
+ "data-slot": "attachment",
881
+ "data-state": state,
882
+ "data-size": size,
883
+ "data-orientation": orientation,
884
+ className: cn(attachmentVariants({ size, orientation }), className),
885
+ ...props
886
+ }
887
+ );
888
+ }
889
+ var attachmentMediaVariants = (0, import_class_variance_authority.cva)(
890
+ "relative flex aspect-square w-10 shrink-0 items-center justify-center overflow-hidden rounded-lg bg-muted text-foreground group-data-[orientation=vertical]/attachment:w-full group-data-[size=sm]/attachment:w-8 group-data-[size=xs]/attachment:w-7 group-data-[size=xs]/attachment:rounded-md group-data-[state=error]/attachment:bg-destructive/10 group-data-[state=error]/attachment:text-destructive group-data-[orientation=vertical]/attachment:*:data-[slot=spinner]:size-6! [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 group-data-[orientation=vertical]/attachment:[&_svg:not([class*='size-'])]:size-6 group-data-[size=xs]/attachment:[&_svg:not([class*='size-'])]:size-3.5",
891
+ {
892
+ variants: {
893
+ variant: {
894
+ icon: "",
895
+ image: "opacity-60 group-data-[state=done]/attachment:opacity-100 group-data-[state=idle]/attachment:opacity-100 *:[img]:aspect-square *:[img]:w-full *:[img]:object-cover"
896
+ }
897
+ },
898
+ defaultVariants: {
899
+ variant: "icon"
900
+ }
901
+ }
902
+ );
903
+ function AttachmentMedia({
904
+ className,
905
+ variant = "icon",
906
+ ...props
907
+ }) {
908
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
909
+ "div",
910
+ {
911
+ "data-slot": "attachment-media",
912
+ "data-variant": variant,
913
+ className: cn(attachmentMediaVariants({ variant }), className),
914
+ ...props
915
+ }
916
+ );
917
+ }
918
+ function AttachmentContent({
919
+ className,
920
+ ...props
921
+ }) {
922
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
923
+ "div",
924
+ {
925
+ "data-slot": "attachment-content",
926
+ className: cn(
927
+ "max-w-full min-w-0 flex-1 leading-tight group-data-[orientation=vertical]/attachment:px-1",
928
+ className
929
+ ),
930
+ ...props
931
+ }
932
+ );
933
+ }
934
+ function AttachmentTitle({
935
+ className,
936
+ ...props
937
+ }) {
938
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
939
+ "span",
940
+ {
941
+ "data-slot": "attachment-title",
942
+ className: cn(
943
+ "block max-w-full min-w-0 truncate font-medium group-data-[state=processing]/attachment:shimmer group-data-[state=uploading]/attachment:shimmer",
944
+ className
945
+ ),
946
+ ...props
947
+ }
948
+ );
949
+ }
950
+ function AttachmentDescription({
951
+ className,
952
+ ...props
953
+ }) {
954
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
955
+ "span",
956
+ {
957
+ "data-slot": "attachment-description",
958
+ className: cn(
959
+ "mt-0.5 block min-w-0 truncate text-xs text-muted-foreground group-data-[state=error]/attachment:text-destructive/80",
960
+ "max-w-full",
961
+ className
962
+ ),
963
+ ...props
964
+ }
965
+ );
966
+ }
967
+ function AttachmentGroup({ className, ...props }) {
968
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
969
+ "div",
970
+ {
971
+ "data-slot": "attachment-group",
972
+ className: cn(
973
+ "flex min-w-0 scroll-fade-x snap-x snap-mandatory scroll-px-1 scrollbar-none gap-3 overflow-x-auto overscroll-x-contain py-1 *:data-[slot=attachment]:flex-none *:data-[slot=attachment]:snap-start",
974
+ className
975
+ ),
976
+ ...props
977
+ }
978
+ );
979
+ }
980
+
981
+ // src/ui/message.tsx
982
+ var import_jsx_runtime5 = require("react/jsx-runtime");
983
+ function Message({
984
+ className,
985
+ align = "start",
986
+ ...props
987
+ }) {
988
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
989
+ "div",
990
+ {
991
+ "data-slot": "message",
992
+ "data-align": align,
993
+ className: cn(
994
+ "group/message relative flex w-full min-w-0 gap-2 text-sm data-[align=end]:flex-row-reverse",
995
+ className
996
+ ),
997
+ ...props
998
+ }
999
+ );
1000
+ }
1001
+
1002
+ // src/ui/adapters.ts
1003
+ function ChatMessage(input) {
1004
+ const { className, from, align, ...props } = input;
1005
+ const isUser = from === "user";
1006
+ const isRtl = props.dir === "rtl";
1007
+ const resolvedAlign = align || "start";
1008
+ const messageProps = Object.assign({}, props);
1009
+ messageProps.align = resolvedAlign;
1010
+ messageProps.className = cn(
1011
+ "group flex w-full flex-col gap-2",
1012
+ isUser ? cn("is-user max-w-[80%] justify-end", isRtl ? "mr-auto" : "ml-auto") : "is-assistant max-w-full",
1013
+ className
1014
+ );
1015
+ return React4.createElement(Message, messageProps);
1016
+ }
1017
+ function ChatMessageContent({
1018
+ className,
1019
+ ...props
1020
+ }) {
1021
+ return React4.createElement("div", {
1022
+ className: cn(
1023
+ "is-user:dark flex w-full max-w-full min-w-0 flex-col gap-2 overflow-hidden text-sm group-[.is-user]:w-fit group-[.is-user]:overflow-visible",
1024
+ "group-[.is-user]:rounded-lg group-[.is-user]:bg-secondary group-[.is-user]:px-4 group-[.is-user]:py-3 group-[.is-user]:text-foreground",
1025
+ "group-[.is-assistant]:w-full group-[.is-assistant]:max-w-full group-[.is-assistant]:text-foreground",
1026
+ props.dir === "rtl" ? "group-[.is-user]:mr-auto" : "group-[.is-user]:ml-auto",
1027
+ className
1028
+ ),
1029
+ "data-slot": "bubble-content",
1030
+ ...props
1031
+ });
1032
+ }
1033
+ function ChatMessageActions({
1034
+ className,
1035
+ ...props
1036
+ }) {
1037
+ return React4.createElement("div", {
1038
+ className: cn("flex items-center gap-1", className),
1039
+ "data-slot": "message-actions",
1040
+ ...props
1041
+ });
1042
+ }
1043
+ function ChatMessageAction({
1044
+ children,
1045
+ className,
1046
+ label,
1047
+ tooltip,
1048
+ type = "button",
1049
+ ...props
1050
+ }) {
1051
+ const tooltipLabel = typeof tooltip === "string" ? tooltip : void 0;
1052
+ return React4.createElement(
1053
+ "button",
1054
+ {
1055
+ "aria-label": label || tooltipLabel,
1056
+ className: cn(
1057
+ "inline-flex size-8 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:border-neutral-300 focus-visible:outline-none focus-visible:ring-0 disabled:pointer-events-none disabled:opacity-50",
1058
+ className
1059
+ ),
1060
+ title: tooltipLabel,
1061
+ type,
1062
+ ...props
1063
+ },
1064
+ children,
1065
+ React4.createElement(
1066
+ "span",
1067
+ { className: "sr-only" },
1068
+ label || tooltipLabel
1069
+ )
1070
+ );
1071
+ }
1072
+ function ChatAttachment({
1073
+ data,
1074
+ onRemove,
1075
+ orientation,
1076
+ ...props
1077
+ }) {
1078
+ const mediaType = data.mediaType || data.contentType || String(data.type || "application/octet-stream");
1079
+ const filename = data.filename || data.name || "Attachment";
1080
+ const isImage = mediaType.startsWith("image/") && Boolean(data.url);
1081
+ return React4.createElement(
1082
+ Attachment,
1083
+ {
1084
+ orientation: orientation || (isImage ? "vertical" : "horizontal"),
1085
+ ...props
1086
+ },
1087
+ React4.createElement(
1088
+ AttachmentMedia,
1089
+ { variant: isImage ? "image" : "icon" },
1090
+ isImage ? React4.createElement("img", {
1091
+ alt: filename,
1092
+ className: "size-full object-cover",
1093
+ src: data.url
1094
+ }) : React4.createElement(
1095
+ "span",
1096
+ { "aria-hidden": "true", className: "text-sm" },
1097
+ filename.slice(0, 1).toUpperCase()
1098
+ )
1099
+ ),
1100
+ React4.createElement(
1101
+ AttachmentContent,
1102
+ null,
1103
+ React4.createElement(AttachmentTitle, null, filename),
1104
+ React4.createElement(AttachmentDescription, null, mediaType)
1105
+ ),
1106
+ onRemove ? React4.createElement(
1107
+ "button",
1108
+ {
1109
+ "aria-label": "Remove attachment",
1110
+ className: "relative z-20 mr-1 inline-flex size-6 shrink-0 items-center justify-center rounded-md text-muted-foreground hover:bg-muted hover:text-foreground",
1111
+ onClick: (event) => {
1112
+ event.stopPropagation();
1113
+ onRemove();
1114
+ },
1115
+ type: "button"
1116
+ },
1117
+ React4.createElement("span", { "aria-hidden": "true" }, "x")
1118
+ ) : null
1119
+ );
1120
+ }
1121
+ function ChatAttachments({
1122
+ children,
1123
+ className,
1124
+ ...props
1125
+ }) {
1126
+ if (!children) return null;
1127
+ return React4.createElement(
1128
+ AttachmentGroup,
1129
+ {
1130
+ className: cn(
1131
+ props.dir === "rtl" ? "mr-auto" : "ml-auto",
1132
+ "w-fit flex-wrap overflow-visible",
1133
+ className
1134
+ ),
1135
+ ...props
1136
+ },
1137
+ children
1138
+ );
1139
+ }
1140
+
1141
+ // src/ui/agent-chat-actions.tsx
1142
+ var import_lucide_react2 = require("lucide-react");
1143
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1144
+ function AgentChatDefaultActions({
1145
+ handlers,
1146
+ message
1147
+ }) {
1148
+ if (!handlers || message.role !== "assistant" || message.isFinal === false) return null;
1149
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(ChatMessageActions, { children: [
1150
+ handlers.onRetry ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1151
+ ChatMessageAction,
1152
+ {
1153
+ label: "Retry",
1154
+ onClick: () => void handlers.onRetry?.(message),
1155
+ tooltip: "Regenerate response",
1156
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react2.RefreshCcw, { className: "size-4" })
1157
+ }
1158
+ ) : null,
1159
+ handlers.onLike ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1160
+ ChatMessageAction,
1161
+ {
1162
+ label: "Like",
1163
+ onClick: () => void handlers.onLike?.(message),
1164
+ tooltip: "Like this response",
1165
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react2.ThumbsUp, { className: "size-4", fill: handlers.liked ? "currentColor" : "none" })
1166
+ }
1167
+ ) : null,
1168
+ handlers.onDislike ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1169
+ ChatMessageAction,
1170
+ {
1171
+ label: "Dislike",
1172
+ onClick: () => void handlers.onDislike?.(message),
1173
+ tooltip: "Dislike this response",
1174
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react2.ThumbsDown, { className: "size-4", fill: handlers.disliked ? "currentColor" : "none" })
1175
+ }
1176
+ ) : null,
1177
+ handlers.onCopy ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1178
+ ChatMessageAction,
1179
+ {
1180
+ label: "Copy",
1181
+ onClick: () => handlers.onCopy?.(message.content || "", message.id),
1182
+ tooltip: handlers.copied ? "Copied!" : "Copy to clipboard",
1183
+ children: handlers.copied ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react2.Check, { className: "size-4 text-green-600" }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react2.Copy, { className: "size-4" })
1184
+ }
1185
+ ) : null
1186
+ ] });
1187
+ }
1188
+
1189
+ // src/ui/agent-response-timeline.tsx
1190
+ var React6 = __toESM(require("react"), 1);
1191
+ var import_lucide_react3 = require("lucide-react");
1192
+
1193
+ // src/ui/message-response.tsx
1194
+ var React5 = __toESM(require("react"), 1);
1195
+ var import_streamdown = require("streamdown");
1196
+
1197
+ // src/active-content.ts
1198
+ var CHAT_MARKDOWN_HARDEN_OPTIONS = Object.freeze({
1199
+ allowDataImages: false,
1200
+ allowedImagePrefixes: [],
1201
+ allowedLinkPrefixes: ["*"],
1202
+ allowedProtocols: [],
1203
+ defaultOrigin: void 0,
1204
+ imageBlockPolicy: "text-only",
1205
+ linkBlockPolicy: "text-only"
1206
+ });
1207
+ function safeChatMarkdownHref(value) {
1208
+ if (!value) return void 0;
1209
+ if (value.startsWith("#")) return value;
1210
+ try {
1211
+ const url = new URL(value);
1212
+ return url.protocol === "https:" || url.protocol === "http:" ? url.href : void 0;
1213
+ } catch {
1214
+ return void 0;
1215
+ }
1216
+ }
1217
+
1218
+ // src/streaming-text.ts
1219
+ var import_react = require("react");
1220
+ var DEFAULT_COMPLETED_TEXT_CACHE_SIZE = 200;
1221
+ var defaultCompletedTextCache = /* @__PURE__ */ new Map();
1222
+ var defaultStreamingTextCache = {
1223
+ get: (id) => defaultCompletedTextCache.get(id),
1224
+ set: (id, text) => {
1225
+ defaultCompletedTextCache.set(id, text);
1226
+ if (defaultCompletedTextCache.size > DEFAULT_COMPLETED_TEXT_CACHE_SIZE) {
1227
+ const firstKey = defaultCompletedTextCache.keys().next().value;
1228
+ if (firstKey) defaultCompletedTextCache.delete(firstKey);
1229
+ }
1230
+ }
1231
+ };
1232
+ function useStreamingText({
1233
+ id,
1234
+ isStreaming,
1235
+ text,
1236
+ cache = defaultStreamingTextCache,
1237
+ charsPerSecond = 72,
1238
+ catchupThreshold = 40,
1239
+ maxCatchupChars = 20
1240
+ }) {
1241
+ const cacheAdapter = cache === false ? null : cache;
1242
+ const [displayedText, setDisplayedText] = (0, import_react.useState)(() => {
1243
+ const cachedText = cacheAdapter?.get(id);
1244
+ return cachedText && text.startsWith(cachedText) ? cachedText : text;
1245
+ });
1246
+ const targetRef = (0, import_react.useRef)(text);
1247
+ const displayedRef = (0, import_react.useRef)(displayedText);
1248
+ const rafRef = (0, import_react.useRef)(null);
1249
+ const lastFrameAtRef = (0, import_react.useRef)(null);
1250
+ const idRef = (0, import_react.useRef)(id);
1251
+ const shouldAnimateRef = (0, import_react.useRef)(isStreaming);
1252
+ (0, import_react.useEffect)(() => {
1253
+ if (isStreaming || !text) return;
1254
+ cacheAdapter?.set(id, text);
1255
+ }, [cacheAdapter, id, isStreaming, text]);
1256
+ (0, import_react.useEffect)(() => {
1257
+ targetRef.current = text;
1258
+ }, [text]);
1259
+ (0, import_react.useEffect)(() => {
1260
+ if (idRef.current === id) return;
1261
+ idRef.current = id;
1262
+ const cachedText = cacheAdapter?.get(id);
1263
+ const initial = cachedText && text.startsWith(cachedText) ? cachedText : text;
1264
+ shouldAnimateRef.current = isStreaming && initial !== text;
1265
+ displayedRef.current = initial;
1266
+ setDisplayedText(initial);
1267
+ if (rafRef.current !== null) {
1268
+ cancelAnimationFrame(rafRef.current);
1269
+ rafRef.current = null;
1270
+ }
1271
+ lastFrameAtRef.current = null;
1272
+ }, [cacheAdapter, id, isStreaming, text]);
1273
+ (0, import_react.useEffect)(() => {
1274
+ if (typeof window === "undefined") {
1275
+ displayedRef.current = text;
1276
+ setDisplayedText(text);
1277
+ return;
1278
+ }
1279
+ const reduceMotion = typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
1280
+ if (reduceMotion) {
1281
+ displayedRef.current = text;
1282
+ setDisplayedText(text);
1283
+ shouldAnimateRef.current = false;
1284
+ return;
1285
+ }
1286
+ const cachedText = cacheAdapter?.get(id);
1287
+ if (cachedText === text) {
1288
+ displayedRef.current = text;
1289
+ setDisplayedText(text);
1290
+ shouldAnimateRef.current = false;
1291
+ return;
1292
+ }
1293
+ if (isStreaming) {
1294
+ shouldAnimateRef.current = true;
1295
+ }
1296
+ if (!shouldAnimateRef.current) {
1297
+ displayedRef.current = text;
1298
+ setDisplayedText(text);
1299
+ return;
1300
+ }
1301
+ if (!text.startsWith(displayedRef.current)) {
1302
+ displayedRef.current = "";
1303
+ setDisplayedText("");
1304
+ }
1305
+ const tick = (timestamp) => {
1306
+ const previousTimestamp = lastFrameAtRef.current ?? timestamp;
1307
+ lastFrameAtRef.current = timestamp;
1308
+ const target = targetRef.current;
1309
+ const current = displayedRef.current;
1310
+ if (current.length >= target.length) {
1311
+ shouldAnimateRef.current = false;
1312
+ rafRef.current = null;
1313
+ return;
1314
+ }
1315
+ const elapsedMs = Math.max(8, timestamp - previousTimestamp);
1316
+ const charsFromTime = Math.max(1, Math.floor(elapsedMs / 1e3 * charsPerSecond));
1317
+ const gap = target.length - current.length;
1318
+ const catchupStep = gap > catchupThreshold ? Math.min(maxCatchupChars, Math.ceil(gap / 10)) : charsFromTime;
1319
+ const nextLength = Math.min(
1320
+ target.length,
1321
+ current.length + Math.max(charsFromTime, catchupStep)
1322
+ );
1323
+ const next = target.slice(0, nextLength);
1324
+ displayedRef.current = next;
1325
+ setDisplayedText(next);
1326
+ rafRef.current = window.requestAnimationFrame(tick);
1327
+ };
1328
+ if (rafRef.current === null && displayedRef.current.length < text.length) {
1329
+ rafRef.current = window.requestAnimationFrame(tick);
1330
+ }
1331
+ return () => {
1332
+ if (rafRef.current !== null) {
1333
+ cancelAnimationFrame(rafRef.current);
1334
+ rafRef.current = null;
1335
+ }
1336
+ lastFrameAtRef.current = null;
1337
+ };
1338
+ }, [cacheAdapter, catchupThreshold, charsPerSecond, id, isStreaming, maxCatchupChars, text]);
1339
+ return {
1340
+ displayedText,
1341
+ isAnimating: isStreaming,
1342
+ mode: isStreaming ? "streaming" : "static",
1343
+ parseIncompleteMarkdown: isStreaming
1344
+ };
1345
+ }
1346
+
1347
+ // src/ui/message-response.tsx
1348
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1349
+ function SafeMarkdownLink({ children, href, ...props }) {
1350
+ const domProps = { ...props };
1351
+ delete domProps.node;
1352
+ const safeHref = safeChatMarkdownHref(href);
1353
+ if (!safeHref) return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children });
1354
+ if (safeHref.startsWith("#")) return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("a", { ...domProps, href: safeHref, children });
1355
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("a", { ...domProps, href: safeHref, rel: "noopener noreferrer", target: "_blank", children });
1356
+ }
1357
+ function BlockedMarkdownImage({ alt }) {
1358
+ return alt ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { "aria-label": `Image blocked: ${alt}`, role: "img", children: alt }) : null;
1359
+ }
1360
+ var BLOCK_MARKDOWN_TAGS = /* @__PURE__ */ new Set([
1361
+ "blockquote",
1362
+ "div",
1363
+ "h1",
1364
+ "h2",
1365
+ "h3",
1366
+ "h4",
1367
+ "h5",
1368
+ "h6",
1369
+ "hr",
1370
+ "ol",
1371
+ "pre",
1372
+ "table",
1373
+ "ul"
1374
+ ]);
1375
+ function isMultilineCodeNode(node) {
1376
+ return node?.tagName === "code" && node.position?.start?.line !== void 0 && node.position?.end?.line !== void 0 && node.position.start.line !== node.position.end.line;
1377
+ }
1378
+ function hasBlockMarkdownChild(child) {
1379
+ if (!React5.isValidElement(child)) {
1380
+ return false;
1381
+ }
1382
+ if (typeof child.type === "string" && BLOCK_MARKDOWN_TAGS.has(child.type)) {
1383
+ return true;
1384
+ }
1385
+ const props = child.props;
1386
+ if (BLOCK_MARKDOWN_TAGS.has(props.node?.tagName || "") || isMultilineCodeNode(props.node)) {
1387
+ return true;
1388
+ }
1389
+ return React5.Children.toArray(props.children).some(hasBlockMarkdownChild);
1390
+ }
1391
+ function MessageMarkdownParagraph({
1392
+ children,
1393
+ ...props
1394
+ }) {
1395
+ const domProps = { ...props };
1396
+ delete domProps.node;
1397
+ const childArray = React5.Children.toArray(children).filter(
1398
+ (child) => child !== null && child !== ""
1399
+ );
1400
+ const isImageOnly = childArray.length === 1 && React5.isValidElement(childArray[0]) && childArray[0].props.node?.tagName === "img";
1401
+ if (isImageOnly) {
1402
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_jsx_runtime7.Fragment, { children });
1403
+ }
1404
+ if (childArray.some(hasBlockMarkdownChild)) {
1405
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { ...domProps, children });
1406
+ }
1407
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { ...domProps, children });
1408
+ }
1409
+ var MESSAGE_RESPONSE_COMPONENTS = {
1410
+ a: SafeMarkdownLink,
1411
+ img: BlockedMarkdownImage,
1412
+ p: MessageMarkdownParagraph
1413
+ };
1414
+ var streamdownDefaultRehypePlugins = import_streamdown.defaultRehypePlugins || {};
1415
+ var streamdownHardenPlugin = Array.isArray(
1416
+ streamdownDefaultRehypePlugins.harden
1417
+ ) ? streamdownDefaultRehypePlugins.harden[0] : streamdownDefaultRehypePlugins.harden;
1418
+ var MESSAGE_RESPONSE_REHYPE_PLUGINS = [
1419
+ ...Object.entries(streamdownDefaultRehypePlugins).filter(([key]) => key !== "harden").map(([, plugin]) => plugin),
1420
+ ...streamdownHardenPlugin ? [
1421
+ [
1422
+ streamdownHardenPlugin,
1423
+ CHAT_MARKDOWN_HARDEN_OPTIONS
1424
+ ]
1425
+ ] : []
1426
+ ];
1427
+ var MessageResponse = React5.memo(
1428
+ ({
1429
+ children,
1430
+ className,
1431
+ components,
1432
+ rehypePlugins,
1433
+ streaming = false,
1434
+ streamingId,
1435
+ ...props
1436
+ }) => {
1437
+ const text = typeof children === "string" || typeof children === "number" ? String(children) : "";
1438
+ const streamingText = useStreamingText({
1439
+ charsPerSecond: 120,
1440
+ id: streamingId || text,
1441
+ isStreaming: streaming && text.length > 0,
1442
+ maxCatchupChars: 32,
1443
+ text
1444
+ });
1445
+ const streamdownComponents = React5.useMemo(
1446
+ () => ({ ...MESSAGE_RESPONSE_COMPONENTS, ...components }),
1447
+ [components]
1448
+ );
1449
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1450
+ import_streamdown.Streamdown,
1451
+ {
1452
+ className: cn("size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0", className),
1453
+ components: streamdownComponents,
1454
+ rehypePlugins: rehypePlugins || MESSAGE_RESPONSE_REHYPE_PLUGINS,
1455
+ ...props,
1456
+ children: text ? streamingText.displayedText : children
1457
+ }
1458
+ );
1459
+ },
1460
+ (prevProps, nextProps) => prevProps.children === nextProps.children && prevProps.className === nextProps.className && prevProps.streaming === nextProps.streaming && prevProps.streamingId === nextProps.streamingId && prevProps.components === nextProps.components
1461
+ );
1462
+ MessageResponse.displayName = "MessageResponse";
1463
+
1464
+ // src/ui/hitl-presentation.ts
1465
+ function optionalString(value) {
1466
+ return typeof value === "string" && value.trim() ? value.trim() : null;
1467
+ }
1468
+ function hitlActionLabel(part, action) {
1469
+ if (action === "approve") return ["tool_review", "app_data_permission"].includes(part.hitlKind) ? "Allow once" : "Approve";
1470
+ if (action === "reject") return ["tool_review", "app_data_permission"].includes(part.hitlKind) ? "Don\u2019t allow" : "Reject";
1471
+ if (action === "connect") return "Connect";
1472
+ return "Skip";
1473
+ }
1474
+ function hitlTitle(part) {
1475
+ const presentation = part.presentation || {};
1476
+ if (part.hitlKind === "tool_review") return `Run ${optionalString(presentation.tool_name) || "this tool"}?`;
1477
+ if (part.hitlKind === "mcp_auth") {
1478
+ const serverName = optionalString(presentation.server_name);
1479
+ return serverName ? `Connect ${serverName} to continue` : part.message;
1480
+ }
1481
+ if (part.hitlKind === "app_data_permission") return "Allow this data change?";
1482
+ return part.message;
1483
+ }
1484
+ function resolvedHitlLabel(part) {
1485
+ const outcome = String(part.resolution?.outcome || part.status).trim().toLowerCase();
1486
+ return {
1487
+ approved: part.hitlKind === "user_approval" ? "Approved" : "Allowed",
1488
+ rejected: part.hitlKind === "user_approval" ? "Rejected" : "Not allowed",
1489
+ connected: "connected",
1490
+ skipped: "Skipped",
1491
+ timeout: "Timed out",
1492
+ expired: "Timed out",
1493
+ cancelled: "Cancelled",
1494
+ invalidated: "No longer available",
1495
+ policy_denied: "Not allowed"
1496
+ }[outcome] || outcome.replace(/_/g, " ");
1497
+ }
1498
+
1499
+ // src/ui/agent-response-timeline.tsx
1500
+ var import_jsx_runtime8 = require("react/jsx-runtime");
1501
+ function optionalString2(value) {
1502
+ return typeof value === "string" && value.trim() ? value.trim() : null;
1503
+ }
1504
+ function presentationValue(part, key) {
1505
+ const presentation = part.presentation || {};
1506
+ const snakeKey = key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
1507
+ return optionalString2(presentation[key]) || optionalString2(presentation[snakeKey]);
1508
+ }
1509
+ function groupKey(part) {
1510
+ return presentationValue(part, "groupKey");
1511
+ }
1512
+ function groupLabel(part) {
1513
+ return presentationValue(part, "groupLabel") || toolLabel(part);
1514
+ }
1515
+ function toolLabel(part) {
1516
+ return presentationValue(part, "activity") || presentationValue(part, "title") || part.toolName || "Tool activity";
1517
+ }
1518
+ function toolDetail(part) {
1519
+ return presentationValue(part, "detail") || part.errorText || null;
1520
+ }
1521
+ function isActiveTool(part, activeToolId) {
1522
+ return part.id === activeToolId || part.toolCallId === activeToolId || part.state === "input-available" || part.state === "input-streaming";
1523
+ }
1524
+ function Shimmer({ children, className }) {
1525
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: cn("inline-block animate-pulse", className), children });
1526
+ }
1527
+ function CompactTask({
1528
+ children,
1529
+ defaultOpen = false,
1530
+ detail,
1531
+ error,
1532
+ label,
1533
+ loading
1534
+ }) {
1535
+ const [open, setOpen] = React6.useState(defaultOpen);
1536
+ const expandable = Boolean(children || detail);
1537
+ const content = loading ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Shimmer, { children: label }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: label });
1538
+ if (!expandable) {
1539
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: cn("flex min-w-0 items-center gap-1.5 text-sm", error ? "text-destructive" : "text-muted-foreground"), children: content });
1540
+ }
1541
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "w-full", children: [
1542
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1543
+ "button",
1544
+ {
1545
+ "aria-expanded": open,
1546
+ className: cn(
1547
+ "group flex min-w-0 items-center gap-1.5 rounded-md px-0 py-0.5 text-left text-sm transition-colors hover:text-foreground",
1548
+ error ? "text-destructive" : "text-muted-foreground"
1549
+ ),
1550
+ onClick: () => setOpen((value) => !value),
1551
+ type: "button",
1552
+ children: [
1553
+ content,
1554
+ open ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react3.ChevronDown, { className: "size-3" }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react3.ChevronRight, { className: "size-3 opacity-70" })
1555
+ ]
1556
+ }
1557
+ ),
1558
+ open ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "mt-1 pl-4 text-sm text-muted-foreground", children: [
1559
+ detail ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { children: detail }) : null,
1560
+ children
1561
+ ] }) : null
1562
+ ] });
1563
+ }
1564
+ function ToolPartRow({
1565
+ activeToolId,
1566
+ part
1567
+ }) {
1568
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { "data-agents24-tool-part": part.type, "data-state": part.state, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1569
+ CompactTask,
1570
+ {
1571
+ defaultOpen: part.state === "output-error",
1572
+ detail: toolDetail(part),
1573
+ error: part.state === "output-error",
1574
+ label: toolLabel(part),
1575
+ loading: isActiveTool(part, activeToolId)
1576
+ }
1577
+ ) });
1578
+ }
1579
+ function ToolGroup({
1580
+ activeToolId,
1581
+ parts
1582
+ }) {
1583
+ const loading = parts.some((part) => isActiveTool(part, activeToolId));
1584
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CompactTask, { label: groupLabel(parts[0]), loading, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "space-y-1", children: parts.map((part) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1585
+ "div",
1586
+ {
1587
+ className: cn("text-sm", part.state === "output-error" ? "text-destructive" : "text-muted-foreground"),
1588
+ children: [
1589
+ isActiveTool(part, activeToolId) ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Shimmer, { children: toolLabel(part) }) : toolLabel(part),
1590
+ toolDetail(part) ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "ml-1 text-xs text-muted-foreground/75", children: toolDetail(part) }) : null
1591
+ ]
1592
+ },
1593
+ part.id
1594
+ )) }) });
1595
+ }
1596
+ function HitlPartRow({
1597
+ getHitlActionState,
1598
+ isLoading,
1599
+ onHitlAction,
1600
+ part
1601
+ }) {
1602
+ const state = getHitlActionState?.(part);
1603
+ const [comment, setComment] = React6.useState("");
1604
+ const presentation = part.presentation || {};
1605
+ const requireComment = presentation.require_comment === true;
1606
+ const isTerminal = part.status !== "pending";
1607
+ const resolvedRef = React6.useRef(null);
1608
+ const wasTerminal = React6.useRef(isTerminal);
1609
+ React6.useEffect(() => {
1610
+ if (isTerminal && !wasTerminal.current) resolvedRef.current?.focus();
1611
+ wasTerminal.current = isTerminal;
1612
+ }, [isTerminal]);
1613
+ if (isTerminal) {
1614
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "a24-hitl a24-hitl--resolved px-1 py-1 text-sm text-muted-foreground", ref: resolvedRef, role: "status", tabIndex: -1, children: resolvedHitlLabel(part) });
1615
+ }
1616
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "a24-hitl space-y-2.5 px-1 py-2", children: [
1617
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "a24-hitl__title text-sm font-medium text-foreground text-pretty", children: hitlTitle(part) }),
1618
+ requireComment ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1619
+ "textarea",
1620
+ {
1621
+ "aria-label": "Approval comment",
1622
+ autoComplete: "off",
1623
+ className: "a24-hitl__comment min-h-20 w-full resize-y rounded-md border border-border/60 bg-background px-3 py-2 text-sm outline-none focus-visible:border-neutral-300 focus-visible:ring-0 dark:focus-visible:border-neutral-600",
1624
+ name: "hitl-comment",
1625
+ onChange: (event) => setComment(event.target.value),
1626
+ placeholder: "Comment required\u2026",
1627
+ value: comment
1628
+ }
1629
+ ) : null,
1630
+ state?.error ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "a24-hitl__error text-sm text-destructive", children: state.error }) : null,
1631
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "a24-hitl__actions flex flex-wrap items-center gap-1.5", children: part.allowedActions.map((action) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1632
+ "button",
1633
+ {
1634
+ "aria-label": hitlActionLabel(part, action),
1635
+ className: cn(
1636
+ "a24-hitl__action min-h-10 rounded-md px-3 text-sm font-medium transition-[background-color,color,opacity,transform] duration-150 ease-out active:scale-[0.96] disabled:pointer-events-none disabled:opacity-60",
1637
+ action === "approve" || action === "connect" ? "bg-neutral-900 text-white hover:bg-neutral-800 dark:bg-neutral-100 dark:text-neutral-900 dark:hover:bg-white" : "bg-transparent text-muted-foreground hover:bg-background/80 hover:text-foreground"
1638
+ ),
1639
+ "data-primary": action === "approve" || action === "connect" ? "" : void 0,
1640
+ disabled: !onHitlAction || isLoading || state?.status === "connecting" || state?.status === "resuming" || requireComment && !comment.trim(),
1641
+ onClick: () => onHitlAction?.(part, action, comment.trim() || void 0),
1642
+ type: "button",
1643
+ children: hitlActionLabel(part, action)
1644
+ },
1645
+ action
1646
+ )) })
1647
+ ] });
1648
+ }
1649
+ function DefaultPartRow({
1650
+ activeToolId,
1651
+ getHitlActionState,
1652
+ isLoading,
1653
+ message,
1654
+ onHitlAction,
1655
+ part
1656
+ }) {
1657
+ if (part.kind === "text") return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(MessageResponse, { children: part.text });
1658
+ if (part.kind === "tool") return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ToolPartRow, { activeToolId, part });
1659
+ if (part.kind === "reasoning") {
1660
+ const label = part.text || part.label || "Thinking";
1661
+ return part.status === "running" || part.status === "streaming" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Shimmer, { className: "text-sm text-muted-foreground", children: label }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "text-sm text-muted-foreground", children: label });
1662
+ }
1663
+ if (part.kind === "ui-blocks") {
1664
+ if (part.state === "output-error") {
1665
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "text-sm text-destructive", children: part.errorText || "Failed to render UI content." });
1666
+ }
1667
+ return part.bundle ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SafeUiBlocks, { bundle: part.bundle }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CompactTask, { label: part.state === "input-streaming" ? "Rendering UI block" : "Rendered UI block", loading: part.state === "input-streaming" });
1668
+ }
1669
+ if (part.kind === "hitl") {
1670
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1671
+ HitlPartRow,
1672
+ {
1673
+ getHitlActionState,
1674
+ isLoading,
1675
+ onHitlAction,
1676
+ part
1677
+ }
1678
+ );
1679
+ }
1680
+ if (part.kind === "error") return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "text-sm text-destructive", role: "alert", children: part.errorText });
1681
+ if (part.kind === "source") {
1682
+ const href = safeExternalUrl(part.url);
1683
+ return href ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("a", { className: "a24-source-link", href, rel: "noopener noreferrer", target: "_blank", children: part.title }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "a24-source-link", children: part.title });
1684
+ }
1685
+ if (part.kind === "citation") {
1686
+ const href = safeExternalUrl(part.url);
1687
+ const label = part.label || "Citation";
1688
+ return href ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("a", { className: "a24-citation", href, rel: "noopener noreferrer", target: "_blank", children: label }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "a24-citation", children: label });
1689
+ }
1690
+ if (part.kind === "attachment") return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "a24-output-attachment", children: part.attachment.filename || "Attachment" });
1691
+ if (part.kind === "data") return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { "data-agents24-data-part": part.name, className: "hidden", children: message.id });
1692
+ return null;
1693
+ }
1694
+ function safeExternalUrl(value) {
1695
+ if (!value) return null;
1696
+ try {
1697
+ const url = new URL(value);
1698
+ return url.protocol === "https:" || url.protocol === "http:" ? url.href : null;
1699
+ } catch {
1700
+ return null;
1701
+ }
1702
+ }
1703
+ function SafeUiBlocks({ bundle }) {
1704
+ const rows = Array.isArray(bundle.rows) ? bundle.rows : [];
1705
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("section", { "aria-label": typeof bundle.title === "string" ? bundle.title : "Agent result", className: "a24-ui-blocks", children: [
1706
+ typeof bundle.title === "string" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h3", { children: bundle.title }) : null,
1707
+ rows.map((rawRow, rowIndex) => {
1708
+ const row = rawRow && typeof rawRow === "object" && !Array.isArray(rawRow) ? rawRow : {};
1709
+ const blocks = Array.isArray(row.blocks) ? row.blocks : [];
1710
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "a24-ui-blocks__row", children: blocks.map((rawBlock, blockIndex) => {
1711
+ const block = rawBlock && typeof rawBlock === "object" && !Array.isArray(rawBlock) ? rawBlock : {};
1712
+ const title = typeof block.title === "string" ? block.title : "Result";
1713
+ const kind = String(block.kind || "note");
1714
+ if (kind === "table" && Array.isArray(block.columns) && Array.isArray(block.rows)) {
1715
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "a24-ui-block", children: [
1716
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h4", { children: title }),
1717
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "a24-ui-table-scroll", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("table", { children: [
1718
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("tr", { children: block.columns.map((column, index) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("th", { children: String(column) }, index)) }) }),
1719
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("tbody", { children: block.rows.map((rawCells, index) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("tr", { children: (Array.isArray(rawCells) ? rawCells : []).map((cell, cellIndex) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("td", { children: String(cell) }, cellIndex)) }, index)) })
1720
+ ] }) })
1721
+ ] }, String(block.id || blockIndex));
1722
+ }
1723
+ const value = block.value ?? block.text ?? "";
1724
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "a24-ui-block", children: [
1725
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h4", { children: title }),
1726
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { children: String(value) })
1727
+ ] }, String(block.id || blockIndex));
1728
+ }) }, `row-${rowIndex}`);
1729
+ })
1730
+ ] });
1731
+ }
1732
+ function AgentResponseTimeline({
1733
+ getHitlActionState,
1734
+ isLoading = false,
1735
+ message,
1736
+ onHitlAction,
1737
+ renderPart
1738
+ }) {
1739
+ const parts = message.parts || [];
1740
+ const activeToolId = [...parts].reverse().find((part) => part.kind === "tool" && (part.state === "input-available" || part.state === "input-streaming"))?.id;
1741
+ const rendered = [];
1742
+ let index = 0;
1743
+ while (index < parts.length) {
1744
+ const part = parts[index];
1745
+ if (part.kind === "tool" && groupKey(part)) {
1746
+ const key = groupKey(part);
1747
+ const group = [];
1748
+ while (index < parts.length) {
1749
+ const candidate = parts[index];
1750
+ if (candidate.kind !== "tool" || groupKey(candidate) !== key) break;
1751
+ group.push(candidate);
1752
+ index += 1;
1753
+ }
1754
+ rendered.push(group.length === 1 ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ToolPartRow, { activeToolId, part: group[0] }, group[0].id) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ToolGroup, { activeToolId, parts: group }, `tool-group-${key}-${group[0].id}`));
1755
+ continue;
1756
+ }
1757
+ rendered.push(
1758
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(React6.Fragment, { children: renderPart ? renderPart(part, message) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1759
+ DefaultPartRow,
1760
+ {
1761
+ activeToolId,
1762
+ getHitlActionState,
1763
+ isLoading,
1764
+ message,
1765
+ onHitlAction,
1766
+ part
1767
+ }
1768
+ ) }, part.id)
1769
+ );
1770
+ index += 1;
1771
+ }
1772
+ if (rendered.length === 0 && message.content) {
1773
+ rendered.push(/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(MessageResponse, { children: message.content }, `${message.id}-fallback`));
1774
+ }
1775
+ if (rendered.length === 0 && isLoading) {
1776
+ rendered.push(/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "text-sm text-muted-foreground", children: "Thinking" }, `${message.id}-thinking`));
1777
+ }
1778
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "space-y-3", children: rendered });
1779
+ }
1780
+
1781
+ // src/ui/agent-chat-message.tsx
1782
+ var import_jsx_runtime9 = require("react/jsx-runtime");
1783
+ var DEFAULT_USER_MESSAGE_COLLAPSE_THRESHOLD = 360;
1784
+ var DEFAULT_USER_MESSAGE_COLLAPSED_LINES = 4;
1785
+ function shouldCollapseUserMessage(content, threshold = DEFAULT_USER_MESSAGE_COLLAPSE_THRESHOLD) {
1786
+ return typeof threshold === "number" && threshold > 0 && content.length > threshold;
1787
+ }
1788
+ function AgentChatUserMessageContent({
1789
+ collapsedLines = DEFAULT_USER_MESSAGE_COLLAPSED_LINES,
1790
+ collapseThreshold = DEFAULT_USER_MESSAGE_COLLAPSE_THRESHOLD,
1791
+ content,
1792
+ showLessLabel = "Show less",
1793
+ showMoreLabel = "Show more"
1794
+ }) {
1795
+ const [isExpanded, setIsExpanded] = React7.useState(false);
1796
+ const contentId = React7.useId();
1797
+ const shouldCollapse = shouldCollapseUserMessage(content, collapseThreshold);
1798
+ const Icon = isExpanded ? import_lucide_react4.ChevronUp : import_lucide_react4.ChevronDown;
1799
+ const clampedStyle = shouldCollapse && !isExpanded ? {
1800
+ WebkitBoxOrient: "vertical",
1801
+ WebkitLineClamp: Math.max(1, collapsedLines),
1802
+ display: "-webkit-box",
1803
+ overflow: "hidden"
1804
+ } : void 0;
1805
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex min-w-0 flex-col gap-3", children: [
1806
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { id: contentId, className: "min-w-0", style: clampedStyle, children: content }),
1807
+ shouldCollapse ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1808
+ "button",
1809
+ {
1810
+ "aria-controls": contentId,
1811
+ "aria-expanded": isExpanded,
1812
+ className: cn(
1813
+ "inline-flex w-fit items-center gap-1 rounded-md text-left text-sm font-medium text-muted-foreground transition-colors",
1814
+ "hover:text-foreground focus-visible:border-neutral-300 focus-visible:outline-none focus-visible:ring-0"
1815
+ ),
1816
+ onClick: () => setIsExpanded((current) => !current),
1817
+ type: "button",
1818
+ children: [
1819
+ isExpanded ? showLessLabel : showMoreLabel,
1820
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Icon, { "aria-hidden": "true", className: "size-4", strokeWidth: 2 })
1821
+ ]
1822
+ }
1823
+ ) : null
1824
+ ] });
1825
+ }
1826
+ function AgentChatMessage({
1827
+ actionHandlers,
1828
+ actions,
1829
+ actionsClassName,
1830
+ className,
1831
+ contentClassName,
1832
+ dir,
1833
+ getHitlActionState,
1834
+ message,
1835
+ onHitlAction,
1836
+ renderAssistantContent,
1837
+ renderPart,
1838
+ renderUserContent,
1839
+ showAssistantAttachments = false,
1840
+ streamingMessageId,
1841
+ userMessageCollapsedLines,
1842
+ userMessageContent,
1843
+ userMessageCollapseThreshold,
1844
+ userMessageShowLessLabel,
1845
+ userMessageShowMoreLabel
1846
+ }) {
1847
+ const isAssistant = message.role === "assistant";
1848
+ const isStreaming = isAssistant && message.id === streamingMessageId;
1849
+ const resolvedUserContent = userMessageContent ?? message.content;
1850
+ const shouldRenderContent = isAssistant || Boolean(renderUserContent || resolvedUserContent);
1851
+ const showAttachments = Boolean(message.attachments?.length) && (message.role === "user" || showAssistantAttachments);
1852
+ const renderedActions = actions ?? (actionHandlers ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(AgentChatDefaultActions, { handlers: actionHandlers, message }) : null);
1853
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(ChatMessage, { className, dir, from: message.role, children: [
1854
+ showAttachments ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ChatAttachments, { className: "mb-2", dir, children: message.attachments?.map((attachment, index) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1855
+ ChatAttachment,
1856
+ {
1857
+ data: attachment,
1858
+ dir: "ltr"
1859
+ },
1860
+ String(attachment.url || attachment.filename || index)
1861
+ )) }) : null,
1862
+ shouldRenderContent ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1863
+ ChatMessageContent,
1864
+ {
1865
+ className: cn(isAssistant ? "bg-transparent p-0" : void 0, contentClassName),
1866
+ dir,
1867
+ children: [
1868
+ isAssistant ? renderAssistantContent ? renderAssistantContent({ isStreaming, message }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1869
+ AgentResponseTimeline,
1870
+ {
1871
+ getHitlActionState,
1872
+ isLoading: isStreaming,
1873
+ message,
1874
+ onHitlAction,
1875
+ renderPart
1876
+ }
1877
+ ) : null,
1878
+ !isAssistant ? renderUserContent ? renderUserContent({ message }) : resolvedUserContent ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1879
+ AgentChatUserMessageContent,
1880
+ {
1881
+ collapsedLines: userMessageCollapsedLines,
1882
+ collapseThreshold: userMessageCollapseThreshold,
1883
+ content: resolvedUserContent,
1884
+ showLessLabel: userMessageShowLessLabel,
1885
+ showMoreLabel: userMessageShowMoreLabel
1886
+ }
1887
+ ) : null : null
1888
+ ]
1889
+ }
1890
+ ) : null,
1891
+ renderedActions ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: cn(dir === "rtl" ? "mt-1 h-9 w-full translate-x-2" : "mt-1 h-9 w-full -translate-x-2", actionsClassName), children: renderedActions }) : null
1892
+ ] });
1893
+ }
1894
+
1895
+ // src/templates/agent-chat-layout.tsx
1896
+ var import_jsx_runtime10 = require("react/jsx-runtime");
1897
+ var stateCopy = {
1898
+ loading: { title: "Loading chat", detail: "Your conversation is being prepared." },
1899
+ reconnecting: { title: "Reconnecting", detail: "Messages are safe. Trying the connection again." },
1900
+ offline: { title: "You\u2019re offline", detail: "Reconnect to continue this conversation." },
1901
+ denied: { title: "Access denied", detail: "This deployment does not allow this request." },
1902
+ quota: { title: "Usage limit reached", detail: "Try again after the deployment limit resets." },
1903
+ failed: { title: "Something went wrong", detail: "The request could not be completed safely." },
1904
+ expired: { title: "Session expired", detail: "Reconnect to start a new secure session." }
1905
+ };
1906
+ function AgentChatStatePanel({
1907
+ errorMessage,
1908
+ onRetry,
1909
+ state
1910
+ }) {
1911
+ const copy = stateCopy[state];
1912
+ const retryable = state === "offline" || state === "failed" || state === "expired";
1913
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "a24-state", "data-state": state, role: state === "failed" ? "alert" : "status", children: [
1914
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "a24-state__icon", "aria-hidden": "true", children: state === "offline" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react5.WifiOff, {}) : state === "reconnecting" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react5.RefreshCw, {}) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react5.MessageSquare, {}) }),
1915
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h2", { children: copy.title }),
1916
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { children: errorMessage || copy.detail }),
1917
+ retryable && onRetry ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { className: "a24-button a24-button--primary", onClick: () => void onRetry(), type: "button", children: "Try again" }) : null
1918
+ ] });
1919
+ }
1920
+ function connectionLabel(state) {
1921
+ if (state === "connected") return "Connected";
1922
+ if (state === "connecting") return "Connecting";
1923
+ if (state === "reconnecting") return "Reconnecting";
1924
+ if (state === "expired") return "Session expired";
1925
+ return "Offline";
1926
+ }
1927
+ function AgentAvatar({ agent }) {
1928
+ return agent.avatarUrl ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("img", { alt: "", className: "a24-avatar", height: 36, src: agent.avatarUrl, width: 36 }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { "aria-hidden": "true", className: "a24-avatar a24-avatar--fallback", children: agent.name.trim().slice(0, 1).toUpperCase() || "A" });
1929
+ }
1930
+ function ThreadNavigation({
1931
+ activeThreadId,
1932
+ close,
1933
+ hasMoreThreads,
1934
+ isDeletingThread,
1935
+ isLoadingThreads,
1936
+ onDeleteThread,
1937
+ onLoadMoreThreads,
1938
+ onNewThread,
1939
+ onOpenThread,
1940
+ slots,
1941
+ threads
1942
+ }) {
1943
+ const [query, setQuery] = React8.useState("");
1944
+ const normalizedQuery = query.trim().toLocaleLowerCase();
1945
+ const visibleThreads = normalizedQuery ? threads.filter((thread) => String(thread.title || "New chat").toLocaleLowerCase().includes(normalizedQuery)) : threads;
1946
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "a24-sidebar__inner", children: [
1947
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "a24-sidebar__top", children: [
1948
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1949
+ "button",
1950
+ {
1951
+ "aria-label": "New chat",
1952
+ className: "a24-button a24-button--primary a24-sidebar__new",
1953
+ onClick: () => {
1954
+ onNewThread();
1955
+ close();
1956
+ },
1957
+ type: "button",
1958
+ children: [
1959
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react5.Plus, { "aria-hidden": "true" }),
1960
+ "New chat"
1961
+ ]
1962
+ }
1963
+ ),
1964
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { "aria-label": "Close conversations", className: "a24-icon-button a24-sidebar__mobile-close", onClick: close, type: "button", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react5.X, { "aria-hidden": "true" }) })
1965
+ ] }),
1966
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("label", { className: "a24-search", children: [
1967
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react5.Search, { "aria-hidden": "true" }),
1968
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "a24-sr-only", children: "Search conversations" }),
1969
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1970
+ "input",
1971
+ {
1972
+ autoComplete: "off",
1973
+ name: "conversation-search",
1974
+ onChange: (event) => setQuery(event.target.value),
1975
+ placeholder: "Search conversations\u2026",
1976
+ type: "search",
1977
+ value: query
1978
+ }
1979
+ )
1980
+ ] }),
1981
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("nav", { "aria-label": "Conversations", className: "a24-thread-list", children: [
1982
+ isLoadingThreads && threads.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "a24-sidebar__hint", role: "status", children: "Loading conversations\u2026" }) : null,
1983
+ !isLoadingThreads && visibleThreads.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "a24-sidebar__hint", children: "No conversations yet." }) : null,
1984
+ visibleThreads.map((thread) => {
1985
+ const selected = thread.id === activeThreadId;
1986
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "a24-thread", "data-selected": selected || void 0, children: [
1987
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1988
+ "button",
1989
+ {
1990
+ "aria-current": selected ? "page" : void 0,
1991
+ className: "a24-thread__open",
1992
+ onClick: () => {
1993
+ void onOpenThread(thread.id);
1994
+ close();
1995
+ },
1996
+ type: "button",
1997
+ children: [
1998
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "a24-thread__title", children: thread.title || "New chat" }),
1999
+ thread.status ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "a24-thread__status", children: thread.status }) : null
2000
+ ]
2001
+ }
2002
+ ),
2003
+ slots?.threadActions ? slots.threadActions(thread) : onDeleteThread ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2004
+ "button",
2005
+ {
2006
+ "aria-label": `Delete ${thread.title || "conversation"}`,
2007
+ className: "a24-icon-button a24-thread__delete",
2008
+ disabled: isDeletingThread,
2009
+ onClick: () => void onDeleteThread(thread.id),
2010
+ type: "button",
2011
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react5.Trash2, { "aria-hidden": "true" })
2012
+ }
2013
+ ) : null
2014
+ ] }, thread.id);
2015
+ })
2016
+ ] }),
2017
+ hasMoreThreads && onLoadMoreThreads ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("button", { className: "a24-button a24-button--quiet a24-sidebar__more", onClick: () => void onLoadMoreThreads(), type: "button", children: [
2018
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react5.MoreHorizontal, { "aria-hidden": "true" }),
2019
+ "Load more"
2020
+ ] }) : null
2021
+ ] });
2022
+ }
2023
+ function AgentChatLayout({
2024
+ activeThreadId,
2025
+ agent,
2026
+ allowAttachments = true,
2027
+ className,
2028
+ connectionState = "connected",
2029
+ dir = "ltr",
2030
+ errorMessage,
2031
+ hasMoreThreads,
2032
+ isDeletingThread,
2033
+ isLoadingOlder,
2034
+ isLoadingThreads,
2035
+ isRunning = false,
2036
+ messages,
2037
+ modelId,
2038
+ models = [],
2039
+ onDeleteThread,
2040
+ onHitlAction,
2041
+ onLoadMoreThreads,
2042
+ onLoadOlder,
2043
+ onModelChange,
2044
+ onNewThread,
2045
+ onOpenThread,
2046
+ onRetryConnection,
2047
+ onStop,
2048
+ onSubmit,
2049
+ placeholder,
2050
+ slots,
2051
+ state = "ready",
2052
+ streamingMessageId,
2053
+ threads,
2054
+ title
2055
+ }) {
2056
+ const [sidebarOpen, setSidebarOpen] = React8.useState(false);
2057
+ const menuButtonRef = React8.useRef(null);
2058
+ const closeSidebar = React8.useCallback(() => {
2059
+ setSidebarOpen(false);
2060
+ menuButtonRef.current?.focus();
2061
+ }, []);
2062
+ React8.useEffect(() => {
2063
+ if (!sidebarOpen) return void 0;
2064
+ const onKeyDown = (event) => {
2065
+ if (event.key !== "Escape") return;
2066
+ event.preventDefault();
2067
+ closeSidebar();
2068
+ };
2069
+ window.addEventListener("keydown", onKeyDown);
2070
+ return () => window.removeEventListener("keydown", onKeyDown);
2071
+ }, [closeSidebar, sidebarOpen]);
2072
+ const shellState = state === "streaming" || state === "paused" ? "ready" : state;
2073
+ const showState = shellState !== "ready";
2074
+ const liveLabel = state === "streaming" ? "Agent is responding" : state === "paused" ? "Waiting for your response" : connectionLabel(connectionState);
2075
+ const composer = /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2076
+ AgentChatComposer,
2077
+ {
2078
+ allowAttachments,
2079
+ disabled: state === "denied" || state === "quota" || state === "expired" || state === "offline",
2080
+ inputToolbarContent: models.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("label", { className: "a24-model-select", children: [
2081
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "a24-sr-only", children: "Model" }),
2082
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("select", { "aria-label": "Model", onChange: (event) => onModelChange?.(event.target.value), value: modelId || models[0]?.id, children: models.map((model) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("option", { value: model.id, children: model.label }, model.id)) }),
2083
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react5.ChevronDown, { "aria-hidden": "true" })
2084
+ ] }) : void 0,
2085
+ isRunning,
2086
+ onStop,
2087
+ onSubmit,
2088
+ placeholder
2089
+ }
2090
+ );
2091
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("section", { className: cn("a24-chat", className), "data-agents24-chat-layout": "", dir, children: [
2092
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("a", { className: "a24-skip-link", href: "#agents24-chat-main", children: "Skip to messages" }),
2093
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("nav", { "aria-label": "Chat navigation", className: "a24-sidebar", "data-open": sidebarOpen || void 0, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2094
+ ThreadNavigation,
2095
+ {
2096
+ activeThreadId,
2097
+ close: closeSidebar,
2098
+ hasMoreThreads,
2099
+ isDeletingThread,
2100
+ isLoadingThreads,
2101
+ onDeleteThread,
2102
+ onLoadMoreThreads,
2103
+ onNewThread,
2104
+ onOpenThread,
2105
+ slots,
2106
+ threads
2107
+ }
2108
+ ) }),
2109
+ sidebarOpen ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { "aria-label": "Close conversations", className: "a24-sidebar-backdrop", onClick: closeSidebar, type: "button" }) : null,
2110
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "a24-chat__body", children: [
2111
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("header", { className: "a24-header", role: "banner", children: [
2112
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { "aria-label": "Open conversations", className: "a24-icon-button a24-header__menu", onClick: () => setSidebarOpen(true), ref: menuButtonRef, type: "button", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react5.Menu, { "aria-hidden": "true" }) }),
2113
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(AgentAvatar, { agent }),
2114
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "a24-header__identity", children: [
2115
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h1", { children: title || agent.name }),
2116
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "a24-connection", "data-state": connectionState, children: [
2117
+ connectionState === "offline" || connectionState === "expired" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react5.WifiOff, { "aria-hidden": "true" }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react5.Wifi, { "aria-hidden": "true" }),
2118
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: connectionLabel(connectionState) })
2119
+ ] })
2120
+ ] }),
2121
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "a24-header__actions", children: slots?.headerActions })
2122
+ ] }),
2123
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("main", { "aria-busy": state === "loading" || state === "reconnecting", className: "a24-main", id: "agents24-chat-main", children: showState ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(AgentChatStatePanel, { errorMessage, onRetry: onRetryConnection, state: shellState }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2124
+ AgentChatShell,
2125
+ {
2126
+ composer,
2127
+ dir,
2128
+ isLoadingOlder,
2129
+ messages,
2130
+ onLoadOlder,
2131
+ renderEmptyState: slots?.empty ? () => slots.empty : void 0,
2132
+ renderMessage: (message) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2133
+ AgentChatMessage,
2134
+ {
2135
+ actions: slots?.messageActions?.(message),
2136
+ message,
2137
+ onHitlAction: (part, action, comment) => void onHitlAction?.(part, action, comment),
2138
+ renderAssistantContent: slots?.assistantBody,
2139
+ streamingMessageId
2140
+ }
2141
+ )
2142
+ }
2143
+ ) }),
2144
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { "aria-live": "polite", "aria-atomic": "true", className: "a24-sr-only", children: liveLabel })
2145
+ ] })
2146
+ ] });
2147
+ }
2148
+
2149
+ // src/templates/agent-chat-app.tsx
2150
+ var import_react2 = require("@agents24/react");
2151
+ var React9 = __toESM(require("react"), 1);
2152
+
2153
+ // src/mcp-oauth.ts
2154
+ var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
2155
+ function isLoopback(hostname) {
2156
+ return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]";
2157
+ }
2158
+ function normalizedOrigin(value) {
2159
+ try {
2160
+ const url = new URL(value);
2161
+ if (url.protocol !== "https:" && !(url.protocol === "http:" && isLoopback(url.hostname))) {
2162
+ throw new Error("unsafe callback origin");
2163
+ }
2164
+ if (url.origin === "null") throw new Error("opaque callback origin");
2165
+ return url.origin;
2166
+ } catch {
2167
+ throw new Error("MCP authorization returned an invalid callback origin.");
2168
+ }
2169
+ }
2170
+ function validateMcpCallbackResult(result, expectedServerId) {
2171
+ const serverId = String(result.server_id || "").trim();
2172
+ const connectionId = String(result.connection_id || "").trim();
2173
+ if (!serverId || serverId !== expectedServerId) {
2174
+ throw new Error("MCP authorization completed for an unexpected server.");
2175
+ }
2176
+ if (!UUID_PATTERN.test(connectionId)) {
2177
+ throw new Error("MCP authorization returned an invalid connection identity.");
2178
+ }
2179
+ return result;
2180
+ }
2181
+ function waitForMcpOauthComplete({
2182
+ popup,
2183
+ callbackOrigin,
2184
+ popupNonce,
2185
+ requireConnectionId = false,
2186
+ serverId,
2187
+ timeoutMs = 12e4
2188
+ }) {
2189
+ if (!popup) return Promise.reject(new Error("Could not open the MCP authorization popup."));
2190
+ const expectedOrigin = normalizedOrigin(callbackOrigin);
2191
+ const expectedServerId = String(serverId || "").trim();
2192
+ const expectedPopupNonce = String(popupNonce || "").trim();
2193
+ if (!expectedServerId) return Promise.reject(new Error("MCP authorization is missing its server identity."));
2194
+ if (!expectedPopupNonce) return Promise.reject(new Error("MCP authorization is missing its popup binding."));
2195
+ const returnFocusTo = typeof document !== "undefined" ? document.activeElement : null;
2196
+ return new Promise((resolve, reject) => {
2197
+ let settled = false;
2198
+ const finish = (result) => {
2199
+ if (settled) return;
2200
+ settled = true;
2201
+ window.clearTimeout(timeout);
2202
+ window.clearInterval(closeTimer);
2203
+ window.removeEventListener("message", onMessage);
2204
+ returnFocusTo?.focus?.();
2205
+ if (result instanceof Error) reject(result);
2206
+ else resolve(result);
2207
+ };
2208
+ const onMessage = (event) => {
2209
+ if (event.source !== popup || event.origin !== expectedOrigin) return;
2210
+ const data = event.data;
2211
+ if (data?.type !== "agents24.mcp.callback" || data.server_id !== expectedServerId || data.popup_nonce !== expectedPopupNonce) return;
2212
+ const code = String(data.code || "").trim();
2213
+ if (!code || code.length > 2048) return;
2214
+ const connectionId = String(data.connection_id || "").trim();
2215
+ if ((requireConnectionId || connectionId) && !UUID_PATTERN.test(connectionId)) return;
2216
+ finish({
2217
+ code,
2218
+ ...connectionId ? { connectionId } : {},
2219
+ popupNonce: expectedPopupNonce,
2220
+ serverId: expectedServerId
2221
+ });
2222
+ };
2223
+ const timeout = window.setTimeout(
2224
+ () => finish(new Error("MCP authorization timed out.")),
2225
+ Math.max(1, timeoutMs)
2226
+ );
2227
+ const closeTimer = window.setInterval(() => {
2228
+ if (popup.closed) finish(new Error("MCP authorization window was closed before connecting."));
2229
+ }, 500);
2230
+ window.addEventListener("message", onMessage);
2231
+ });
2232
+ }
2233
+
2234
+ // src/templates/agent-chat-app.tsx
2235
+ var import_jsx_runtime11 = require("react/jsx-runtime");
2236
+ function browserCrypto() {
2237
+ const value = globalThis.crypto;
2238
+ if (!value?.subtle || !value.randomUUID) throw new Error("Secure browser crypto is required for MCP authorization.");
2239
+ return value;
2240
+ }
2241
+ function base64Url(bytes) {
2242
+ let binary = "";
2243
+ bytes.forEach((byte) => {
2244
+ binary += String.fromCharCode(byte);
2245
+ });
2246
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
2247
+ }
2248
+ async function createPkceProof() {
2249
+ const crypto2 = browserCrypto();
2250
+ const verifier = base64Url(crypto2.getRandomValues(new Uint8Array(32)));
2251
+ const bytes = new TextEncoder().encode(verifier);
2252
+ const digest = await crypto2.subtle.digest("SHA-256", bytes);
2253
+ return { verifier, challenge: base64Url(new Uint8Array(digest)) };
2254
+ }
2255
+ function stateFromError(error) {
2256
+ const typed = error;
2257
+ if (typed?.kind === "authentication") return "expired";
2258
+ if (typed?.kind === "authorization") return "denied";
2259
+ if (typed?.kind === "quota" || typed?.kind === "rate_limit") return "quota";
2260
+ if (typed?.kind === "network") return "offline";
2261
+ return "failed";
2262
+ }
2263
+ function BoundAgentChatApp({
2264
+ agent: agentOverride,
2265
+ client,
2266
+ completeMcpAuthorization,
2267
+ mcpRedirectUri,
2268
+ modelId,
2269
+ models: modelOverrides,
2270
+ onModelChange,
2271
+ slots,
2272
+ ...layoutProps
2273
+ }) {
2274
+ const controller = (0, import_react2.useAgentChat)();
2275
+ const attachments = (0, import_react2.useAgentAttachments)();
2276
+ const mcp = (0, import_react2.useAgentMcp)();
2277
+ const [agent, setAgent] = React9.useState(agentOverride || { name: "Agent" });
2278
+ const [capabilities, setCapabilities] = React9.useState([]);
2279
+ const [bootstrapModels, setBootstrapModels] = React9.useState([]);
2280
+ const [selectedModelId, setSelectedModelId] = React9.useState(modelId || null);
2281
+ const [bootstrapLoading, setBootstrapLoading] = React9.useState(true);
2282
+ const [localError, setLocalError] = React9.useState(null);
2283
+ const [liked, setLiked] = React9.useState({});
2284
+ const [disliked, setDisliked] = React9.useState({});
2285
+ const loadBootstrap = React9.useCallback(async () => {
2286
+ setBootstrapLoading(true);
2287
+ setLocalError(null);
2288
+ try {
2289
+ const bootstrap = await client.bootstrap();
2290
+ if (!agentOverride) setAgent({ name: bootstrap.deployment.name || "Agent" });
2291
+ setCapabilities(bootstrap.capabilities);
2292
+ const modelFeatures = bootstrap.features.models;
2293
+ const allowedModelIds = Array.isArray(modelFeatures?.allowed_ids) ? modelFeatures.allowed_ids.filter((value) => typeof value === "string") : [];
2294
+ if (bootstrap.capabilities.includes("models.select") && modelFeatures?.selectable) {
2295
+ setBootstrapModels(allowedModelIds.map((id) => ({ id, label: id })));
2296
+ if (!modelId) {
2297
+ const defaultId = typeof modelFeatures.default_id === "string" ? modelFeatures.default_id : allowedModelIds[0];
2298
+ setSelectedModelId(defaultId || null);
2299
+ }
2300
+ } else {
2301
+ setBootstrapModels([]);
2302
+ }
2303
+ await controller.refreshThreads();
2304
+ } catch (error2) {
2305
+ setLocalError(error2);
2306
+ } finally {
2307
+ setBootstrapLoading(false);
2308
+ }
2309
+ }, [agentOverride, client, controller.refreshThreads, modelId]);
2310
+ React9.useEffect(() => {
2311
+ if (modelId !== void 0) setSelectedModelId(modelId);
2312
+ }, [modelId]);
2313
+ React9.useEffect(() => {
2314
+ void loadBootstrap();
2315
+ }, [loadBootstrap]);
2316
+ const submit = React9.useCallback(async (message) => {
2317
+ setLocalError(null);
2318
+ try {
2319
+ const uploaded = await Promise.all(message.files.map((file) => attachments.upload({
2320
+ data: file.source,
2321
+ name: file.filename,
2322
+ mediaType: file.mediaType,
2323
+ threadId: controller.activeThreadId || void 0
2324
+ })));
2325
+ await controller.submit({
2326
+ text: message.text,
2327
+ attachmentIds: uploaded.flatMap((attachment) => attachment.id ? [attachment.id] : []),
2328
+ attachments: uploaded,
2329
+ requestedModelId: selectedModelId || void 0
2330
+ });
2331
+ } catch (error2) {
2332
+ setLocalError(error2);
2333
+ }
2334
+ }, [attachments, controller, selectedModelId]);
2335
+ const deleteThread = React9.useCallback(async (threadId) => {
2336
+ if (!window.confirm("Delete this conversation? This cannot be undone.")) return;
2337
+ await controller.deleteThread(threadId);
2338
+ }, [controller.deleteThread]);
2339
+ const changeModel = React9.useCallback((nextModelId) => {
2340
+ if (modelId === void 0) setSelectedModelId(nextModelId);
2341
+ onModelChange?.(nextModelId);
2342
+ }, [modelId, onModelChange]);
2343
+ const handleMcp = React9.useCallback(async (part) => {
2344
+ if (!mcpRedirectUri) throw new Error("mcpRedirectUri is required for MCP Connect.");
2345
+ const serverId = String(part.presentation.server_id || "").trim();
2346
+ if (!serverId) throw new Error("MCP authorization is missing its server identity.");
2347
+ const runId = controller.activeRunId || controller.messages.find((message) => message.parts.includes(part))?.runId;
2348
+ if (!runId) throw new Error("MCP authorization is missing its paused run.");
2349
+ const popupNonce = browserCrypto().randomUUID();
2350
+ const proof = await createPkceProof();
2351
+ const popup = window.open("about:blank", "agents24-mcp", "popup,width=560,height=720");
2352
+ try {
2353
+ const start = await mcp.startAuthorization({
2354
+ runId,
2355
+ serverId,
2356
+ interruptId: part.interruptId,
2357
+ redirectUri: mcpRedirectUri,
2358
+ popupNonce,
2359
+ codeChallenge: proof.challenge
2360
+ });
2361
+ if (!start.callback_origin) throw new Error("MCP authorization did not return a callback origin.");
2362
+ if (popup) popup.location.replace(start.authorization_url);
2363
+ const completion = await waitForMcpOauthComplete({
2364
+ popup,
2365
+ callbackOrigin: start.callback_origin,
2366
+ popupNonce,
2367
+ requireConnectionId: Boolean(completeMcpAuthorization),
2368
+ serverId
2369
+ });
2370
+ if (completeMcpAuthorization) {
2371
+ await completeMcpAuthorization({ completion, codeVerifier: proof.verifier, serverId });
2372
+ } else {
2373
+ const redeemed = await mcp.redeemCallback({ code: completion.code, codeVerifier: proof.verifier });
2374
+ validateMcpCallbackResult(redeemed, serverId);
2375
+ }
2376
+ popup?.close();
2377
+ await controller.resumeHitl(part, "connect");
2378
+ } catch (error2) {
2379
+ popup?.close();
2380
+ throw error2;
2381
+ }
2382
+ }, [completeMcpAuthorization, controller, mcp, mcpRedirectUri]);
2383
+ const onHitlAction = React9.useCallback(async (part, action, comment) => {
2384
+ setLocalError(null);
2385
+ try {
2386
+ if (part.hitlKind === "mcp_auth" && action === "connect") await handleMcp(part);
2387
+ else await controller.resumeHitl(part, action, comment);
2388
+ } catch (error2) {
2389
+ setLocalError(error2);
2390
+ }
2391
+ }, [controller, handleMcp]);
2392
+ const error = localError || controller.error;
2393
+ const state = bootstrapLoading ? "loading" : error ? stateFromError(error) : controller.runState === "streaming" ? "streaming" : controller.runState === "reconnecting" ? "reconnecting" : controller.runState === "paused" ? "paused" : "ready";
2394
+ const connectionState = state === "offline" ? "offline" : state === "expired" ? "expired" : state === "reconnecting" ? "reconnecting" : bootstrapLoading ? "connecting" : "connected";
2395
+ const defaultMessageActions = (message) => message.role === "assistant" ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2396
+ AgentChatDefaultActions,
2397
+ {
2398
+ handlers: {
2399
+ copied: false,
2400
+ disliked: Boolean(disliked[message.id]),
2401
+ liked: Boolean(liked[message.id]),
2402
+ onCopy: async (content) => navigator.clipboard.writeText(content),
2403
+ onDislike: () => {
2404
+ setDisliked((current) => ({ ...current, [message.id]: !current[message.id] }));
2405
+ setLiked((current) => ({ ...current, [message.id]: false }));
2406
+ },
2407
+ onLike: () => {
2408
+ setLiked((current) => ({ ...current, [message.id]: !current[message.id] }));
2409
+ setDisliked((current) => ({ ...current, [message.id]: false }));
2410
+ },
2411
+ onRetry: () => controller.regenerate(message)
2412
+ },
2413
+ message
2414
+ }
2415
+ ) : null;
2416
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2417
+ AgentChatLayout,
2418
+ {
2419
+ ...layoutProps,
2420
+ activeThreadId: controller.activeThreadId,
2421
+ agent,
2422
+ allowAttachments: layoutProps.allowAttachments ?? capabilities.includes("attachments.write"),
2423
+ connectionState,
2424
+ errorMessage: error instanceof Error ? error.message : null,
2425
+ isLoadingOlder: controller.isLoadingOlder,
2426
+ isLoadingThreads: controller.isLoadingThreads,
2427
+ isRunning: controller.runState === "streaming" || controller.isSubmitting,
2428
+ messages: controller.messages,
2429
+ hasMoreThreads: controller.hasMoreThreads,
2430
+ modelId: selectedModelId,
2431
+ models: modelOverrides ?? bootstrapModels,
2432
+ onDeleteThread: deleteThread,
2433
+ onHitlAction,
2434
+ onLoadOlder: controller.hasOlderTurns ? controller.loadOlder : void 0,
2435
+ onLoadMoreThreads: controller.hasMoreThreads ? controller.loadMoreThreads : void 0,
2436
+ onModelChange: changeModel,
2437
+ onNewThread: controller.startNewThread,
2438
+ onOpenThread: controller.openThread,
2439
+ onRetryConnection: loadBootstrap,
2440
+ onStop: () => void controller.cancelRun(),
2441
+ onSubmit: submit,
2442
+ slots: { ...slots, messageActions: slots?.messageActions || defaultMessageActions },
2443
+ state,
2444
+ streamingMessageId: controller.streamingMessageId,
2445
+ threads: controller.threads.map((thread) => ({ id: thread.id, title: thread.title, status: thread.last_run_status || thread.status, updatedAt: thread.updated_at }))
2446
+ }
2447
+ );
2448
+ }
2449
+ function AgentChatApp({
2450
+ client,
2451
+ createAbortController,
2452
+ createId: createId2,
2453
+ storage,
2454
+ ...props
2455
+ }) {
2456
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2457
+ import_react2.Agents24Provider,
2458
+ {
2459
+ client,
2460
+ createAbortController: createAbortController || (() => new AbortController()),
2461
+ createId: createId2,
2462
+ storage,
2463
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(BoundAgentChatApp, { client, ...props })
2464
+ }
2465
+ );
2466
+ }
584
2467
  // Annotate the CommonJS export names for ESM import in node:
585
2468
  0 && (module.exports = {
2469
+ AgentChatApp,
2470
+ AgentChatLayout,
586
2471
  AgentChatShell,
2472
+ AgentChatStatePanel,
587
2473
  buildAgentChatOutlineItems,
588
2474
  getAgentChatShellScrollState
589
2475
  });