@agents24/chat-react 0.4.0 → 0.5.1

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 (132) hide show
  1. package/README.md +92 -18
  2. package/compatibility.json +10 -10
  3. package/dist/chunk-SSUO4EPD.js +189 -0
  4. package/dist/chunk-SSUO4EPD.js.map +1 -0
  5. package/dist/chunk-ZCNHLFRB.js +108 -0
  6. package/dist/chunk-ZCNHLFRB.js.map +1 -0
  7. package/dist/index.cjs +112 -95
  8. package/dist/index.cjs.map +1 -1
  9. package/dist/index.d.ts +0 -1
  10. package/dist/index.js +362 -68
  11. package/dist/index.js.map +1 -1
  12. package/dist/mcp-oauth.d.ts +13 -0
  13. package/dist/runtime/index.cjs +594 -0
  14. package/dist/runtime/index.cjs.map +1 -0
  15. package/dist/runtime/index.d.ts +2 -0
  16. package/dist/runtime/index.js +458 -0
  17. package/dist/runtime/index.js.map +1 -0
  18. package/dist/runtime/input-contract.d.ts +20 -0
  19. package/dist/runtime/use-agent-chat-runtime.d.ts +83 -0
  20. package/dist/scaffold/app.d.ts +30 -0
  21. package/dist/scaffold/components/chat/chat-composer.d.ts +21 -0
  22. package/dist/scaffold/components/chat/chat-message.d.ts +21 -0
  23. package/dist/scaffold/components/chat/chat-shell.d.ts +8 -0
  24. package/dist/scaffold/components/chat/collapsible-user-message.d.ts +3 -0
  25. package/dist/scaffold/components/chat/external-link-dialog.d.ts +2 -0
  26. package/dist/scaffold/components/chat/identity-control.d.ts +4 -0
  27. package/dist/scaffold/components/chat/message-parts.d.ts +12 -0
  28. package/dist/scaffold/components/chat/theme-menu.d.ts +1 -0
  29. package/dist/scaffold/components/chat/thread-sidebar.d.ts +16 -0
  30. package/dist/scaffold/components/chat/timeline-loading.d.ts +7 -0
  31. package/dist/scaffold/components/chat/turn-outline.d.ts +4 -0
  32. package/dist/scaffold/components/theme-provider.d.ts +20 -0
  33. package/dist/scaffold/components/ui/attachment.d.ts +24 -0
  34. package/dist/scaffold/components/ui/bubble.d.ts +17 -0
  35. package/dist/scaffold/components/ui/button-group.d.ts +11 -0
  36. package/dist/scaffold/components/ui/button.d.ts +10 -0
  37. package/dist/scaffold/components/ui/dialog.d.ts +17 -0
  38. package/dist/scaffold/components/ui/dropdown-menu.d.ts +29 -0
  39. package/dist/scaffold/components/ui/input.d.ts +3 -0
  40. package/dist/scaffold/components/ui/marker.d.ts +11 -0
  41. package/dist/scaffold/components/ui/message-scroller.d.ts +10 -0
  42. package/dist/scaffold/components/ui/message.d.ts +10 -0
  43. package/dist/scaffold/components/ui/popover.d.ts +6 -0
  44. package/dist/scaffold/components/ui/select.d.ts +8 -0
  45. package/dist/scaffold/components/ui/separator.d.ts +4 -0
  46. package/dist/scaffold/components/ui/sheet.d.ts +14 -0
  47. package/dist/scaffold/components/ui/sidebar.d.ts +69 -0
  48. package/dist/scaffold/components/ui/skeleton.d.ts +2 -0
  49. package/dist/scaffold/components/ui/textarea.d.ts +3 -0
  50. package/dist/scaffold/components/ui/tooltip.d.ts +15 -0
  51. package/dist/scaffold/hooks/use-mobile.d.ts +1 -0
  52. package/dist/scaffold/identity.d.ts +10 -0
  53. package/dist/scaffold/index.cjs +2270 -0
  54. package/dist/scaffold/index.cjs.map +1 -0
  55. package/dist/scaffold/index.d.ts +14 -0
  56. package/dist/scaffold/index.js +2244 -0
  57. package/dist/scaffold/index.js.map +1 -0
  58. package/dist/scaffold/lib/utils.d.ts +2 -0
  59. package/dist/styles.css +143 -298
  60. package/dist/types.d.ts +3 -13
  61. package/dist/ui/adapters.d.ts +1 -1
  62. package/dist/ui/agent-chat-actions.d.ts +12 -5
  63. package/dist/ui/agent-chat-composer.d.ts +2 -1
  64. package/dist/ui/agent-chat-context-compression-row.d.ts +5 -0
  65. package/dist/ui/agent-chat-context-status.d.ts +13 -0
  66. package/dist/ui/agent-chat-message.d.ts +7 -5
  67. package/dist/ui/agent-response-timeline.d.ts +29 -7
  68. package/dist/ui/ask-user-interaction-state.d.ts +20 -0
  69. package/dist/ui/ask-user-interaction.d.ts +8 -0
  70. package/dist/ui/index.cjs +1392 -272
  71. package/dist/ui/index.cjs.map +1 -1
  72. package/dist/ui/index.d.ts +5 -0
  73. package/dist/ui/index.js +2380 -65
  74. package/dist/ui/index.js.map +1 -1
  75. package/dist/ui/tool-sessions.d.ts +70 -0
  76. package/dist/ui/use-audio-recorder.d.ts +8 -0
  77. package/package.json +27 -12
  78. package/scaffold/components.json +25 -0
  79. package/scaffold/manifest.json +28 -0
  80. package/scaffold/src/app.tsx +71 -0
  81. package/scaffold/src/chat.css +54 -0
  82. package/scaffold/src/components/chat/chat-composer.tsx +237 -0
  83. package/scaffold/src/components/chat/chat-message.tsx +76 -0
  84. package/scaffold/src/components/chat/chat-shell.tsx +257 -0
  85. package/scaffold/src/components/chat/collapsible-user-message.tsx +37 -0
  86. package/scaffold/src/components/chat/external-link-dialog.tsx +48 -0
  87. package/scaffold/src/components/chat/identity-control.tsx +69 -0
  88. package/scaffold/src/components/chat/message-parts.tsx +83 -0
  89. package/scaffold/src/components/chat/theme-menu.tsx +79 -0
  90. package/scaffold/src/components/chat/thread-sidebar.tsx +114 -0
  91. package/scaffold/src/components/chat/timeline-loading.ts +17 -0
  92. package/scaffold/src/components/chat/turn-outline.tsx +73 -0
  93. package/scaffold/src/components/theme-provider.tsx +91 -0
  94. package/scaffold/src/components/ui/attachment.tsx +204 -0
  95. package/scaffold/src/components/ui/bubble.tsx +125 -0
  96. package/scaffold/src/components/ui/button-group.tsx +83 -0
  97. package/scaffold/src/components/ui/button.tsx +65 -0
  98. package/scaffold/src/components/ui/dialog.tsx +171 -0
  99. package/scaffold/src/components/ui/dropdown-menu.tsx +271 -0
  100. package/scaffold/src/components/ui/input.tsx +19 -0
  101. package/scaffold/src/components/ui/marker.tsx +69 -0
  102. package/scaffold/src/components/ui/message-scroller.tsx +129 -0
  103. package/scaffold/src/components/ui/message.tsx +92 -0
  104. package/scaffold/src/components/ui/popover.tsx +40 -0
  105. package/scaffold/src/components/ui/select.tsx +92 -0
  106. package/scaffold/src/components/ui/separator.tsx +28 -0
  107. package/scaffold/src/components/ui/sheet.tsx +150 -0
  108. package/scaffold/src/components/ui/sidebar.tsx +677 -0
  109. package/scaffold/src/components/ui/skeleton.tsx +13 -0
  110. package/scaffold/src/components/ui/textarea.tsx +18 -0
  111. package/scaffold/src/components/ui/tooltip.tsx +56 -0
  112. package/scaffold/src/hooks/use-mobile.ts +24 -0
  113. package/scaffold/src/identity.ts +17 -0
  114. package/scaffold/src/index.css +222 -0
  115. package/scaffold/src/index.tsx +39 -0
  116. package/scaffold/src/lib/utils.ts +6 -0
  117. package/scaffold/styles.css +3 -0
  118. package/dist/chunk-CHAU7FNW.js +0 -140
  119. package/dist/chunk-CHAU7FNW.js.map +0 -1
  120. package/dist/chunk-LI67M74F.js +0 -445
  121. package/dist/chunk-LI67M74F.js.map +0 -1
  122. package/dist/chunk-NLEOWTZG.js +0 -1327
  123. package/dist/chunk-NLEOWTZG.js.map +0 -1
  124. package/dist/renderers.d.ts +0 -10
  125. package/dist/templates/agent-chat-app.d.ts +0 -18
  126. package/dist/templates/agent-chat-layout.d.ts +0 -69
  127. package/dist/templates/agent-chat-shell.d.ts +0 -37
  128. package/dist/templates/index.cjs +0 -2476
  129. package/dist/templates/index.cjs.map +0 -1
  130. package/dist/templates/index.d.ts +0 -3
  131. package/dist/templates/index.js +0 -723
  132. package/dist/templates/index.js.map +0 -1
package/dist/ui/index.js CHANGED
@@ -1,48 +1,2283 @@
1
1
  import {
2
- AgentChatComposer,
3
- AgentChatDefaultActions,
4
- AgentChatMessage,
5
- AgentChatUserMessageContent,
6
- AgentResponseTimeline,
7
- Attachment,
8
- AttachmentAction,
9
- AttachmentActions,
10
- AttachmentContent,
11
- AttachmentDescription,
12
- AttachmentGroup,
13
- AttachmentMedia,
14
- AttachmentTitle,
15
- AttachmentTrigger,
16
- ChatAttachment,
17
- ChatAttachments,
18
- ChatMessage,
19
- ChatMessageAction,
20
- ChatMessageActions,
21
- ChatMessageContent,
22
- DefaultPartRow,
23
- Message,
24
- MessageAvatar,
25
- MessageContent,
26
- MessageFooter,
27
- MessageGroup,
28
- MessageHeader,
29
- MessageResponse,
30
- attachmentMediaVariants,
31
- attachmentVariants,
32
- cn,
33
- hitlActionLabel,
34
- hitlTitle,
35
- resolvedHitlLabel,
36
- shouldCollapseUserMessage
37
- } from "../chunk-NLEOWTZG.js";
38
- import "../chunk-CHAU7FNW.js";
2
+ getActiveStreamingTextPartId,
3
+ useStreamingText
4
+ } from "../chunk-SSUO4EPD.js";
5
+
6
+ // src/ui/agent-chat-composer.tsx
7
+ import * as React from "react";
8
+ import { ArrowUp, Paperclip, Plus, Square, X } from "lucide-react";
9
+
10
+ // src/ui/utils.ts
11
+ import { clsx } from "clsx";
12
+ import { twMerge } from "tailwind-merge";
13
+ function cn(...values) {
14
+ return twMerge(clsx(values));
15
+ }
16
+
17
+ // src/ui/agent-chat-composer.tsx
18
+ import { jsx, jsxs } from "react/jsx-runtime";
19
+ var createId = () => typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `file-${Date.now()}-${Math.random().toString(36).slice(2)}`;
20
+ function createComposerFile(file) {
21
+ return {
22
+ id: createId(),
23
+ type: "file",
24
+ url: URL.createObjectURL(file),
25
+ filename: file.name || "attachment",
26
+ mediaType: file.type || "application/octet-stream",
27
+ source: file
28
+ };
29
+ }
30
+ function revokeComposerFiles(files) {
31
+ files.forEach((file) => {
32
+ try {
33
+ URL.revokeObjectURL(file.url);
34
+ } catch {
35
+ }
36
+ });
37
+ }
38
+ function formatFileCount(files) {
39
+ if (files.length === 1) return files[0].filename;
40
+ return `${files.length} files`;
41
+ }
42
+ var pillButtonClass = "inline-flex size-8 shrink-0 items-center justify-center gap-2 rounded-lg border-0 bg-muted text-sm text-muted-foreground shadow-none transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-0 disabled:pointer-events-none disabled:opacity-50";
43
+ var submitButtonClass = "inline-flex size-9 shrink-0 items-center justify-center gap-2 rounded-lg border-0 bg-primary text-sm text-primary-foreground shadow-none transition-[background-color,color,opacity,transform] duration-200 hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-0 disabled:pointer-events-none disabled:opacity-50";
44
+ function AgentChatComposer({
45
+ accept,
46
+ allowAttachments = true,
47
+ attachmentLayout = "flow",
48
+ className,
49
+ disabled = false,
50
+ forceExpanded = false,
51
+ inputToolbarContent,
52
+ isRunning = false,
53
+ onAttachmentCountChange,
54
+ onStop,
55
+ onSubmit,
56
+ placeholder = "Message the agent",
57
+ textareaRef
58
+ }) {
59
+ const fileInputRef = React.useRef(null);
60
+ const filesRef = React.useRef([]);
61
+ const localTextareaRef = React.useRef(null);
62
+ const [text, setText] = React.useState("");
63
+ const [files, setFiles] = React.useState([]);
64
+ const [isSubmitting, setIsSubmitting] = React.useState(false);
65
+ const isExpanded = forceExpanded || text.includes("\n") || text.length > 62;
66
+ const setTextareaRef = React.useCallback(
67
+ (node) => {
68
+ localTextareaRef.current = node;
69
+ if (typeof textareaRef === "function") {
70
+ textareaRef(node);
71
+ } else if (textareaRef) {
72
+ textareaRef.current = node;
73
+ }
74
+ },
75
+ [textareaRef]
76
+ );
77
+ React.useEffect(() => {
78
+ filesRef.current = files;
79
+ onAttachmentCountChange?.(files.length);
80
+ }, [files, onAttachmentCountChange]);
81
+ React.useEffect(() => () => revokeComposerFiles(filesRef.current), []);
82
+ React.useLayoutEffect(() => {
83
+ const textarea = localTextareaRef.current;
84
+ if (!textarea) return;
85
+ if (isExpanded) {
86
+ textarea.style.height = "auto";
87
+ const scrollHeight = textarea.scrollHeight;
88
+ textarea.style.height = `${Math.min(scrollHeight, 224)}px`;
89
+ textarea.style.overflowY = scrollHeight >= 224 ? "auto" : "hidden";
90
+ return;
91
+ }
92
+ textarea.style.height = "";
93
+ textarea.style.overflowY = "hidden";
94
+ }, [isExpanded, text]);
95
+ const canSubmit = !disabled && !isSubmitting && !isRunning && (text.trim().length > 0 || files.length > 0);
96
+ const handleFilesChange = (event) => {
97
+ const selected = Array.from(event.target.files || []).map(createComposerFile);
98
+ if (selected.length > 0) {
99
+ setFiles((current) => [...current, ...selected]);
100
+ }
101
+ event.currentTarget.value = "";
102
+ };
103
+ const removeFile = (fileId) => {
104
+ setFiles((current) => {
105
+ const target = current.find((file) => file.id === fileId);
106
+ if (target) revokeComposerFiles([target]);
107
+ return current.filter((file) => file.id !== fileId);
108
+ });
109
+ };
110
+ const submit = async () => {
111
+ if (!canSubmit) return;
112
+ const submittedText = text.trim();
113
+ const submittedFiles = files;
114
+ setIsSubmitting(true);
115
+ try {
116
+ await onSubmit({ text: submittedText, files: submittedFiles });
117
+ setText("");
118
+ setFiles([]);
119
+ revokeComposerFiles(submittedFiles);
120
+ } catch {
121
+ } finally {
122
+ setIsSubmitting(false);
123
+ }
124
+ };
125
+ const handleKeyDown = (event) => {
126
+ if (event.key === "Enter" && !event.shiftKey) {
127
+ event.preventDefault();
128
+ void submit();
129
+ }
130
+ };
131
+ return /* @__PURE__ */ jsxs("div", { className: cn("a24-composer relative mx-auto w-full max-w-3xl overflow-visible", className), "data-agents24-chat-composer": "", children: [
132
+ files.length > 0 ? /* @__PURE__ */ jsx(
133
+ "div",
134
+ {
135
+ "data-testid": "agent-chat-composer-attachments",
136
+ "data-layout": attachmentLayout,
137
+ className: cn(
138
+ "a24-composer__attachments flex min-w-0 gap-2 py-1",
139
+ 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"
140
+ ),
141
+ children: files.map((file) => /* @__PURE__ */ jsxs(
142
+ "div",
143
+ {
144
+ 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-border bg-card px-1.5 text-sm font-medium text-card-foreground transition-colors hover:bg-accent",
145
+ children: [
146
+ /* @__PURE__ */ jsx("span", { className: "flex size-5 shrink-0 items-center justify-center rounded bg-background text-muted-foreground", children: /* @__PURE__ */ jsx(Paperclip, { className: "size-3" }) }),
147
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: file.filename }),
148
+ /* @__PURE__ */ jsx(
149
+ "button",
150
+ {
151
+ "aria-label": `Remove ${file.filename}`,
152
+ 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",
153
+ onClick: () => removeFile(file.id),
154
+ type: "button",
155
+ children: /* @__PURE__ */ jsx(X, { className: "size-3" })
156
+ }
157
+ )
158
+ ]
159
+ },
160
+ file.id
161
+ ))
162
+ }
163
+ ) : null,
164
+ /* @__PURE__ */ jsx(
165
+ "div",
166
+ {
167
+ "data-expanded": isExpanded ? "" : void 0,
168
+ className: cn(
169
+ "a24-composer__surface relative w-full border border-input bg-background shadow-sm",
170
+ isExpanded ? "rounded-xl p-2 pb-1.5" : "rounded-xl px-2 py-[3px]"
171
+ ),
172
+ children: /* @__PURE__ */ jsxs(
173
+ "div",
174
+ {
175
+ className: "a24-composer__grid w-full",
176
+ style: {
177
+ alignItems: "center",
178
+ display: "grid",
179
+ gridTemplateAreas: isExpanded ? `"textarea textarea textarea" "plus toolbar submit"` : `"plus textarea composer mic submit"`,
180
+ gridTemplateColumns: isExpanded ? "auto 1fr auto" : "auto 1fr auto auto auto",
181
+ gap: isExpanded ? "3px 10px" : "8px"
182
+ },
183
+ children: [
184
+ /* @__PURE__ */ jsxs("div", { style: { gridArea: "plus" }, className: "a24-composer__attach-slot flex shrink-0 items-center justify-center", children: [
185
+ /* @__PURE__ */ jsx(
186
+ "input",
187
+ {
188
+ ref: fileInputRef,
189
+ accept,
190
+ "aria-label": "Attach files",
191
+ className: "hidden",
192
+ multiple: true,
193
+ name: "attachments",
194
+ onChange: handleFilesChange,
195
+ type: "file"
196
+ }
197
+ ),
198
+ /* @__PURE__ */ jsx(
199
+ "button",
200
+ {
201
+ "aria-label": "Attach files",
202
+ className: cn("a24-composer__attach", pillButtonClass),
203
+ disabled: disabled || isSubmitting || isRunning || !allowAttachments,
204
+ onClick: () => fileInputRef.current?.click(),
205
+ title: "Attach files",
206
+ type: "button",
207
+ children: /* @__PURE__ */ jsx(Plus, { className: "size-4" })
208
+ }
209
+ )
210
+ ] }),
211
+ /* @__PURE__ */ jsx("div", { style: { gridArea: "textarea" }, className: "a24-composer__textarea-slot min-w-0 w-full", children: /* @__PURE__ */ jsx(
212
+ "textarea",
213
+ {
214
+ ref: setTextareaRef,
215
+ "aria-label": "Message",
216
+ autoComplete: "off",
217
+ className: cn(
218
+ "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-muted-foreground focus-visible:outline-none focus-visible:ring-0",
219
+ isExpanded ? "min-h-9 max-h-[224px] py-1" : "min-h-9 h-9 max-h-9 py-2"
220
+ ),
221
+ disabled: disabled || isSubmitting || isRunning,
222
+ name: "message",
223
+ onChange: (event) => setText(event.target.value),
224
+ onKeyDown: handleKeyDown,
225
+ placeholder,
226
+ rows: isExpanded ? 2 : 1,
227
+ value: text
228
+ }
229
+ ) }),
230
+ isExpanded ? /* @__PURE__ */ jsx("div", { style: { gridArea: "toolbar" }, className: "a24-composer__toolbar flex min-w-0 items-center gap-1.5", children: inputToolbarContent }) : null,
231
+ !isExpanded ? /* @__PURE__ */ jsx("div", { style: { gridArea: "composer" }, className: "flex min-w-0 items-center gap-1.5" }) : null,
232
+ !isExpanded ? /* @__PURE__ */ jsx("div", { style: { gridArea: "mic" }, className: "flex shrink-0 items-center justify-end" }) : null,
233
+ /* @__PURE__ */ jsx("div", { style: { gridArea: "submit" }, className: "a24-composer__submit-slot flex shrink-0 items-center justify-end", children: isRunning ? /* @__PURE__ */ jsx(
234
+ "button",
235
+ {
236
+ "aria-label": "Stop generating",
237
+ className: cn("a24-composer__submit", submitButtonClass),
238
+ disabled: disabled || !onStop,
239
+ onClick: onStop,
240
+ title: "Stop",
241
+ type: "button",
242
+ children: /* @__PURE__ */ jsx(Square, { className: "size-3.5 fill-current" })
243
+ }
244
+ ) : /* @__PURE__ */ jsx(
245
+ "button",
246
+ {
247
+ "aria-label": files.length > 0 ? `Send ${formatFileCount(files)}` : "Send message",
248
+ className: cn("a24-composer__submit", submitButtonClass, text.trim().length > 0 && "hover:scale-105"),
249
+ disabled: !canSubmit,
250
+ onClick: () => void submit(),
251
+ title: "Send",
252
+ type: "button",
253
+ children: /* @__PURE__ */ jsx(ArrowUp, { className: "size-4", strokeWidth: 2.5 })
254
+ }
255
+ ) })
256
+ ]
257
+ }
258
+ )
259
+ }
260
+ ),
261
+ !isExpanded && inputToolbarContent ? /* @__PURE__ */ jsx("div", { className: "a24-composer__toolbar a24-composer__toolbar--external mt-2 flex items-center gap-2 px-1", children: inputToolbarContent }) : null
262
+ ] });
263
+ }
264
+
265
+ // src/ui/agent-chat-actions.tsx
266
+ import * as React3 from "react";
267
+ import { Check, Copy, RefreshCcw, ThumbsDown, ThumbsUp } from "lucide-react";
268
+ import { Popover } from "radix-ui";
269
+
270
+ // src/ui/adapters.ts
271
+ import * as React2 from "react";
272
+
273
+ // src/ui/attachment.tsx
274
+ import { cva } from "class-variance-authority";
275
+ import { Slot } from "@radix-ui/react-slot";
276
+ import { jsx as jsx2 } from "react/jsx-runtime";
277
+ var attachmentVariants = cva(
278
+ "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",
279
+ {
280
+ variants: {
281
+ size: {
282
+ 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",
283
+ 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",
284
+ 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"
285
+ },
286
+ orientation: {
287
+ horizontal: "min-w-40 items-center",
288
+ vertical: "w-24 flex-col has-data-[slot=attachment-content]:w-30"
289
+ }
290
+ }
291
+ }
292
+ );
293
+ function Attachment({
294
+ className,
295
+ state = "done",
296
+ size = "default",
297
+ orientation = "horizontal",
298
+ ...props
299
+ }) {
300
+ return /* @__PURE__ */ jsx2(
301
+ "div",
302
+ {
303
+ "data-slot": "attachment",
304
+ "data-state": state,
305
+ "data-size": size,
306
+ "data-orientation": orientation,
307
+ className: cn(attachmentVariants({ size, orientation }), className),
308
+ ...props
309
+ }
310
+ );
311
+ }
312
+ var attachmentMediaVariants = cva(
313
+ "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",
314
+ {
315
+ variants: {
316
+ variant: {
317
+ icon: "",
318
+ 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"
319
+ }
320
+ },
321
+ defaultVariants: {
322
+ variant: "icon"
323
+ }
324
+ }
325
+ );
326
+ function AttachmentMedia({
327
+ className,
328
+ variant = "icon",
329
+ ...props
330
+ }) {
331
+ return /* @__PURE__ */ jsx2(
332
+ "div",
333
+ {
334
+ "data-slot": "attachment-media",
335
+ "data-variant": variant,
336
+ className: cn(attachmentMediaVariants({ variant }), className),
337
+ ...props
338
+ }
339
+ );
340
+ }
341
+ function AttachmentContent({
342
+ className,
343
+ ...props
344
+ }) {
345
+ return /* @__PURE__ */ jsx2(
346
+ "div",
347
+ {
348
+ "data-slot": "attachment-content",
349
+ className: cn(
350
+ "max-w-full min-w-0 flex-1 leading-tight group-data-[orientation=vertical]/attachment:px-1",
351
+ className
352
+ ),
353
+ ...props
354
+ }
355
+ );
356
+ }
357
+ function AttachmentTitle({
358
+ className,
359
+ ...props
360
+ }) {
361
+ return /* @__PURE__ */ jsx2(
362
+ "span",
363
+ {
364
+ "data-slot": "attachment-title",
365
+ className: cn(
366
+ "block max-w-full min-w-0 truncate font-medium group-data-[state=processing]/attachment:shimmer group-data-[state=uploading]/attachment:shimmer",
367
+ className
368
+ ),
369
+ ...props
370
+ }
371
+ );
372
+ }
373
+ function AttachmentDescription({
374
+ className,
375
+ ...props
376
+ }) {
377
+ return /* @__PURE__ */ jsx2(
378
+ "span",
379
+ {
380
+ "data-slot": "attachment-description",
381
+ className: cn(
382
+ "mt-0.5 block min-w-0 truncate text-xs text-muted-foreground group-data-[state=error]/attachment:text-destructive/80",
383
+ "max-w-full",
384
+ className
385
+ ),
386
+ ...props
387
+ }
388
+ );
389
+ }
390
+ function AttachmentActions({
391
+ className,
392
+ ...props
393
+ }) {
394
+ return /* @__PURE__ */ jsx2(
395
+ "div",
396
+ {
397
+ "data-slot": "attachment-actions",
398
+ className: cn(
399
+ "relative z-20 flex shrink-0 items-center group-data-[orientation=vertical]/attachment:absolute group-data-[orientation=vertical]/attachment:top-3 group-data-[orientation=vertical]/attachment:right-3 group-data-[orientation=vertical]/attachment:gap-1",
400
+ className
401
+ ),
402
+ ...props
403
+ }
404
+ );
405
+ }
406
+ function AttachmentAction({
407
+ className,
408
+ type = "button",
409
+ ...props
410
+ }) {
411
+ return /* @__PURE__ */ jsx2(
412
+ "button",
413
+ {
414
+ "data-slot": "attachment-action",
415
+ type,
416
+ className: cn(
417
+ "inline-flex size-6 shrink-0 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",
418
+ className
419
+ ),
420
+ ...props
421
+ }
422
+ );
423
+ }
424
+ function AttachmentTrigger({
425
+ className,
426
+ asChild = false,
427
+ type,
428
+ ...props
429
+ }) {
430
+ const Comp = asChild ? Slot : "button";
431
+ return /* @__PURE__ */ jsx2(
432
+ Comp,
433
+ {
434
+ "data-slot": "attachment-trigger",
435
+ type: asChild ? void 0 : type ?? "button",
436
+ className: cn("absolute inset-0 z-10 outline-none", className),
437
+ ...props
438
+ }
439
+ );
440
+ }
441
+ function AttachmentGroup({ className, ...props }) {
442
+ return /* @__PURE__ */ jsx2(
443
+ "div",
444
+ {
445
+ "data-slot": "attachment-group",
446
+ className: cn(
447
+ "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",
448
+ className
449
+ ),
450
+ ...props
451
+ }
452
+ );
453
+ }
454
+
455
+ // src/ui/message.tsx
456
+ import { jsx as jsx3 } from "react/jsx-runtime";
457
+ function MessageGroup({ className, ...props }) {
458
+ return /* @__PURE__ */ jsx3(
459
+ "div",
460
+ {
461
+ "data-slot": "message-group",
462
+ className: cn("flex min-w-0 flex-col gap-2", className),
463
+ ...props
464
+ }
465
+ );
466
+ }
467
+ function Message({
468
+ className,
469
+ align = "start",
470
+ ...props
471
+ }) {
472
+ return /* @__PURE__ */ jsx3(
473
+ "div",
474
+ {
475
+ "data-slot": "message",
476
+ "data-align": align,
477
+ className: cn(
478
+ "group/message relative flex w-full min-w-0 gap-2 text-sm data-[align=end]:flex-row-reverse",
479
+ className
480
+ ),
481
+ ...props
482
+ }
483
+ );
484
+ }
485
+ function MessageAvatar({ className, ...props }) {
486
+ return /* @__PURE__ */ jsx3(
487
+ "div",
488
+ {
489
+ "data-slot": "message-avatar",
490
+ className: cn(
491
+ "flex w-fit min-w-8 shrink-0 items-center justify-center self-end overflow-hidden rounded-full bg-muted group-has-data-[slot=message-footer]/message:-translate-y-8",
492
+ className
493
+ ),
494
+ ...props
495
+ }
496
+ );
497
+ }
498
+ function MessageContent({ className, ...props }) {
499
+ return /* @__PURE__ */ jsx3(
500
+ "div",
501
+ {
502
+ "data-slot": "message-content",
503
+ className: cn(
504
+ "flex w-full min-w-0 flex-col gap-2.5 wrap-break-word group-data-[align=end]/message:*:data-slot:self-end",
505
+ className
506
+ ),
507
+ ...props
508
+ }
509
+ );
510
+ }
511
+ function MessageHeader({ className, ...props }) {
512
+ return /* @__PURE__ */ jsx3(
513
+ "div",
514
+ {
515
+ "data-slot": "message-header",
516
+ className: cn(
517
+ "flex max-w-full min-w-0 items-center px-3 text-xs font-medium text-muted-foreground group-has-data-[variant=ghost]/message:px-0",
518
+ className
519
+ ),
520
+ ...props
521
+ }
522
+ );
523
+ }
524
+ function MessageFooter({ className, ...props }) {
525
+ return /* @__PURE__ */ jsx3(
526
+ "div",
527
+ {
528
+ "data-slot": "message-footer",
529
+ className: cn(
530
+ "flex max-w-full min-w-0 items-center px-3 text-xs font-medium text-muted-foreground group-has-data-[variant=ghost]/message:px-0 group-data-[align=end]/message:justify-end",
531
+ className
532
+ ),
533
+ ...props
534
+ }
535
+ );
536
+ }
537
+
538
+ // src/ui/adapters.ts
539
+ var ChatMessageRoleContext = React2.createContext("assistant");
540
+ function ChatMessage(input) {
541
+ const { className, from, align, ...props } = input;
542
+ const isUser = from === "user";
543
+ const isRtl = props.dir === "rtl";
544
+ const resolvedAlign = align || "start";
545
+ const messageProps = Object.assign({}, props);
546
+ messageProps.align = resolvedAlign;
547
+ messageProps.className = cn(
548
+ "a24-chat-message group flex w-full flex-col gap-2",
549
+ isUser ? cn("a24-chat-message--user is-user max-w-[80%] justify-end", isRtl ? "mr-auto" : "ml-auto") : "a24-chat-message--assistant is-assistant max-w-full",
550
+ className
551
+ );
552
+ return React2.createElement(
553
+ ChatMessageRoleContext.Provider,
554
+ { value: from },
555
+ React2.createElement(Message, messageProps)
556
+ );
557
+ }
558
+ function ChatMessageContent({
559
+ className,
560
+ from,
561
+ ...props
562
+ }) {
563
+ const inheritedRole = React2.useContext(ChatMessageRoleContext);
564
+ const isUser = (from ?? inheritedRole) === "user";
565
+ return React2.createElement("div", {
566
+ className: cn(
567
+ "a24-chat-message-content flex max-w-full min-w-0 flex-col gap-2 text-sm",
568
+ isUser ? "a24-chat-message-content--user is-user:dark w-fit overflow-visible rounded-lg bg-secondary px-4 py-3 text-foreground" : "a24-chat-message-content--assistant w-full overflow-visible text-foreground",
569
+ className
570
+ ),
571
+ "data-slot": "bubble-content",
572
+ ...props
573
+ });
574
+ }
575
+ function ChatMessageActions({
576
+ className,
577
+ ...props
578
+ }) {
579
+ return React2.createElement("div", {
580
+ className: cn("flex items-center gap-1", className),
581
+ "data-slot": "message-actions",
582
+ ...props
583
+ });
584
+ }
585
+ function ChatMessageAction({
586
+ children,
587
+ className,
588
+ label,
589
+ tooltip,
590
+ type = "button",
591
+ ...props
592
+ }) {
593
+ const tooltipLabel = typeof tooltip === "string" ? tooltip : void 0;
594
+ return React2.createElement(
595
+ "button",
596
+ {
597
+ "aria-label": label || tooltipLabel,
598
+ className: cn(
599
+ "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",
600
+ className
601
+ ),
602
+ title: tooltipLabel,
603
+ type,
604
+ ...props
605
+ },
606
+ children,
607
+ React2.createElement(
608
+ "span",
609
+ { className: "sr-only" },
610
+ label || tooltipLabel
611
+ )
612
+ );
613
+ }
614
+ function ChatAttachment({
615
+ data,
616
+ onRemove,
617
+ orientation,
618
+ ...props
619
+ }) {
620
+ const mediaType = data.mediaType || data.contentType || String(data.type || "application/octet-stream");
621
+ const filename = data.filename || data.name || "Attachment";
622
+ const isImage = mediaType.startsWith("image/") && Boolean(data.url);
623
+ return React2.createElement(
624
+ Attachment,
625
+ {
626
+ orientation: orientation || (isImage ? "vertical" : "horizontal"),
627
+ ...props
628
+ },
629
+ React2.createElement(
630
+ AttachmentMedia,
631
+ { variant: isImage ? "image" : "icon" },
632
+ isImage ? React2.createElement("img", {
633
+ alt: filename,
634
+ className: "size-full object-cover",
635
+ src: data.url
636
+ }) : React2.createElement(
637
+ "span",
638
+ { "aria-hidden": "true", className: "text-sm" },
639
+ filename.slice(0, 1).toUpperCase()
640
+ )
641
+ ),
642
+ React2.createElement(
643
+ AttachmentContent,
644
+ null,
645
+ React2.createElement(AttachmentTitle, null, filename),
646
+ React2.createElement(AttachmentDescription, null, mediaType)
647
+ ),
648
+ onRemove ? React2.createElement(
649
+ "button",
650
+ {
651
+ "aria-label": "Remove attachment",
652
+ 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",
653
+ onClick: (event) => {
654
+ event.stopPropagation();
655
+ onRemove();
656
+ },
657
+ type: "button"
658
+ },
659
+ React2.createElement("span", { "aria-hidden": "true" }, "x")
660
+ ) : null
661
+ );
662
+ }
663
+ function ChatAttachments({
664
+ children,
665
+ className,
666
+ ...props
667
+ }) {
668
+ if (!children) return null;
669
+ return React2.createElement(
670
+ AttachmentGroup,
671
+ {
672
+ className: cn(
673
+ props.dir === "rtl" ? "mr-auto" : "ml-auto",
674
+ "w-fit flex-wrap overflow-visible",
675
+ className
676
+ ),
677
+ ...props
678
+ },
679
+ children
680
+ );
681
+ }
682
+
683
+ // src/ui/agent-chat-actions.tsx
684
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
685
+ var FEEDBACK_REASONS = [
686
+ ["incorrect", "Incorrect"],
687
+ ["irrelevant", "Irrelevant"],
688
+ ["incomplete", "Incomplete"],
689
+ ["unsafe", "Unsafe"],
690
+ ["citation_issue", "Citation issue"],
691
+ ["other", "Other"]
692
+ ];
693
+ function AgentChatDefaultActions({
694
+ additionalActions,
695
+ dir,
696
+ handlers,
697
+ message
698
+ }) {
699
+ const [feedbackOpen, setFeedbackOpen] = React3.useState(false);
700
+ const [feedbackTrigger, setFeedbackTrigger] = React3.useState(null);
701
+ const [reason, setReason] = React3.useState(handlers?.feedback?.reason ?? null);
702
+ const [comment, setComment] = React3.useState(handlers?.feedback?.comment ?? "");
703
+ React3.useEffect(() => {
704
+ setReason(handlers?.feedback?.reason ?? null);
705
+ setComment(handlers?.feedback?.comment ?? "");
706
+ }, [handlers?.feedback?.comment, handlers?.feedback?.reason]);
707
+ if (!handlers || message.role !== "assistant" || message.isFinal === false) return null;
708
+ const canWriteFeedback = Boolean(message.runId && handlers.onSetFeedback);
709
+ return /* @__PURE__ */ jsxs2(ChatMessageActions, { children: [
710
+ handlers.onRetry ? /* @__PURE__ */ jsx4(
711
+ ChatMessageAction,
712
+ {
713
+ label: "Retry",
714
+ onClick: () => void handlers.onRetry?.(message),
715
+ tooltip: "Regenerate response",
716
+ children: /* @__PURE__ */ jsx4(RefreshCcw, { className: "size-4" })
717
+ }
718
+ ) : null,
719
+ canWriteFeedback ? /* @__PURE__ */ jsx4(
720
+ ChatMessageAction,
721
+ {
722
+ label: "Like",
723
+ onClick: () => void handlers.onSetFeedback?.(message, {
724
+ rating: handlers.feedback?.rating === "like" ? null : "like"
725
+ }),
726
+ tooltip: "Like this response",
727
+ children: /* @__PURE__ */ jsx4(ThumbsUp, { className: "size-4", fill: handlers.feedback?.rating === "like" ? "currentColor" : "none" })
728
+ }
729
+ ) : null,
730
+ canWriteFeedback ? /* @__PURE__ */ jsxs2(Popover.Root, { onOpenChange: setFeedbackOpen, open: feedbackOpen, children: [
731
+ /* @__PURE__ */ jsx4(Popover.Trigger, { asChild: true, children: /* @__PURE__ */ jsx4(
732
+ ChatMessageAction,
733
+ {
734
+ label: "Dislike",
735
+ onClick: () => {
736
+ if (handlers.feedback?.rating !== "dislike") {
737
+ void handlers.onSetFeedback?.(message, { rating: "dislike" });
738
+ }
739
+ setFeedbackOpen(true);
740
+ },
741
+ ref: setFeedbackTrigger,
742
+ tooltip: "Dislike this response",
743
+ children: /* @__PURE__ */ jsx4(ThumbsDown, { className: "size-4", fill: handlers.feedback?.rating === "dislike" ? "currentColor" : "none" })
744
+ }
745
+ ) }),
746
+ /* @__PURE__ */ jsx4(
747
+ Popover.Portal,
748
+ {
749
+ container: feedbackTrigger?.closest("[data-agents24-theme-root]") ?? void 0,
750
+ children: /* @__PURE__ */ jsxs2(
751
+ Popover.Content,
752
+ {
753
+ align: "end",
754
+ "aria-label": "Dislike details",
755
+ className: "z-50 w-80 rounded-lg border border-border bg-popover p-4 text-popover-foreground shadow-md outline-none",
756
+ dir,
757
+ side: "top",
758
+ sideOffset: 8,
759
+ children: [
760
+ /* @__PURE__ */ jsx4("p", { className: "text-sm font-medium", children: "What could be better?" }),
761
+ /* @__PURE__ */ jsx4("div", { className: "mt-3 flex flex-wrap gap-1.5", children: FEEDBACK_REASONS.map(([value, label]) => /* @__PURE__ */ jsx4(
762
+ "button",
763
+ {
764
+ "aria-pressed": reason === value,
765
+ className: "rounded-md border border-border px-2.5 py-1.5 text-xs font-medium transition-colors hover:bg-muted aria-pressed:border-transparent aria-pressed:bg-secondary",
766
+ onClick: () => setReason(reason === value ? null : value),
767
+ type: "button",
768
+ children: label
769
+ },
770
+ value
771
+ )) }),
772
+ /* @__PURE__ */ jsx4(
773
+ "textarea",
774
+ {
775
+ "aria-label": "Optional feedback comment",
776
+ className: "mt-3 min-h-20 w-full resize-none rounded-md border border-input bg-background px-3 py-2 text-sm outline-none placeholder:text-muted-foreground focus-visible:ring-2 focus-visible:ring-ring",
777
+ maxLength: 1e3,
778
+ onChange: (event) => setComment(event.target.value),
779
+ placeholder: "Add a comment (optional)",
780
+ value: comment
781
+ }
782
+ ),
783
+ /* @__PURE__ */ jsxs2("div", { className: "mt-3 flex items-center justify-between gap-2", children: [
784
+ /* @__PURE__ */ jsx4(
785
+ "button",
786
+ {
787
+ className: "rounded-md px-3 py-2 text-sm font-medium hover:bg-muted",
788
+ onClick: () => {
789
+ void Promise.resolve(handlers.onSetFeedback?.(message, { rating: null })).then(() => setFeedbackOpen(false)).catch(() => void 0);
790
+ },
791
+ type: "button",
792
+ children: "Remove feedback"
793
+ }
794
+ ),
795
+ /* @__PURE__ */ jsx4(
796
+ "button",
797
+ {
798
+ className: "rounded-md bg-primary px-3 py-2 text-sm font-medium text-primary-foreground hover:opacity-90",
799
+ onClick: () => {
800
+ void Promise.resolve(handlers.onSetFeedback?.(message, { rating: "dislike", reason, comment })).then(() => setFeedbackOpen(false)).catch(() => void 0);
801
+ },
802
+ type: "button",
803
+ children: "Save"
804
+ }
805
+ )
806
+ ] })
807
+ ]
808
+ }
809
+ )
810
+ }
811
+ )
812
+ ] }) : null,
813
+ additionalActions,
814
+ handlers.onCopy ? /* @__PURE__ */ jsx4(
815
+ ChatMessageAction,
816
+ {
817
+ label: "Copy",
818
+ onClick: () => handlers.onCopy?.(message.content || "", message.id),
819
+ tooltip: handlers.copied ? "Copied!" : "Copy to clipboard",
820
+ children: handlers.copied ? /* @__PURE__ */ jsx4(Check, { className: "size-4 text-green-600" }) : /* @__PURE__ */ jsx4(Copy, { className: "size-4" })
821
+ }
822
+ ) : null
823
+ ] });
824
+ }
825
+
826
+ // src/ui/agent-chat-message.tsx
827
+ import * as React8 from "react";
828
+ import { ChevronDown as ChevronDown3, ChevronUp } from "lucide-react";
829
+
830
+ // src/ui/agent-response-timeline.tsx
831
+ import * as React7 from "react";
832
+
833
+ // src/ui/ask-user-interaction.tsx
834
+ import * as React4 from "react";
835
+ import {
836
+ ChevronDown,
837
+ ChevronLeft,
838
+ ChevronRight,
839
+ Pencil
840
+ } from "lucide-react";
841
+
842
+ // src/ui/ask-user-interaction-state.ts
843
+ function emptyAskUserDrafts(questions) {
844
+ return Object.fromEntries(
845
+ questions.map((question) => [
846
+ question.id,
847
+ { customText: "", selectedOptionIds: [] }
848
+ ])
849
+ );
850
+ }
851
+ function askUserDraftHasAnswer(draft) {
852
+ return Boolean(
853
+ draft && (draft.selectedOptionIds.length > 0 || draft.customText.trim().length > 0)
854
+ );
855
+ }
856
+ function selectAskUserOption({
857
+ draft,
858
+ drafts,
859
+ optionId,
860
+ question
861
+ }) {
862
+ const selectedOptionIds = question.multiSelect ? draft.selectedOptionIds.includes(optionId) ? draft.selectedOptionIds.filter((id) => id !== optionId) : [...draft.selectedOptionIds, optionId] : [optionId];
863
+ return {
864
+ ...drafts,
865
+ [question.id]: {
866
+ customText: "",
867
+ selectedOptionIds
868
+ }
869
+ };
870
+ }
871
+ function updateAskUserCustomText({
872
+ customText,
873
+ drafts,
874
+ question
875
+ }) {
876
+ return {
877
+ ...drafts,
878
+ [question.id]: {
879
+ customText,
880
+ selectedOptionIds: question.multiSelect ? drafts[question.id]?.selectedOptionIds || [] : []
881
+ }
882
+ };
883
+ }
884
+ function askUserAnswersFromDrafts(questions, drafts) {
885
+ return questions.map((question) => {
886
+ const answer = drafts[question.id];
887
+ return {
888
+ questionId: question.id,
889
+ selectedOptionIds: answer?.selectedOptionIds || [],
890
+ ...answer?.customText.trim() ? { customText: answer.customText.trim() } : {}
891
+ };
892
+ });
893
+ }
894
+
895
+ // src/ui/ask-user-interaction.tsx
896
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
897
+ function resolvedAnswerText(question, answer) {
898
+ if (!answer) return ["No response recorded"];
899
+ const labels = answer.selectedOptionIds.flatMap((optionId) => {
900
+ const option = question.options.find((item) => item.id === optionId);
901
+ return option ? [option.label] : [];
902
+ });
903
+ if (answer.customText) labels.push(answer.customText);
904
+ return labels.length ? labels : ["No response recorded"];
905
+ }
906
+ function ResolvedQuestions({
907
+ buttonRef,
908
+ part
909
+ }) {
910
+ const [open, setOpen] = React4.useState(false);
911
+ const count = part.questions.length;
912
+ const answers = new Map(
913
+ (part.resolution?.answers || []).map((answer) => [
914
+ answer.questionId,
915
+ answer
916
+ ])
917
+ );
918
+ return /* @__PURE__ */ jsxs3("div", { className: "a24-ask-user a24-ask-user--resolved w-full", children: [
919
+ /* @__PURE__ */ jsxs3(
920
+ "button",
921
+ {
922
+ "aria-expanded": open,
923
+ className: "group flex min-w-0 items-center gap-1.5 rounded-md px-0 py-0.5 text-start text-sm text-muted-foreground transition-colors hover:text-foreground motion-reduce:transition-none",
924
+ onClick: () => setOpen((value) => !value),
925
+ ref: buttonRef,
926
+ type: "button",
927
+ children: [
928
+ /* @__PURE__ */ jsxs3("span", { children: [
929
+ "Asked ",
930
+ count,
931
+ " question",
932
+ count === 1 ? "" : "s"
933
+ ] }),
934
+ open ? /* @__PURE__ */ jsx5(ChevronDown, { "aria-hidden": "true", className: "size-3.5" }) : /* @__PURE__ */ jsx5(
935
+ ChevronRight,
936
+ {
937
+ "aria-hidden": "true",
938
+ className: "size-3.5 opacity-70 rtl:rotate-180"
939
+ }
940
+ )
941
+ ]
942
+ }
943
+ ),
944
+ open ? /* @__PURE__ */ jsx5("div", { className: "mt-2 space-y-3 ps-4", children: part.questions.map((question) => /* @__PURE__ */ jsxs3("div", { className: "min-w-0", children: [
945
+ /* @__PURE__ */ jsx5("div", { className: "text-sm font-medium text-foreground", children: question.question }),
946
+ /* @__PURE__ */ jsx5("div", { className: "mt-1 space-y-1 text-sm text-muted-foreground", children: resolvedAnswerText(
947
+ question,
948
+ answers.get(question.id)
949
+ ).map((value, index) => /* @__PURE__ */ jsx5(
950
+ "div",
951
+ {
952
+ className: "whitespace-pre-wrap break-words",
953
+ children: value
954
+ },
955
+ `${question.id}:${index}`
956
+ )) })
957
+ ] }, question.id)) }) : null
958
+ ] });
959
+ }
960
+ function AskUserInteraction({
961
+ error,
962
+ isBusy = false,
963
+ onSubmit,
964
+ part
965
+ }) {
966
+ const questions = part.questions;
967
+ const [step, setStep] = React4.useState(0);
968
+ const [drafts, setDrafts] = React4.useState(
969
+ () => emptyAskUserDrafts(questions)
970
+ );
971
+ const headingRef = React4.useRef(null);
972
+ const questionsRef = React4.useRef(questions);
973
+ const resolvedButtonRef = React4.useRef(null);
974
+ const wasResolvedRef = React4.useRef(part.status !== "pending");
975
+ questionsRef.current = questions;
976
+ React4.useEffect(() => {
977
+ setStep(0);
978
+ setDrafts(emptyAskUserDrafts(questionsRef.current));
979
+ }, [part.interruptId]);
980
+ React4.useEffect(() => {
981
+ headingRef.current?.focus();
982
+ }, [step]);
983
+ React4.useEffect(() => {
984
+ const isResolved = part.status !== "pending";
985
+ if (isResolved && !wasResolvedRef.current) {
986
+ resolvedButtonRef.current?.focus();
987
+ }
988
+ wasResolvedRef.current = isResolved;
989
+ }, [part.status]);
990
+ if (part.status !== "pending") {
991
+ return /* @__PURE__ */ jsx5(ResolvedQuestions, { buttonRef: resolvedButtonRef, part });
992
+ }
993
+ const question = questions[step];
994
+ if (!question) {
995
+ return /* @__PURE__ */ jsx5("div", { className: "text-sm text-destructive", role: "alert", children: "This question request is unavailable." });
996
+ }
997
+ const draft = drafts[question.id] || {
998
+ customText: "",
999
+ selectedOptionIds: []
1000
+ };
1001
+ const isLastQuestion = step === questions.length - 1;
1002
+ const canMoveForward = askUserDraftHasAnswer(draft);
1003
+ const completeStep = (nextDrafts) => {
1004
+ if (isLastQuestion) {
1005
+ onSubmit?.(askUserAnswersFromDrafts(questions, nextDrafts));
1006
+ return;
1007
+ }
1008
+ setStep((current) => Math.min(current + 1, questions.length - 1));
1009
+ };
1010
+ const selectOption = (optionId) => {
1011
+ const nextDrafts = selectAskUserOption({
1012
+ draft,
1013
+ drafts,
1014
+ optionId,
1015
+ question
1016
+ });
1017
+ setDrafts(nextDrafts);
1018
+ if (!question.multiSelect) completeStep(nextDrafts);
1019
+ };
1020
+ const updateCustomText = (customText) => {
1021
+ setDrafts(
1022
+ (current) => updateAskUserCustomText({
1023
+ customText,
1024
+ drafts: current,
1025
+ question
1026
+ })
1027
+ );
1028
+ };
1029
+ return /* @__PURE__ */ jsxs3("div", { className: "a24-ask-user space-y-3 rounded-xl border border-border/50 bg-background p-4 shadow-sm", children: [
1030
+ /* @__PURE__ */ jsxs3("div", { className: "flex min-w-0 items-start justify-between gap-4", children: [
1031
+ /* @__PURE__ */ jsx5(
1032
+ "h3",
1033
+ {
1034
+ className: "min-w-0 flex-1 text-base font-medium leading-6 text-foreground outline-none",
1035
+ ref: headingRef,
1036
+ tabIndex: -1,
1037
+ children: question.question
1038
+ }
1039
+ ),
1040
+ /* @__PURE__ */ jsxs3("div", { className: "flex shrink-0 items-center gap-1 text-muted-foreground", children: [
1041
+ /* @__PURE__ */ jsx5(
1042
+ "button",
1043
+ {
1044
+ "aria-label": "Previous question",
1045
+ className: "flex size-8 items-center justify-center rounded-full transition-colors hover:bg-muted hover:text-foreground disabled:pointer-events-none disabled:opacity-30",
1046
+ disabled: isBusy || step === 0,
1047
+ onClick: () => setStep((current) => Math.max(0, current - 1)),
1048
+ type: "button",
1049
+ children: /* @__PURE__ */ jsx5(
1050
+ ChevronLeft,
1051
+ {
1052
+ "aria-hidden": "true",
1053
+ className: "size-4 rtl:rotate-180"
1054
+ }
1055
+ )
1056
+ }
1057
+ ),
1058
+ /* @__PURE__ */ jsxs3(
1059
+ "span",
1060
+ {
1061
+ "aria-label": `Question ${step + 1} of ${questions.length}`,
1062
+ className: "min-w-12 text-center text-sm tabular-nums",
1063
+ children: [
1064
+ step + 1,
1065
+ " / ",
1066
+ questions.length
1067
+ ]
1068
+ }
1069
+ ),
1070
+ /* @__PURE__ */ jsx5(
1071
+ "button",
1072
+ {
1073
+ "aria-label": isLastQuestion ? "Submit answers" : "Next question",
1074
+ className: "flex size-8 items-center justify-center rounded-full transition-colors hover:bg-muted hover:text-foreground disabled:pointer-events-none disabled:opacity-30",
1075
+ disabled: isBusy || !onSubmit || !canMoveForward,
1076
+ onClick: () => completeStep(drafts),
1077
+ type: "button",
1078
+ children: /* @__PURE__ */ jsx5(
1079
+ ChevronRight,
1080
+ {
1081
+ "aria-hidden": "true",
1082
+ className: "size-4 rtl:rotate-180"
1083
+ }
1084
+ )
1085
+ }
1086
+ )
1087
+ ] })
1088
+ ] }),
1089
+ /* @__PURE__ */ jsxs3("div", { className: "space-y-1", children: [
1090
+ question.options.map((option, index) => {
1091
+ const selected = draft.selectedOptionIds.includes(option.id);
1092
+ return /* @__PURE__ */ jsxs3(
1093
+ "button",
1094
+ {
1095
+ "aria-pressed": selected,
1096
+ className: cn(
1097
+ "group flex min-h-11 w-full items-center gap-3 rounded-xl px-2.5 py-2 text-start text-sm transition-colors motion-reduce:transition-none",
1098
+ selected ? "bg-muted" : "hover:bg-muted/70"
1099
+ ),
1100
+ disabled: isBusy || !onSubmit,
1101
+ onClick: () => selectOption(option.id),
1102
+ type: "button",
1103
+ children: [
1104
+ /* @__PURE__ */ jsx5(
1105
+ "span",
1106
+ {
1107
+ className: cn(
1108
+ "flex size-8 shrink-0 items-center justify-center rounded-full border text-sm tabular-nums transition-colors",
1109
+ selected ? "border-border bg-background text-foreground" : "border-border/70 bg-muted/60 text-muted-foreground"
1110
+ ),
1111
+ children: index + 1
1112
+ }
1113
+ ),
1114
+ /* @__PURE__ */ jsx5("span", { className: "min-w-0 flex-1 font-medium text-foreground", children: option.label }),
1115
+ /* @__PURE__ */ jsx5(
1116
+ ChevronRight,
1117
+ {
1118
+ "aria-hidden": "true",
1119
+ className: "size-4 shrink-0 text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100 group-focus-visible:opacity-100 rtl:rotate-180"
1120
+ }
1121
+ )
1122
+ ]
1123
+ },
1124
+ option.id
1125
+ );
1126
+ }),
1127
+ /* @__PURE__ */ jsxs3("div", { className: "flex min-h-11 items-center gap-2 rounded-xl bg-muted px-2.5 py-1.5 focus-within:ring-1 focus-within:ring-ring", children: [
1128
+ /* @__PURE__ */ jsx5("span", { className: "flex size-8 shrink-0 items-center justify-center rounded-full border border-border/70 bg-background/70 text-muted-foreground", children: /* @__PURE__ */ jsx5(Pencil, { "aria-hidden": "true", className: "size-4" }) }),
1129
+ /* @__PURE__ */ jsx5(
1130
+ "input",
1131
+ {
1132
+ "aria-label": "Other response",
1133
+ autoComplete: "off",
1134
+ className: "h-8 min-w-0 flex-1 bg-transparent px-1 text-sm text-foreground outline-none placeholder:text-muted-foreground",
1135
+ disabled: isBusy || !onSubmit,
1136
+ maxLength: 2e3,
1137
+ onChange: (event) => updateCustomText(event.target.value),
1138
+ onKeyDown: (event) => {
1139
+ if (event.key === "Enter" && !event.nativeEvent.isComposing && draft.customText.trim()) {
1140
+ event.preventDefault();
1141
+ completeStep(drafts);
1142
+ }
1143
+ },
1144
+ placeholder: "Other\u2026",
1145
+ value: draft.customText
1146
+ }
1147
+ ),
1148
+ draft.customText.trim() ? /* @__PURE__ */ jsx5(
1149
+ "button",
1150
+ {
1151
+ className: "h-8 shrink-0 rounded-full bg-foreground px-4 text-sm font-medium text-background transition-colors hover:opacity-85 disabled:opacity-50",
1152
+ disabled: isBusy || !onSubmit,
1153
+ onClick: () => completeStep(drafts),
1154
+ type: "button",
1155
+ children: isLastQuestion ? "Submit" : "Next"
1156
+ }
1157
+ ) : null
1158
+ ] })
1159
+ ] }),
1160
+ error ? /* @__PURE__ */ jsx5("div", { className: "text-sm text-destructive", role: "alert", children: error }) : null
1161
+ ] });
1162
+ }
1163
+
1164
+ // src/ui/agent-chat-context-compression-row.tsx
1165
+ import { jsx as jsx6 } from "react/jsx-runtime";
1166
+ function compressionPresentation(part) {
1167
+ if (part.status === "failed") {
1168
+ return { label: "Could not compact message history", status: "failed" };
1169
+ }
1170
+ if (part.status === "completed") {
1171
+ return { label: "Compacted message history", status: "completed" };
1172
+ }
1173
+ return { label: "Compacting message history", status: "running" };
1174
+ }
1175
+ function AgentChatContextCompressionRow({
1176
+ className,
1177
+ part
1178
+ }) {
1179
+ const presentation = compressionPresentation(part);
1180
+ return /* @__PURE__ */ jsx6(
1181
+ "div",
1182
+ {
1183
+ "aria-live": "polite",
1184
+ className: cn("a24-context-compression-row", className),
1185
+ "data-agents24-context-compression": "",
1186
+ "data-status": part.status,
1187
+ role: "status",
1188
+ children: /* @__PURE__ */ jsx6("div", { className: "a24-tool-trigger-row", children: /* @__PURE__ */ jsx6("div", { className: cn(
1189
+ "a24-tool-trigger a24-tool-trigger--root a24-tool-trigger--without-orb",
1190
+ presentation.status === "failed" && "a24-tool-trigger--error",
1191
+ presentation.status === "running" && "a24-tool-trigger--active"
1192
+ ), children: /* @__PURE__ */ jsx6("span", { className: cn(
1193
+ "a24-tool-trigger__label",
1194
+ presentation.status === "running" && "a24-tool-shimmer"
1195
+ ), children: presentation.label }) }) })
1196
+ }
1197
+ );
1198
+ }
1199
+
1200
+ // src/ui/message-response.tsx
1201
+ import * as React5 from "react";
1202
+ import { defaultRehypePlugins, Streamdown } from "streamdown";
1203
+
1204
+ // src/active-content.ts
1205
+ var CHAT_MARKDOWN_HARDEN_OPTIONS = Object.freeze({
1206
+ allowDataImages: false,
1207
+ allowedImagePrefixes: [],
1208
+ allowedLinkPrefixes: ["*"],
1209
+ allowedProtocols: [],
1210
+ defaultOrigin: void 0,
1211
+ imageBlockPolicy: "text-only",
1212
+ linkBlockPolicy: "text-only"
1213
+ });
1214
+ function safeChatMarkdownHref(value) {
1215
+ if (!value) return void 0;
1216
+ if (value.startsWith("#")) return value;
1217
+ try {
1218
+ const url = new URL(value);
1219
+ return url.protocol === "https:" || url.protocol === "http:" ? url.href : void 0;
1220
+ } catch {
1221
+ return void 0;
1222
+ }
1223
+ }
1224
+
1225
+ // src/ui/message-response.tsx
1226
+ import { Fragment, jsx as jsx7 } from "react/jsx-runtime";
1227
+ function SafeMarkdownLink({ children, href, ...props }) {
1228
+ const domProps = { ...props };
1229
+ delete domProps.node;
1230
+ const safeHref = safeChatMarkdownHref(href);
1231
+ if (!safeHref) return /* @__PURE__ */ jsx7("span", { children });
1232
+ if (safeHref.startsWith("#")) return /* @__PURE__ */ jsx7("a", { ...domProps, href: safeHref, children });
1233
+ return /* @__PURE__ */ jsx7("a", { ...domProps, href: safeHref, rel: "noopener noreferrer", target: "_blank", children });
1234
+ }
1235
+ function BlockedMarkdownImage({ alt }) {
1236
+ return alt ? /* @__PURE__ */ jsx7("span", { "aria-label": `Image blocked: ${alt}`, role: "img", children: alt }) : null;
1237
+ }
1238
+ var BLOCK_MARKDOWN_TAGS = /* @__PURE__ */ new Set([
1239
+ "blockquote",
1240
+ "div",
1241
+ "h1",
1242
+ "h2",
1243
+ "h3",
1244
+ "h4",
1245
+ "h5",
1246
+ "h6",
1247
+ "hr",
1248
+ "ol",
1249
+ "pre",
1250
+ "table",
1251
+ "ul"
1252
+ ]);
1253
+ function isMultilineCodeNode(node) {
1254
+ return node?.tagName === "code" && node.position?.start?.line !== void 0 && node.position?.end?.line !== void 0 && node.position.start.line !== node.position.end.line;
1255
+ }
1256
+ function hasBlockMarkdownChild(child) {
1257
+ if (!React5.isValidElement(child)) {
1258
+ return false;
1259
+ }
1260
+ if (typeof child.type === "string" && BLOCK_MARKDOWN_TAGS.has(child.type)) {
1261
+ return true;
1262
+ }
1263
+ const props = child.props;
1264
+ if (BLOCK_MARKDOWN_TAGS.has(props.node?.tagName || "") || isMultilineCodeNode(props.node)) {
1265
+ return true;
1266
+ }
1267
+ return React5.Children.toArray(props.children).some(hasBlockMarkdownChild);
1268
+ }
1269
+ function MessageMarkdownParagraph({
1270
+ children,
1271
+ ...props
1272
+ }) {
1273
+ const domProps = { ...props };
1274
+ delete domProps.node;
1275
+ const childArray = React5.Children.toArray(children).filter(
1276
+ (child) => child !== null && child !== ""
1277
+ );
1278
+ const isImageOnly = childArray.length === 1 && React5.isValidElement(childArray[0]) && childArray[0].props.node?.tagName === "img";
1279
+ if (isImageOnly) {
1280
+ return /* @__PURE__ */ jsx7(Fragment, { children });
1281
+ }
1282
+ if (childArray.some(hasBlockMarkdownChild)) {
1283
+ return /* @__PURE__ */ jsx7("div", { ...domProps, children });
1284
+ }
1285
+ return /* @__PURE__ */ jsx7("p", { ...domProps, children });
1286
+ }
1287
+ var MESSAGE_RESPONSE_COMPONENTS = {
1288
+ a: SafeMarkdownLink,
1289
+ img: BlockedMarkdownImage,
1290
+ p: MessageMarkdownParagraph
1291
+ };
1292
+ var streamdownDefaultRehypePlugins = defaultRehypePlugins || {};
1293
+ var streamdownHardenPlugin = Array.isArray(
1294
+ streamdownDefaultRehypePlugins.harden
1295
+ ) ? streamdownDefaultRehypePlugins.harden[0] : streamdownDefaultRehypePlugins.harden;
1296
+ var MESSAGE_RESPONSE_REHYPE_PLUGINS = [
1297
+ ...Object.entries(streamdownDefaultRehypePlugins).filter(([key]) => key !== "harden").map(([, plugin]) => plugin),
1298
+ ...streamdownHardenPlugin ? [
1299
+ [
1300
+ streamdownHardenPlugin,
1301
+ CHAT_MARKDOWN_HARDEN_OPTIONS
1302
+ ]
1303
+ ] : []
1304
+ ];
1305
+ var MessageResponse = React5.memo(
1306
+ ({
1307
+ children,
1308
+ className,
1309
+ components,
1310
+ rehypePlugins,
1311
+ streaming = false,
1312
+ streamingId,
1313
+ ...props
1314
+ }) => {
1315
+ const text = typeof children === "string" || typeof children === "number" ? String(children) : "";
1316
+ const streamingText = useStreamingText({
1317
+ charsPerSecond: 120,
1318
+ id: streamingId || text,
1319
+ isStreaming: streaming && text.length > 0,
1320
+ maxCatchupChars: 32,
1321
+ text
1322
+ });
1323
+ const streamdownComponents = React5.useMemo(
1324
+ () => ({ ...MESSAGE_RESPONSE_COMPONENTS, ...components }),
1325
+ [components]
1326
+ );
1327
+ return /* @__PURE__ */ jsx7(
1328
+ Streamdown,
1329
+ {
1330
+ className: cn("a24-message-response", className),
1331
+ components: streamdownComponents,
1332
+ rehypePlugins: rehypePlugins || MESSAGE_RESPONSE_REHYPE_PLUGINS,
1333
+ ...props,
1334
+ children: text ? streamingText.displayedText : children
1335
+ }
1336
+ );
1337
+ },
1338
+ (prevProps, nextProps) => prevProps.children === nextProps.children && prevProps.className === nextProps.className && prevProps.streaming === nextProps.streaming && prevProps.streamingId === nextProps.streamingId && prevProps.components === nextProps.components
1339
+ );
1340
+ MessageResponse.displayName = "MessageResponse";
1341
+
1342
+ // src/ui/hitl-presentation.ts
1343
+ function optionalString(value) {
1344
+ return typeof value === "string" && value.trim() ? value.trim() : null;
1345
+ }
1346
+ function hitlActionLabel(part, action) {
1347
+ if (action === "approve") return ["tool_review", "app_data_permission"].includes(part.hitlKind) ? "Allow once" : "Approve";
1348
+ if (action === "reject") return ["tool_review", "app_data_permission"].includes(part.hitlKind) ? "Don\u2019t allow" : "Reject";
1349
+ if (action === "connect") return "Connect";
1350
+ if (action === "respond") return "Submit";
1351
+ return "Skip";
1352
+ }
1353
+ function hitlTitle(part) {
1354
+ const presentation = part.presentation || {};
1355
+ if (part.hitlKind === "tool_review") return `Run ${optionalString(presentation.tool_name) || "this tool"}?`;
1356
+ if (part.hitlKind === "mcp_auth") {
1357
+ const serverName = optionalString(presentation.server_name);
1358
+ return serverName ? `Connect ${serverName} to continue` : part.message;
1359
+ }
1360
+ if (part.hitlKind === "app_data_permission") return "Allow this data change?";
1361
+ return part.message;
1362
+ }
1363
+ function resolvedHitlLabel(part) {
1364
+ const outcome = String(part.resolution?.outcome || part.status).trim().toLowerCase();
1365
+ return {
1366
+ approved: part.hitlKind === "user_approval" ? "Approved" : "Allowed",
1367
+ rejected: part.hitlKind === "user_approval" ? "Rejected" : "Not allowed",
1368
+ connected: "connected",
1369
+ skipped: "Skipped",
1370
+ timeout: "Timed out",
1371
+ expired: "Timed out",
1372
+ cancelled: "Cancelled",
1373
+ invalidated: "No longer available",
1374
+ policy_denied: "Not allowed"
1375
+ }[outcome] || outcome.replace(/_/g, " ");
1376
+ }
1377
+
1378
+ // src/ui/tool-sessions.tsx
1379
+ import * as React6 from "react";
1380
+ import { ChevronDown as ChevronDown2, ChevronRight as ChevronRight2 } from "lucide-react";
1381
+ import { ThinkingOrb } from "thinking-orbs";
1382
+ import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
1383
+ function agentChatToolLabel(part) {
1384
+ const presentation = part.presentation || {};
1385
+ return optionalText(presentation.activity) || optionalText(presentation.title) || optionalText(part.displayName) || optionalText(part.summary) || optionalText(part.toolName) || "Tool activity";
1386
+ }
1387
+ function isAgentChatSubagent(part) {
1388
+ const sourceKind = String(part.sourceKind || "").trim().toLowerCase();
1389
+ return sourceKind === "agent" || sourceKind === "agent_call" || Boolean(part.presentation.childRun);
1390
+ }
1391
+ function isAgentChatToolRunning(part) {
1392
+ return part.state === "input-available" || part.state === "input-streaming";
1393
+ }
1394
+ function latestRunningAgentChatToolId(parts) {
1395
+ const active = [...parts].reverse().find(isAgentChatToolRunning);
1396
+ return active?.toolCallId || active?.id || null;
1397
+ }
1398
+ function partitionAgentChatToolTimeline(parts, toolViewMode = "closed") {
1399
+ const segments = [];
1400
+ let index = 0;
1401
+ while (index < parts.length) {
1402
+ const part = parts[index];
1403
+ const leadingModelTurn = part.kind === "model-turn" && parts[index + 1]?.kind === "tool" && !isAgentChatSubagent(parts[index + 1]) ? part : void 0;
1404
+ if (leadingModelTurn) index += 1;
1405
+ const candidate = parts[index];
1406
+ if (candidate.kind !== "tool") {
1407
+ segments.push({ kind: "part", part: candidate });
1408
+ index += 1;
1409
+ continue;
1410
+ }
1411
+ if (isAgentChatSubagent(candidate)) {
1412
+ segments.push({ kind: "subagent", part: candidate });
1413
+ index += 1;
1414
+ continue;
1415
+ }
1416
+ const streak = [];
1417
+ while (index < parts.length) {
1418
+ const candidate2 = parts[index];
1419
+ if (candidate2.kind !== "tool" || isAgentChatSubagent(candidate2)) break;
1420
+ streak.push(candidate2);
1421
+ index += 1;
1422
+ }
1423
+ const modelTurn = parts[index]?.kind === "model-turn" ? parts[index] : void 0;
1424
+ if (modelTurn) index += 1;
1425
+ if (toolViewMode === "expanded") {
1426
+ if (leadingModelTurn) segments.push({ kind: "part", part: leadingModelTurn });
1427
+ segments.push(...streak.map((toolPart) => ({ kind: "tool", part: toolPart })));
1428
+ if (modelTurn) segments.push({ kind: "part", part: modelTurn });
1429
+ } else if (streak.length === 1 && !modelTurn && !leadingModelTurn) {
1430
+ segments.push({ kind: "tool", part: streak[0] });
1431
+ } else {
1432
+ segments.push({
1433
+ kind: "tool-session",
1434
+ parts: streak,
1435
+ ...leadingModelTurn ? { leadingModelTurn } : {},
1436
+ ...modelTurn ? { modelTurn } : {}
1437
+ });
1438
+ }
1439
+ }
1440
+ return segments;
1441
+ }
1442
+ function optionalText(value) {
1443
+ return typeof value === "string" && value.trim() ? value.trim() : null;
1444
+ }
1445
+ function isActive(part, activeToolId) {
1446
+ return Boolean(activeToolId && (part.id === activeToolId || part.toolCallId === activeToolId));
1447
+ }
1448
+ function ToolActivityReel({
1449
+ announce = true,
1450
+ initialLabel,
1451
+ initialShownAt,
1452
+ label
1453
+ }) {
1454
+ const initial = initialLabel && initialLabel !== label ? initialLabel : label;
1455
+ const parsedInitialShownAt = initialShownAt ? Date.parse(initialShownAt) : Number.NaN;
1456
+ const [displayed, setDisplayed] = React6.useState(initial);
1457
+ const [previous, setPrevious] = React6.useState(null);
1458
+ const displayedRef = React6.useRef(initial);
1459
+ const latestRef = React6.useRef(label);
1460
+ const shownAtRef = React6.useRef(Number.isFinite(parsedInitialShownAt) ? parsedInitialShownAt : Date.now());
1461
+ React6.useEffect(() => {
1462
+ latestRef.current = label;
1463
+ if (displayedRef.current === label) return;
1464
+ const showLatest = () => {
1465
+ const next = latestRef.current;
1466
+ if (displayedRef.current === next) return;
1467
+ const old = displayedRef.current;
1468
+ displayedRef.current = next;
1469
+ shownAtRef.current = Date.now();
1470
+ setPrevious(old);
1471
+ setDisplayed(next);
1472
+ };
1473
+ const remaining = Math.max(0, 500 - (Date.now() - shownAtRef.current));
1474
+ if (remaining === 0) {
1475
+ showLatest();
1476
+ return;
1477
+ }
1478
+ const timer = window.setTimeout(showLatest, remaining);
1479
+ return () => window.clearTimeout(timer);
1480
+ }, [label]);
1481
+ React6.useEffect(() => {
1482
+ if (!previous) return;
1483
+ const timer = window.setTimeout(() => setPrevious(null), 200);
1484
+ return () => window.clearTimeout(timer);
1485
+ }, [previous]);
1486
+ return /* @__PURE__ */ jsxs4("span", { className: "a24-tool-reel", "aria-live": announce ? "polite" : "off", children: [
1487
+ previous ? /* @__PURE__ */ jsx8("span", { "aria-hidden": "true", className: "a24-tool-reel__previous", children: previous }) : null,
1488
+ /* @__PURE__ */ jsx8("span", { className: previous ? "a24-tool-reel__current" : void 0, children: displayed })
1489
+ ] });
1490
+ }
1491
+ function ToolActivityOrb({ label }) {
1492
+ return /* @__PURE__ */ jsx8("span", { className: "a24-tool-orb", children: /* @__PURE__ */ jsx8(ThinkingOrb, { "aria-label": label || "Working", size: 20, speed: 0.8, state: "searching" }) });
1493
+ }
1494
+ function AgentChatActivityRow({
1495
+ label,
1496
+ modelTurnId,
1497
+ onSelect
1498
+ }) {
1499
+ const content = /* @__PURE__ */ jsxs4(Fragment2, { children: [
1500
+ /* @__PURE__ */ jsx8(ToolActivityOrb, { label }),
1501
+ /* @__PURE__ */ jsx8("span", { className: "a24-tool-trigger__label a24-tool-shimmer", children: /* @__PURE__ */ jsx8(ToolActivityReel, { label }) })
1502
+ ] });
1503
+ return /* @__PURE__ */ jsx8("div", { className: "a24-tool-trigger-row", "data-agents24-model-turn": modelTurnId, role: "status", children: onSelect ? /* @__PURE__ */ jsx8("button", { className: "a24-tool-trigger a24-tool-trigger--root a24-tool-trigger--active", onClick: onSelect, type: "button", children: content }) : /* @__PURE__ */ jsx8("div", { className: "a24-tool-trigger a24-tool-trigger--root a24-tool-trigger--active", children: content }) });
1504
+ }
1505
+ function AgentChatModelTurnRow({ part }) {
1506
+ return /* @__PURE__ */ jsx8(AgentChatActivityRow, { label: "Planning next moves", modelTurnId: part.modelTurnId });
1507
+ }
1508
+ function AgentChatNodeActivityRow({
1509
+ onSelect,
1510
+ part
1511
+ }) {
1512
+ const [open, setOpen] = React6.useState(false);
1513
+ const contentId = React6.useId();
1514
+ const running = part.status === "running";
1515
+ const failed = part.status === "failed";
1516
+ const failureMessage = part.failure?.message || "The node could not complete the request.";
1517
+ const triggerClassName = cn(
1518
+ "a24-tool-trigger",
1519
+ "a24-tool-trigger--root",
1520
+ !running && "a24-tool-trigger--without-orb",
1521
+ running && "a24-tool-trigger--active",
1522
+ failed && "a24-tool-trigger--error"
1523
+ );
1524
+ const label = running ? /* @__PURE__ */ jsx8("span", { className: "a24-tool-shimmer", children: /* @__PURE__ */ jsx8(ToolActivityReel, { label: part.label }) }) : part.label;
1525
+ if (!failed) {
1526
+ return /* @__PURE__ */ jsx8("div", { className: "a24-tool-trigger-row", role: "status", children: onSelect ? /* @__PURE__ */ jsxs4("button", { className: triggerClassName, onClick: () => onSelect(part), type: "button", children: [
1527
+ running ? /* @__PURE__ */ jsx8(ToolActivityOrb, { label: part.label }) : null,
1528
+ /* @__PURE__ */ jsx8("span", { className: "a24-tool-trigger__label", children: label })
1529
+ ] }) : /* @__PURE__ */ jsxs4("div", { className: triggerClassName, children: [
1530
+ running ? /* @__PURE__ */ jsx8(ToolActivityOrb, { label: part.label }) : null,
1531
+ /* @__PURE__ */ jsx8("span", { className: "a24-tool-trigger__label", children: label })
1532
+ ] }) });
1533
+ }
1534
+ return /* @__PURE__ */ jsxs4("div", { className: "a24-tool-disclosure", children: [
1535
+ /* @__PURE__ */ jsx8("div", { className: "a24-tool-trigger-row", children: /* @__PURE__ */ jsxs4(
1536
+ "button",
1537
+ {
1538
+ "aria-controls": contentId,
1539
+ "aria-expanded": open,
1540
+ className: triggerClassName,
1541
+ onClick: () => {
1542
+ onSelect?.(part);
1543
+ setOpen((value) => !value);
1544
+ },
1545
+ type: "button",
1546
+ children: [
1547
+ /* @__PURE__ */ jsx8("span", { className: "a24-tool-trigger__label", children: label }),
1548
+ open ? /* @__PURE__ */ jsx8(ChevronDown2, { "aria-hidden": "true", className: "a24-tool-trigger__chevron" }) : /* @__PURE__ */ jsx8(ChevronRight2, { "aria-hidden": "true", className: "a24-tool-trigger__chevron" })
1549
+ ]
1550
+ }
1551
+ ) }),
1552
+ open ? /* @__PURE__ */ jsx8("div", { className: "a24-tool-detail a24-tool-detail--error", id: contentId, children: failureMessage }) : null
1553
+ ] });
1554
+ }
1555
+ function toolRowLabel(part) {
1556
+ const label = agentChatToolLabel(part);
1557
+ return isAgentChatToolRunning(part) ? label : label.replace(/^(?:running|ran)\s+/i, "");
1558
+ }
1559
+ function ToolDisclosure({
1560
+ children,
1561
+ error,
1562
+ label,
1563
+ loading,
1564
+ onExpandedChange,
1565
+ onSelect,
1566
+ part,
1567
+ root,
1568
+ showOrb,
1569
+ statusLabel,
1570
+ accessory
1571
+ }) {
1572
+ const [open, setOpen] = React6.useState(false);
1573
+ const contentId = React6.useId();
1574
+ const expandable = React6.Children.toArray(children).length > 0;
1575
+ if (!expandable) {
1576
+ const triggerClassName = cn("a24-tool-trigger", root && "a24-tool-trigger--root", root && !showOrb && "a24-tool-trigger--without-orb", error && "a24-tool-trigger--error", loading && "a24-tool-trigger--active");
1577
+ return /* @__PURE__ */ jsxs4("div", { className: "a24-tool-trigger-row", children: [
1578
+ onSelect && part ? /* @__PURE__ */ jsxs4("button", { className: triggerClassName, onClick: () => onSelect(part), type: "button", children: [
1579
+ showOrb ? /* @__PURE__ */ jsx8(ToolActivityOrb, { label: statusLabel }) : null,
1580
+ /* @__PURE__ */ jsx8("span", { className: "a24-tool-trigger__label", children: label })
1581
+ ] }) : /* @__PURE__ */ jsxs4("div", { className: triggerClassName, children: [
1582
+ showOrb ? /* @__PURE__ */ jsx8(ToolActivityOrb, { label: statusLabel }) : null,
1583
+ /* @__PURE__ */ jsx8("span", { className: "a24-tool-trigger__label", children: label })
1584
+ ] }),
1585
+ accessory
1586
+ ] });
1587
+ }
1588
+ return /* @__PURE__ */ jsxs4("div", { className: "a24-tool-disclosure", children: [
1589
+ /* @__PURE__ */ jsxs4("div", { className: "a24-tool-trigger-row", children: [
1590
+ /* @__PURE__ */ jsxs4(
1591
+ "button",
1592
+ {
1593
+ "aria-controls": contentId,
1594
+ "aria-expanded": open,
1595
+ className: cn("a24-tool-trigger", root && "a24-tool-trigger--root", root && !showOrb && "a24-tool-trigger--without-orb", error && "a24-tool-trigger--error", loading && "a24-tool-trigger--active"),
1596
+ onClick: () => {
1597
+ const nextOpen = !open;
1598
+ setOpen(nextOpen);
1599
+ if (part) onSelect?.(part);
1600
+ onExpandedChange?.(nextOpen, part);
1601
+ },
1602
+ type: "button",
1603
+ children: [
1604
+ showOrb ? /* @__PURE__ */ jsx8(ToolActivityOrb, { label: statusLabel }) : null,
1605
+ /* @__PURE__ */ jsx8("span", { className: "a24-tool-trigger__label", children: label }),
1606
+ open ? /* @__PURE__ */ jsx8(ChevronDown2, { "aria-hidden": "true", className: "a24-tool-trigger__chevron" }) : /* @__PURE__ */ jsx8(ChevronRight2, { "aria-hidden": "true", className: "a24-tool-trigger__chevron" })
1607
+ ]
1608
+ }
1609
+ ),
1610
+ accessory
1611
+ ] }),
1612
+ open ? /* @__PURE__ */ jsx8("div", { className: "a24-tool-disclosure__content", id: contentId, children }) : null
1613
+ ] });
1614
+ }
1615
+ function ToolLabel({ active, label, part, slots }) {
1616
+ const rendered = slots?.renderToolLabel?.({ isActive: active, label, part }) ?? label;
1617
+ return active ? /* @__PURE__ */ jsx8("span", { className: "a24-tool-shimmer", children: rendered }) : /* @__PURE__ */ jsx8(Fragment2, { children: rendered });
1618
+ }
1619
+ function ToolList({ children }) {
1620
+ const listRef = React6.useRef(null);
1621
+ const [fades, setFades] = React6.useState({ bottom: false, top: false });
1622
+ const updateFades = React6.useCallback(() => {
1623
+ const list = listRef.current;
1624
+ if (!list) return;
1625
+ const next = {
1626
+ bottom: list.scrollTop + list.clientHeight < list.scrollHeight - 1,
1627
+ top: list.scrollTop > 1
1628
+ };
1629
+ setFades((current) => current.bottom === next.bottom && current.top === next.top ? current : next);
1630
+ }, []);
1631
+ React6.useEffect(() => {
1632
+ updateFades();
1633
+ const list = listRef.current;
1634
+ if (!list || typeof ResizeObserver === "undefined") return;
1635
+ const observer = new ResizeObserver(updateFades);
1636
+ observer.observe(list);
1637
+ return () => observer.disconnect();
1638
+ }, [children, updateFades]);
1639
+ return /* @__PURE__ */ jsx8(
1640
+ "div",
1641
+ {
1642
+ className: "a24-tool-list-frame",
1643
+ "data-scroll-fade-bottom": fades.bottom ? "" : void 0,
1644
+ "data-scroll-fade-top": fades.top ? "" : void 0,
1645
+ children: /* @__PURE__ */ jsx8("div", { className: "a24-tool-list", onScroll: updateFades, ref: listRef, role: "list", children })
1646
+ }
1647
+ );
1648
+ }
1649
+ function AgentChatToolRow({
1650
+ activeToolId,
1651
+ part,
1652
+ root = true,
1653
+ showOrb,
1654
+ slots
1655
+ }) {
1656
+ const active = isActive(part, activeToolId);
1657
+ const failed = part.state === "output-error";
1658
+ const failureMessage = part.failure?.message || null;
1659
+ const detail = failed ? failureMessage : optionalText(part.presentation.detail);
1660
+ const hostDetails = failed ? null : slots?.renderToolDetails?.(part);
1661
+ const context = { isActive: active, label: toolRowLabel(part), part };
1662
+ return /* @__PURE__ */ jsx8("div", { "data-agents24-tool-part": part.type, "data-state": part.state, children: /* @__PURE__ */ jsxs4(
1663
+ ToolDisclosure,
1664
+ {
1665
+ error: failed,
1666
+ accessory: slots?.renderToolAccessory?.(context),
1667
+ label: /* @__PURE__ */ jsx8(ToolLabel, { active, label: context.label, part, slots }),
1668
+ loading: active,
1669
+ onExpandedChange: slots?.onExpandedChange,
1670
+ onSelect: slots?.onToolSelect,
1671
+ part,
1672
+ root,
1673
+ showOrb: showOrb ?? active,
1674
+ statusLabel: active ? context.label : void 0,
1675
+ children: [
1676
+ detail ? /* @__PURE__ */ jsx8("div", { className: cn("a24-tool-detail", failed && "a24-tool-detail--error"), children: detail }) : null,
1677
+ hostDetails
1678
+ ]
1679
+ }
1680
+ ) });
1681
+ }
1682
+ function sessionLabel(parts, visiblePart, running) {
1683
+ const label = agentChatToolLabel(visiblePart);
1684
+ const terminalLabel = label.replace(/^(?:running|ran)\s+/i, "");
1685
+ return running ? label : `Ran ${terminalLabel} + ${parts.length - 1} more`;
1686
+ }
1687
+ function AgentChatToolSession({
1688
+ activeToolId,
1689
+ isLatestActivity = true,
1690
+ isLive = false,
1691
+ leadingModelTurn,
1692
+ modelTurn,
1693
+ parts,
1694
+ slots
1695
+ }) {
1696
+ const presentedLiveRef = React6.useRef(false);
1697
+ if (isLive && isLatestActivity) presentedLiveRef.current = true;
1698
+ const activeIndex = parts.findLastIndex((part) => isActive(part, activeToolId));
1699
+ const running = activeIndex >= 0;
1700
+ if (parts.length === 1 && !modelTurn && !leadingModelTurn) return /* @__PURE__ */ jsx8(AgentChatToolRow, { activeToolId, part: parts[0], slots });
1701
+ const visibleIndex = running ? activeIndex : parts.length - 1;
1702
+ const visiblePart = parts[visibleIndex];
1703
+ const planning = Boolean(modelTurn);
1704
+ const presentingActivity = isLatestActivity && (planning || running || isLive);
1705
+ const failed = isLatestActivity && (visiblePart.state === "output-error" || visiblePart.state === "cancelled");
1706
+ const label = planning ? "Planning next moves" : isLive && isLatestActivity ? agentChatToolLabel(visiblePart) : sessionLabel(parts, visiblePart, running);
1707
+ const labelContent = presentedLiveRef.current ? /* @__PURE__ */ jsx8("span", { className: presentingActivity ? "a24-tool-shimmer" : void 0, children: /* @__PURE__ */ jsx8(ToolActivityReel, { initialLabel: leadingModelTurn ? "Planning next moves" : void 0, initialShownAt: leadingModelTurn?.timestamp, label }) }) : label;
1708
+ const context = { isActive: presentingActivity, label, part: visiblePart };
1709
+ return /* @__PURE__ */ jsx8(
1710
+ ToolDisclosure,
1711
+ {
1712
+ accessory: slots?.renderToolAccessory?.(context),
1713
+ error: failed,
1714
+ label: labelContent,
1715
+ loading: presentingActivity,
1716
+ onExpandedChange: slots?.onExpandedChange,
1717
+ part: visiblePart,
1718
+ root: true,
1719
+ showOrb: isLive && isLatestActivity,
1720
+ statusLabel: planning ? "Planning next moves" : label,
1721
+ children: /* @__PURE__ */ jsx8(ToolList, { children: parts.map((part) => /* @__PURE__ */ jsx8("div", { role: "listitem", children: /* @__PURE__ */ jsx8(AgentChatToolRow, { activeToolId, part, root: false, showOrb: false, slots }) }, part.id)) })
1722
+ }
1723
+ );
1724
+ }
1725
+ function childLabel(child) {
1726
+ return optionalText(child.title) || optionalText(child.toolName) || "Tool";
1727
+ }
1728
+ function subagentLabel(part) {
1729
+ const childRun = part.presentation.childRun;
1730
+ const children = part.presentation.childTools || [];
1731
+ const name = optionalText(childRun?.agentName) || optionalText(part.displayName) || "agent";
1732
+ const status = String(childRun?.status || (isAgentChatToolRunning(part) ? "running" : "completed")).toLowerCase();
1733
+ const activeChild = children.find((child) => child.id === part.presentation.activeChildToolId) || [...children].reverse().find((child) => child.status === "running") || children[children.length - 1];
1734
+ if (status === "running" && activeChild) return { label: `Agent ${name}, running ${childLabel(activeChild)}`, running: true };
1735
+ if (status === "running") return { label: `Agent ${name}, starting`, running: true };
1736
+ if (status === "failed" || part.state === "output-error") return { label: `Agent ${name}, failed`, running: false };
1737
+ if (status === "cancelled" || status === "canceled" || part.state === "cancelled") return { label: `Agent ${name}, cancelled`, running: false };
1738
+ if (status === "paused") return { label: `Agent ${name}, paused`, running: false };
1739
+ return { label: `Agent ${name}, ran ${children.length} ${children.length === 1 ? "tool" : "tools"}`, running: false };
1740
+ }
1741
+ function AgentChatSubagentGroup({ isLatestActivity = true, isLive = false, part, slots }) {
1742
+ const presentedLiveRef = React6.useRef(false);
1743
+ if (isLive && isLatestActivity) presentedLiveRef.current = true;
1744
+ const children = part.presentation.childTools || [];
1745
+ const state = subagentLabel(part);
1746
+ const childRunStatus = String(part.presentation.childRun?.status || "").toLowerCase();
1747
+ const failed = part.state === "output-error" || part.state === "cancelled" || childRunStatus === "failed" || childRunStatus === "cancelled" || childRunStatus === "canceled";
1748
+ const hostDetails = failed ? null : slots?.renderToolDetails?.(part);
1749
+ const failureMessage = part.failure?.message || null;
1750
+ const presentingActivity = isLatestActivity && state.running;
1751
+ const labelContent = presentedLiveRef.current ? /* @__PURE__ */ jsx8("span", { className: presentingActivity ? "a24-tool-shimmer" : void 0, children: /* @__PURE__ */ jsx8(ToolActivityReel, { label: state.label }) }) : state.label;
1752
+ const context = { isActive: presentingActivity, label: state.label, part };
1753
+ return /* @__PURE__ */ jsx8(
1754
+ ToolDisclosure,
1755
+ {
1756
+ accessory: slots?.renderToolAccessory?.(context),
1757
+ error: failed,
1758
+ label: labelContent,
1759
+ loading: presentingActivity,
1760
+ onExpandedChange: slots?.onExpandedChange,
1761
+ part,
1762
+ root: true,
1763
+ showOrb: isLive && isLatestActivity,
1764
+ statusLabel: state.label,
1765
+ children: /* @__PURE__ */ jsxs4(ToolList, { children: [
1766
+ children.map((child) => /* @__PURE__ */ jsx8("div", { className: cn("a24-child-tool", child.status === "failed" && "a24-child-tool--error"), role: "listitem", children: slots?.renderChildTool?.(child, part) ?? /* @__PURE__ */ jsxs4(Fragment2, { children: [
1767
+ /* @__PURE__ */ jsx8("span", { className: cn(isLive && isLatestActivity && child.status === "running" && "a24-tool-shimmer"), children: childLabel(child) }),
1768
+ child.detail ? /* @__PURE__ */ jsx8("span", { className: "a24-child-tool__detail", children: child.detail }) : null
1769
+ ] }) }, child.id)),
1770
+ failureMessage ? /* @__PURE__ */ jsx8("div", { className: "a24-tool-detail a24-tool-detail--error", children: failureMessage }) : null,
1771
+ hostDetails
1772
+ ] })
1773
+ }
1774
+ );
1775
+ }
1776
+
1777
+ // src/ui/agent-response-timeline.tsx
1778
+ import { jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
1779
+ function isInternalAskUserTool(part) {
1780
+ return [part.toolName, part.toolKey, part.action].some((value) => value?.trim().toLowerCase() === "runtime__ask_user");
1781
+ }
1782
+ function Shimmer({ children, className }) {
1783
+ return /* @__PURE__ */ jsx9("span", { className: cn("inline-block animate-pulse", className), children });
1784
+ }
1785
+ function AgentChatToolPart({
1786
+ activeToolId,
1787
+ part
1788
+ }) {
1789
+ return /* @__PURE__ */ jsx9(AgentChatToolRow, { activeToolId, part });
1790
+ }
1791
+ function AgentChatToolGroup({
1792
+ activeToolId,
1793
+ parts
1794
+ }) {
1795
+ return /* @__PURE__ */ jsx9(AgentChatToolSession, { activeToolId, parts });
1796
+ }
1797
+ function HitlPartRow({
1798
+ getHitlActionState,
1799
+ isLoading,
1800
+ onHitlAction,
1801
+ part
1802
+ }) {
1803
+ const state = getHitlActionState?.(part);
1804
+ const [comment, setComment] = React7.useState("");
1805
+ const presentation = part.presentation || {};
1806
+ const requireComment = presentation.require_comment === true;
1807
+ const isTerminal = part.status !== "pending";
1808
+ const resolvedRef = React7.useRef(null);
1809
+ const wasTerminal = React7.useRef(isTerminal);
1810
+ React7.useEffect(() => {
1811
+ if (isTerminal && !wasTerminal.current) resolvedRef.current?.focus();
1812
+ wasTerminal.current = isTerminal;
1813
+ }, [isTerminal]);
1814
+ if (part.hitlKind === "user_input") {
1815
+ return /* @__PURE__ */ jsx9(
1816
+ AskUserInteraction,
1817
+ {
1818
+ error: state?.error,
1819
+ isBusy: isLoading || state?.status === "resuming",
1820
+ onSubmit: onHitlAction ? (answers) => onHitlAction(part, {
1821
+ action: "respond",
1822
+ answers: answers.map((answer) => ({
1823
+ question_id: answer.questionId,
1824
+ selected_option_ids: answer.selectedOptionIds,
1825
+ ...answer.customText ? { custom_text: answer.customText } : {}
1826
+ }))
1827
+ }) : void 0,
1828
+ part
1829
+ }
1830
+ );
1831
+ }
1832
+ if (isTerminal) {
1833
+ return /* @__PURE__ */ jsx9("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) });
1834
+ }
1835
+ return /* @__PURE__ */ jsxs5("div", { className: "a24-hitl space-y-2.5 px-1 py-2", children: [
1836
+ /* @__PURE__ */ jsx9("div", { className: "a24-hitl__title text-sm font-medium text-foreground text-pretty", children: hitlTitle(part) }),
1837
+ requireComment ? /* @__PURE__ */ jsx9(
1838
+ "textarea",
1839
+ {
1840
+ "aria-label": "Approval comment",
1841
+ autoComplete: "off",
1842
+ 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",
1843
+ name: "hitl-comment",
1844
+ onChange: (event) => setComment(event.target.value),
1845
+ placeholder: "Comment required\u2026",
1846
+ value: comment
1847
+ }
1848
+ ) : null,
1849
+ state?.error ? /* @__PURE__ */ jsx9("div", { className: "a24-hitl__error text-sm text-destructive", children: state.error }) : null,
1850
+ /* @__PURE__ */ jsx9("div", { className: "a24-hitl__actions flex flex-wrap items-center gap-1.5", children: part.allowedActions.map((action) => /* @__PURE__ */ jsx9(
1851
+ "button",
1852
+ {
1853
+ "aria-label": hitlActionLabel(part, action),
1854
+ className: cn(
1855
+ "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",
1856
+ 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"
1857
+ ),
1858
+ "data-primary": action === "approve" || action === "connect" ? "" : void 0,
1859
+ disabled: !onHitlAction || isLoading || state?.status === "connecting" || state?.status === "resuming" || requireComment && !comment.trim(),
1860
+ onClick: () => onHitlAction?.(part, {
1861
+ action,
1862
+ ...comment.trim() ? { comment: comment.trim() } : {}
1863
+ }),
1864
+ type: "button",
1865
+ children: hitlActionLabel(part, action)
1866
+ },
1867
+ action
1868
+ )) })
1869
+ ] });
1870
+ }
1871
+ function AgentChatPart({
1872
+ activeToolId,
1873
+ getHitlActionState,
1874
+ isLoading,
1875
+ isTextStreaming,
1876
+ message,
1877
+ onHitlAction,
1878
+ part,
1879
+ responseProps,
1880
+ toolSlots
1881
+ }) {
1882
+ if (part.kind === "text") {
1883
+ return /* @__PURE__ */ jsx9(
1884
+ MessageResponse,
1885
+ {
1886
+ ...responseProps,
1887
+ streaming: isTextStreaming ?? Boolean(isLoading && part.raw?.status === "streaming"),
1888
+ streamingId: part.id,
1889
+ children: part.text
1890
+ }
1891
+ );
1892
+ }
1893
+ if (part.kind === "tool") return isAgentChatSubagent(part) ? /* @__PURE__ */ jsx9(AgentChatSubagentGroup, { part, slots: toolSlots }) : /* @__PURE__ */ jsx9(AgentChatToolRow, { activeToolId, part, slots: toolSlots });
1894
+ if (part.kind === "model-turn") return /* @__PURE__ */ jsx9(AgentChatModelTurnRow, { part });
1895
+ if (part.kind === "reasoning") {
1896
+ const label = part.text || part.label || "Thinking";
1897
+ return part.status === "running" || part.status === "streaming" ? /* @__PURE__ */ jsx9(Shimmer, { className: "text-sm text-muted-foreground", children: label }) : /* @__PURE__ */ jsx9("div", { className: "text-sm text-muted-foreground", children: label });
1898
+ }
1899
+ if (part.kind === "context-compression") {
1900
+ return /* @__PURE__ */ jsx9(AgentChatContextCompressionRow, { part });
1901
+ }
1902
+ if (part.kind === "ui-blocks") {
1903
+ if (part.state === "output-error") {
1904
+ return /* @__PURE__ */ jsx9("div", { className: "a24-timeline-error text-sm text-destructive", children: part.errorText || "Failed to render UI content." });
1905
+ }
1906
+ return part.bundle ? /* @__PURE__ */ jsx9(SafeUiBlocks, { bundle: part.bundle }) : /* @__PURE__ */ jsx9("div", { className: "text-sm text-muted-foreground", children: part.state === "input-streaming" ? /* @__PURE__ */ jsx9(Shimmer, { children: "Rendering UI block" }) : "Rendered UI block" });
1907
+ }
1908
+ if (part.kind === "hitl") {
1909
+ return /* @__PURE__ */ jsx9(
1910
+ HitlPartRow,
1911
+ {
1912
+ getHitlActionState,
1913
+ isLoading,
1914
+ onHitlAction,
1915
+ part
1916
+ }
1917
+ );
1918
+ }
1919
+ if (part.kind === "error") return /* @__PURE__ */ jsx9("div", { className: "a24-timeline-error text-sm text-destructive", role: "alert", children: part.errorText });
1920
+ if (part.kind === "source") {
1921
+ const href = safeExternalUrl(part.url);
1922
+ return href ? /* @__PURE__ */ jsx9("a", { className: "a24-source-link", href, rel: "noopener noreferrer", target: "_blank", children: part.title }) : /* @__PURE__ */ jsx9("span", { className: "a24-source-link", children: part.title });
1923
+ }
1924
+ if (part.kind === "citation") {
1925
+ const href = safeExternalUrl(part.url);
1926
+ const label = part.label || "Citation";
1927
+ return href ? /* @__PURE__ */ jsx9("a", { className: "a24-citation", href, rel: "noopener noreferrer", target: "_blank", children: label }) : /* @__PURE__ */ jsx9("span", { className: "a24-citation", children: label });
1928
+ }
1929
+ if (part.kind === "attachment") return /* @__PURE__ */ jsx9("div", { className: "a24-output-attachment", children: part.attachment.filename || "Attachment" });
1930
+ if (part.kind === "data") return /* @__PURE__ */ jsx9("div", { "data-agents24-data-part": part.name, className: "hidden", children: message.id });
1931
+ return null;
1932
+ }
1933
+ function safeExternalUrl(value) {
1934
+ if (!value) return null;
1935
+ try {
1936
+ const url = new URL(value);
1937
+ return url.protocol === "https:" || url.protocol === "http:" ? url.href : null;
1938
+ } catch {
1939
+ return null;
1940
+ }
1941
+ }
1942
+ function SafeUiBlocks({ bundle }) {
1943
+ const rows = Array.isArray(bundle.rows) ? bundle.rows : [];
1944
+ return /* @__PURE__ */ jsxs5("section", { "aria-label": typeof bundle.title === "string" ? bundle.title : "Agent result", className: "a24-ui-blocks", children: [
1945
+ typeof bundle.title === "string" ? /* @__PURE__ */ jsx9("h3", { children: bundle.title }) : null,
1946
+ rows.map((rawRow, rowIndex) => {
1947
+ const row = rawRow && typeof rawRow === "object" && !Array.isArray(rawRow) ? rawRow : {};
1948
+ const blocks = Array.isArray(row.blocks) ? row.blocks : [];
1949
+ return /* @__PURE__ */ jsx9("div", { className: "a24-ui-blocks__row", children: blocks.map((rawBlock, blockIndex) => {
1950
+ const block = rawBlock && typeof rawBlock === "object" && !Array.isArray(rawBlock) ? rawBlock : {};
1951
+ const title = typeof block.title === "string" ? block.title : "Result";
1952
+ const kind = String(block.kind || "note");
1953
+ if (kind === "table" && Array.isArray(block.columns) && Array.isArray(block.rows)) {
1954
+ return /* @__PURE__ */ jsxs5("div", { className: "a24-ui-block", children: [
1955
+ /* @__PURE__ */ jsx9("h4", { children: title }),
1956
+ /* @__PURE__ */ jsx9("div", { className: "a24-ui-table-scroll", children: /* @__PURE__ */ jsxs5("table", { children: [
1957
+ /* @__PURE__ */ jsx9("thead", { children: /* @__PURE__ */ jsx9("tr", { children: block.columns.map((column, index) => /* @__PURE__ */ jsx9("th", { children: String(column) }, index)) }) }),
1958
+ /* @__PURE__ */ jsx9("tbody", { children: block.rows.map((rawCells, index) => /* @__PURE__ */ jsx9("tr", { children: (Array.isArray(rawCells) ? rawCells : []).map((cell, cellIndex) => /* @__PURE__ */ jsx9("td", { children: String(cell) }, cellIndex)) }, index)) })
1959
+ ] }) })
1960
+ ] }, String(block.id || blockIndex));
1961
+ }
1962
+ const value = block.value ?? block.text ?? "";
1963
+ return /* @__PURE__ */ jsxs5("div", { className: "a24-ui-block", children: [
1964
+ /* @__PURE__ */ jsx9("h4", { children: title }),
1965
+ /* @__PURE__ */ jsx9("p", { children: String(value) })
1966
+ ] }, String(block.id || blockIndex));
1967
+ }) }, `row-${rowIndex}`);
1968
+ })
1969
+ ] });
1970
+ }
1971
+ function AgentResponseTimeline({
1972
+ getHitlActionState,
1973
+ isLoading = false,
1974
+ message,
1975
+ nodeActivityMode = "default",
1976
+ onHitlAction,
1977
+ parts: displayParts,
1978
+ responseProps,
1979
+ toolSlots,
1980
+ toolViewMode = "closed"
1981
+ }) {
1982
+ const parts = (displayParts || message.parts || []).filter((part) => (part.kind !== "tool" || !isInternalAskUserTool(part)) && (part.kind !== "model-turn" || part.turnKind === "agent") && (part.kind !== "node-activity" || nodeActivityMode === "all" || nodeActivityMode === "default" && part.defaultChatVisible));
1983
+ const toolParts = parts.filter((part) => part.kind === "tool");
1984
+ const activeToolId = latestRunningAgentChatToolId(toolParts);
1985
+ const activeTextPartId = getActiveStreamingTextPartId(
1986
+ { ...message, parts: [...parts] },
1987
+ isLoading ? message.id : null
1988
+ );
1989
+ const rendered = [];
1990
+ let activeActivityClaimed = false;
1991
+ const segments = partitionAgentChatToolTimeline(parts, toolViewMode);
1992
+ for (const [segmentIndex, segment] of segments.entries()) {
1993
+ if (segment.kind === "tool") {
1994
+ const isLatestActivity = segmentIndex === segments.length - 1;
1995
+ const showOrb = toolViewMode === "closed" ? isLoading && isLatestActivity : Boolean(activeToolId && (segment.part.id === activeToolId || segment.part.toolCallId === activeToolId));
1996
+ rendered.push(/* @__PURE__ */ jsx9(AgentChatToolRow, { activeToolId, part: segment.part, showOrb, slots: toolSlots }, segment.part.id));
1997
+ continue;
1998
+ }
1999
+ if (segment.kind === "tool-session") {
2000
+ rendered.push(/* @__PURE__ */ jsx9(AgentChatToolSession, { activeToolId, isLatestActivity: segmentIndex === segments.length - 1, isLive: isLoading, leadingModelTurn: segment.leadingModelTurn, modelTurn: segment.modelTurn, parts: segment.parts, slots: toolSlots }, `tool-session-${segment.parts[0].id}`));
2001
+ continue;
2002
+ }
2003
+ if (segment.kind === "subagent") {
2004
+ rendered.push(/* @__PURE__ */ jsx9(AgentChatSubagentGroup, { isLatestActivity: segmentIndex === segments.length - 1, isLive: isLoading, part: segment.part, slots: toolSlots }, segment.part.id));
2005
+ continue;
2006
+ }
2007
+ const part = segment.part;
2008
+ if (part.kind === "model-turn") {
2009
+ const key = activeActivityClaimed ? part.id : `${message.id}-active-activity`;
2010
+ activeActivityClaimed = true;
2011
+ rendered.push(/* @__PURE__ */ jsx9(AgentChatActivityRow, { label: "Planning next moves", modelTurnId: part.modelTurnId }, key));
2012
+ continue;
2013
+ }
2014
+ if (part.kind === "node-activity") {
2015
+ const key = activeActivityClaimed ? part.id : `${message.id}-active-activity`;
2016
+ if (part.status === "running") activeActivityClaimed = true;
2017
+ rendered.push(
2018
+ part.status === "running" ? /* @__PURE__ */ jsx9(AgentChatActivityRow, { label: part.label }, key) : /* @__PURE__ */ jsx9(AgentChatNodeActivityRow, { part }, part.id)
2019
+ );
2020
+ continue;
2021
+ }
2022
+ rendered.push(
2023
+ /* @__PURE__ */ jsx9(React7.Fragment, { children: /* @__PURE__ */ jsx9(
2024
+ AgentChatPart,
2025
+ {
2026
+ activeToolId,
2027
+ getHitlActionState,
2028
+ isLoading,
2029
+ isTextStreaming: part.kind === "text" && part.id === activeTextPartId,
2030
+ message,
2031
+ onHitlAction,
2032
+ part,
2033
+ responseProps,
2034
+ toolSlots
2035
+ }
2036
+ ) }, part.id)
2037
+ );
2038
+ }
2039
+ if (rendered.length === 0 && message.content) {
2040
+ rendered.push(/* @__PURE__ */ jsx9(MessageResponse, { children: message.content }, `${message.id}-fallback`));
2041
+ }
2042
+ if (rendered.length === 0 && isLoading) {
2043
+ rendered.push(/* @__PURE__ */ jsx9(AgentChatActivityRow, { label: "Thinking" }, `${message.id}-active-activity`));
2044
+ }
2045
+ return /* @__PURE__ */ jsx9("div", { className: "space-y-3", children: rendered });
2046
+ }
2047
+
2048
+ // src/ui/agent-chat-message.tsx
2049
+ import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
2050
+ var DEFAULT_USER_MESSAGE_COLLAPSE_THRESHOLD = 360;
2051
+ var DEFAULT_USER_MESSAGE_COLLAPSED_LINES = 4;
2052
+ function shouldCollapseUserMessage(content, threshold = DEFAULT_USER_MESSAGE_COLLAPSE_THRESHOLD) {
2053
+ return typeof threshold === "number" && threshold > 0 && content.length > threshold;
2054
+ }
2055
+ function AgentChatUserMessageContent({
2056
+ collapsedLines = DEFAULT_USER_MESSAGE_COLLAPSED_LINES,
2057
+ collapseThreshold = DEFAULT_USER_MESSAGE_COLLAPSE_THRESHOLD,
2058
+ content,
2059
+ showLessLabel = "Show less",
2060
+ showMoreLabel = "Show more"
2061
+ }) {
2062
+ const [isExpanded, setIsExpanded] = React8.useState(false);
2063
+ const contentId = React8.useId();
2064
+ const shouldCollapse = shouldCollapseUserMessage(content, collapseThreshold);
2065
+ const Icon = isExpanded ? ChevronUp : ChevronDown3;
2066
+ const clampedStyle = shouldCollapse && !isExpanded ? {
2067
+ WebkitBoxOrient: "vertical",
2068
+ WebkitLineClamp: Math.max(1, collapsedLines),
2069
+ display: "-webkit-box",
2070
+ overflow: "hidden"
2071
+ } : void 0;
2072
+ return /* @__PURE__ */ jsxs6("div", { className: "flex min-w-0 flex-col gap-3", children: [
2073
+ /* @__PURE__ */ jsx10("div", { id: contentId, className: "min-w-0", style: clampedStyle, children: content }),
2074
+ shouldCollapse ? /* @__PURE__ */ jsxs6(
2075
+ "button",
2076
+ {
2077
+ "aria-controls": contentId,
2078
+ "aria-expanded": isExpanded,
2079
+ className: cn(
2080
+ "inline-flex w-fit items-center gap-1 rounded-md text-left text-sm font-medium text-muted-foreground transition-colors",
2081
+ "hover:text-foreground focus-visible:border-neutral-300 focus-visible:outline-none focus-visible:ring-0"
2082
+ ),
2083
+ onClick: () => setIsExpanded((current) => !current),
2084
+ type: "button",
2085
+ children: [
2086
+ isExpanded ? showLessLabel : showMoreLabel,
2087
+ /* @__PURE__ */ jsx10(Icon, { "aria-hidden": "true", className: "size-4", strokeWidth: 2 })
2088
+ ]
2089
+ }
2090
+ ) : null
2091
+ ] });
2092
+ }
2093
+ function AgentChatMessage({
2094
+ actionHandlers,
2095
+ additionalActions,
2096
+ actions,
2097
+ actionsClassName,
2098
+ className,
2099
+ contentClassName,
2100
+ dir,
2101
+ getHitlActionState,
2102
+ message,
2103
+ nodeActivityMode,
2104
+ onHitlAction,
2105
+ Timeline = AgentResponseTimeline,
2106
+ renderAssistantContent,
2107
+ renderUserContent,
2108
+ showAssistantAttachments = false,
2109
+ streamingMessageId,
2110
+ userMessageCollapsedLines,
2111
+ userMessageContent,
2112
+ userMessageCollapseThreshold,
2113
+ userMessageShowLessLabel,
2114
+ userMessageShowMoreLabel
2115
+ }) {
2116
+ const isAssistant = message.role === "assistant";
2117
+ const isStreaming = isAssistant && message.id === streamingMessageId;
2118
+ const resolvedUserContent = userMessageContent ?? message.content;
2119
+ const shouldRenderContent = isAssistant || Boolean(renderUserContent || resolvedUserContent);
2120
+ const showAttachments = Boolean(message.attachments?.length) && (message.role === "user" || showAssistantAttachments);
2121
+ const renderedActions = actions ?? (actionHandlers ? /* @__PURE__ */ jsx10(
2122
+ AgentChatDefaultActions,
2123
+ {
2124
+ additionalActions,
2125
+ dir,
2126
+ handlers: actionHandlers,
2127
+ message
2128
+ }
2129
+ ) : null);
2130
+ return /* @__PURE__ */ jsxs6(ChatMessage, { className, dir, from: message.role, children: [
2131
+ showAttachments ? /* @__PURE__ */ jsx10(ChatAttachments, { className: "mb-2", dir, children: message.attachments?.map((attachment, index) => /* @__PURE__ */ jsx10(
2132
+ ChatAttachment,
2133
+ {
2134
+ data: attachment,
2135
+ dir: "ltr"
2136
+ },
2137
+ String(attachment.url || attachment.filename || index)
2138
+ )) }) : null,
2139
+ shouldRenderContent ? /* @__PURE__ */ jsxs6(
2140
+ ChatMessageContent,
2141
+ {
2142
+ className: cn(isAssistant ? "bg-transparent p-0" : void 0, contentClassName),
2143
+ dir,
2144
+ from: message.role,
2145
+ children: [
2146
+ isAssistant ? renderAssistantContent ? renderAssistantContent({ isStreaming, message }) : /* @__PURE__ */ jsx10(
2147
+ Timeline,
2148
+ {
2149
+ getHitlActionState,
2150
+ isLoading: isStreaming,
2151
+ message,
2152
+ nodeActivityMode,
2153
+ onHitlAction,
2154
+ parts: message.parts
2155
+ }
2156
+ ) : null,
2157
+ !isAssistant ? renderUserContent ? renderUserContent({ message }) : resolvedUserContent ? /* @__PURE__ */ jsx10(
2158
+ AgentChatUserMessageContent,
2159
+ {
2160
+ collapsedLines: userMessageCollapsedLines,
2161
+ collapseThreshold: userMessageCollapseThreshold,
2162
+ content: resolvedUserContent,
2163
+ showLessLabel: userMessageShowLessLabel,
2164
+ showMoreLabel: userMessageShowMoreLabel
2165
+ }
2166
+ ) : null : null
2167
+ ]
2168
+ }
2169
+ ) : null,
2170
+ renderedActions ? /* @__PURE__ */ jsx10("div", { className: cn(dir === "rtl" ? "mt-1 h-9 w-full" : "mt-1 h-9 w-full -translate-x-2", actionsClassName), children: renderedActions }) : null
2171
+ ] });
2172
+ }
2173
+
2174
+ // src/ui/agent-chat-context-status.tsx
2175
+ import * as React9 from "react";
2176
+ import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
2177
+ var COMPACT_TOKENS = new Intl.NumberFormat("en-US", { notation: "compact" });
2178
+ var PERCENT = new Intl.NumberFormat("en-US", {
2179
+ style: "percent",
2180
+ maximumFractionDigits: 1
2181
+ });
2182
+ var EXACT_SOURCES = /* @__PURE__ */ new Set(["exact", "provider_usage"]);
2183
+ function contextStatusMetrics(contextWindow) {
2184
+ const maximum = Number(contextWindow?.max_tokens || 0);
2185
+ const input = Math.max(0, Number(contextWindow?.input_tokens || 0));
2186
+ const admissionInput = Math.max(0, Number(contextWindow?.admission_input_tokens ?? input));
2187
+ const usageRatio = Math.max(0, Math.min(1, Number(
2188
+ contextWindow?.usage_ratio ?? input / maximum
2189
+ )));
2190
+ const admissionRatio = Math.max(0, Math.min(1, Number(
2191
+ contextWindow?.admission_ratio ?? admissionInput / maximum
2192
+ )));
2193
+ return { maximum, input, admissionInput, usageRatio, admissionRatio };
2194
+ }
2195
+ function AgentChatContextStatus({
2196
+ className,
2197
+ contextWindow
2198
+ }) {
2199
+ const tooltipId = React9.useId();
2200
+ const [open, setOpen] = React9.useState(false);
2201
+ const { maximum, input, admissionInput, usageRatio, admissionRatio } = contextStatusMetrics(contextWindow);
2202
+ const percentage = Math.round(usageRatio * 100);
2203
+ const exact = EXACT_SOURCES.has(contextWindow?.source || "unknown");
2204
+ if (!Number.isFinite(maximum) || maximum <= 0) return null;
2205
+ return /* @__PURE__ */ jsxs7(
2206
+ "div",
2207
+ {
2208
+ className: cn("a24-context-status", className),
2209
+ onBlurCapture: (event) => {
2210
+ if (!event.currentTarget.contains(event.relatedTarget)) setOpen(false);
2211
+ },
2212
+ onFocusCapture: () => setOpen(true),
2213
+ onMouseEnter: () => setOpen(true),
2214
+ onMouseLeave: () => setOpen(false),
2215
+ children: [
2216
+ /* @__PURE__ */ jsxs7(
2217
+ "button",
2218
+ {
2219
+ "aria-describedby": open ? tooltipId : void 0,
2220
+ "aria-label": `Context window ${percentage}% used`,
2221
+ className: "a24-context-status__trigger",
2222
+ onKeyDown: (event) => {
2223
+ if (event.key === "Escape") setOpen(false);
2224
+ },
2225
+ type: "button",
2226
+ children: [
2227
+ /* @__PURE__ */ jsxs7("svg", { "aria-hidden": "true", className: "a24-context-status__ring", viewBox: "0 0 24 24", children: [
2228
+ /* @__PURE__ */ jsx11("circle", { className: "a24-context-status__track", cx: "12", cy: "12", r: "10" }),
2229
+ /* @__PURE__ */ jsx11(
2230
+ "circle",
2231
+ {
2232
+ className: "a24-context-status__value",
2233
+ cx: "12",
2234
+ cy: "12",
2235
+ pathLength: "100",
2236
+ r: "10",
2237
+ strokeDasharray: `${percentage} 100`
2238
+ }
2239
+ )
2240
+ ] }),
2241
+ /* @__PURE__ */ jsxs7("span", { children: [
2242
+ percentage,
2243
+ "%"
2244
+ ] })
2245
+ ]
2246
+ }
2247
+ ),
2248
+ open ? /* @__PURE__ */ jsxs7("div", { className: "a24-context-status__popover", id: tooltipId, role: "tooltip", children: [
2249
+ /* @__PURE__ */ jsx11("span", { className: "a24-context-status__sr-only", children: "Context window" }),
2250
+ /* @__PURE__ */ jsxs7("strong", { children: [
2251
+ PERCENT.format(usageRatio),
2252
+ " context used"
2253
+ ] }),
2254
+ /* @__PURE__ */ jsxs7("span", { children: [
2255
+ exact ? "Input" : "Estimated input",
2256
+ ": ",
2257
+ COMPACT_TOKENS.format(input),
2258
+ " / ",
2259
+ COMPACT_TOKENS.format(maximum),
2260
+ " tokens"
2261
+ ] }),
2262
+ admissionInput !== input ? /* @__PURE__ */ jsxs7("span", { children: [
2263
+ "Safety bound: ",
2264
+ COMPACT_TOKENS.format(admissionInput),
2265
+ " tokens (",
2266
+ PERCENT.format(admissionRatio),
2267
+ ")"
2268
+ ] }) : null
2269
+ ] }) : null
2270
+ ]
2271
+ }
2272
+ );
2273
+ }
39
2274
 
40
2275
  // src/ui/bubble.tsx
41
- import { cva } from "class-variance-authority";
42
- import { Slot } from "@radix-ui/react-slot";
43
- import { jsx } from "react/jsx-runtime";
2276
+ import { cva as cva2 } from "class-variance-authority";
2277
+ import { Slot as Slot2 } from "@radix-ui/react-slot";
2278
+ import { jsx as jsx12 } from "react/jsx-runtime";
44
2279
  function BubbleGroup({ className, ...props }) {
45
- return /* @__PURE__ */ jsx(
2280
+ return /* @__PURE__ */ jsx12(
46
2281
  "div",
47
2282
  {
48
2283
  "data-slot": "bubble-group",
@@ -51,7 +2286,7 @@ function BubbleGroup({ className, ...props }) {
51
2286
  }
52
2287
  );
53
2288
  }
54
- var bubbleVariants = cva(
2289
+ var bubbleVariants = cva2(
55
2290
  "group/bubble relative flex w-fit max-w-[80%] min-w-0 flex-col gap-1 group-data-[align=end]/message:self-end data-[align=end]:self-end data-[variant=ghost]:max-w-full",
56
2291
  {
57
2292
  variants: {
@@ -76,7 +2311,7 @@ function Bubble({
76
2311
  className,
77
2312
  ...props
78
2313
  }) {
79
- return /* @__PURE__ */ jsx(
2314
+ return /* @__PURE__ */ jsx12(
80
2315
  "div",
81
2316
  {
82
2317
  "data-slot": "bubble",
@@ -92,8 +2327,8 @@ function BubbleContent({
92
2327
  className,
93
2328
  ...props
94
2329
  }) {
95
- const Comp = asChild ? Slot : "div";
96
- return /* @__PURE__ */ jsx(
2330
+ const Comp = asChild ? Slot2 : "div";
2331
+ return /* @__PURE__ */ jsx12(
97
2332
  Comp,
98
2333
  {
99
2334
  "data-slot": "bubble-content",
@@ -105,7 +2340,7 @@ function BubbleContent({
105
2340
  }
106
2341
  );
107
2342
  }
108
- var bubbleReactionsVariants = cva(
2343
+ var bubbleReactionsVariants = cva2(
109
2344
  "absolute z-10 flex w-fit shrink-0 items-center justify-center gap-1 rounded-full bg-muted px-1.5 py-0.5 text-sm ring-3 ring-card has-[button]:p-0",
110
2345
  {
111
2346
  variants: {
@@ -130,7 +2365,7 @@ function BubbleReactions({
130
2365
  className,
131
2366
  ...props
132
2367
  }) {
133
- return /* @__PURE__ */ jsx(
2368
+ return /* @__PURE__ */ jsx12(
134
2369
  "div",
135
2370
  {
136
2371
  "data-slot": "bubble-reactions",
@@ -143,10 +2378,10 @@ function BubbleReactions({
143
2378
  }
144
2379
 
145
2380
  // src/ui/marker.tsx
146
- import { cva as cva2 } from "class-variance-authority";
147
- import { Slot as Slot2 } from "@radix-ui/react-slot";
148
- import { jsx as jsx2 } from "react/jsx-runtime";
149
- var markerVariants = cva2(
2381
+ import { cva as cva3 } from "class-variance-authority";
2382
+ import { Slot as Slot3 } from "@radix-ui/react-slot";
2383
+ import { jsx as jsx13 } from "react/jsx-runtime";
2384
+ var markerVariants = cva3(
150
2385
  "group/marker relative flex min-h-4 w-full items-center gap-2 text-left text-sm text-muted-foreground [&_svg:not([class*='size-'])]:size-4 [a]:underline [a]:underline-offset-3 [a]:hover:text-foreground",
151
2386
  {
152
2387
  variants: {
@@ -164,8 +2399,8 @@ function Marker({
164
2399
  asChild = false,
165
2400
  ...props
166
2401
  }) {
167
- const Comp = asChild ? Slot2 : "div";
168
- return /* @__PURE__ */ jsx2(
2402
+ const Comp = asChild ? Slot3 : "div";
2403
+ return /* @__PURE__ */ jsx13(
169
2404
  Comp,
170
2405
  {
171
2406
  "data-slot": "marker",
@@ -176,7 +2411,7 @@ function Marker({
176
2411
  );
177
2412
  }
178
2413
  function MarkerIcon({ className, ...props }) {
179
- return /* @__PURE__ */ jsx2(
2414
+ return /* @__PURE__ */ jsx13(
180
2415
  "span",
181
2416
  {
182
2417
  "data-slot": "marker-icon",
@@ -190,7 +2425,7 @@ function MarkerIcon({ className, ...props }) {
190
2425
  );
191
2426
  }
192
2427
  function MarkerContent({ className, ...props }) {
193
- return /* @__PURE__ */ jsx2(
2428
+ return /* @__PURE__ */ jsx13(
194
2429
  "span",
195
2430
  {
196
2431
  "data-slot": "marker-content",
@@ -204,7 +2439,7 @@ function MarkerContent({ className, ...props }) {
204
2439
  }
205
2440
 
206
2441
  // src/ui/mcp-connect-required-card.tsx
207
- import { jsx as jsx3, jsxs } from "react/jsx-runtime";
2442
+ import { jsx as jsx14, jsxs as jsxs8 } from "react/jsx-runtime";
208
2443
  var STATUS_LABELS = {
209
2444
  pending: "Connect",
210
2445
  connecting: "Connecting",
@@ -228,7 +2463,7 @@ function McpConnectRequiredCard({
228
2463
  const isBusy = loading || status === "connecting" || status === "resuming";
229
2464
  const isConnected = status === "connected";
230
2465
  const buttonDisabled = disabled || isBusy || isConnected || !onConnect;
231
- return /* @__PURE__ */ jsx3(
2466
+ return /* @__PURE__ */ jsx14(
232
2467
  "div",
233
2468
  {
234
2469
  "data-agents24-mcp-connect-card": true,
@@ -238,13 +2473,13 @@ function McpConnectRequiredCard({
238
2473
  className
239
2474
  ),
240
2475
  ...props,
241
- children: /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-start justify-between gap-3", children: [
242
- /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
243
- /* @__PURE__ */ jsx3("div", { className: "truncate text-sm font-medium", children: label }),
244
- /* @__PURE__ */ jsx3("div", { className: "mt-1 text-sm text-muted-foreground", children: message || "Connect your account to continue." }),
245
- status === "error" && error ? /* @__PURE__ */ jsx3("div", { className: "mt-2 text-sm text-destructive", children: error }) : null
2476
+ children: /* @__PURE__ */ jsxs8("div", { className: "flex min-w-0 items-start justify-between gap-3", children: [
2477
+ /* @__PURE__ */ jsxs8("div", { className: "min-w-0", children: [
2478
+ /* @__PURE__ */ jsx14("div", { className: "truncate text-sm font-medium", children: label }),
2479
+ /* @__PURE__ */ jsx14("div", { className: "mt-1 text-sm text-muted-foreground", children: message || "Connect your account to continue." }),
2480
+ status === "error" && error ? /* @__PURE__ */ jsx14("div", { className: "mt-2 text-sm text-destructive", children: error }) : null
246
2481
  ] }),
247
- /* @__PURE__ */ jsx3(
2482
+ /* @__PURE__ */ jsx14(
248
2483
  "button",
249
2484
  {
250
2485
  type: "button",
@@ -261,13 +2496,86 @@ function McpConnectRequiredCard({
261
2496
  }
262
2497
  );
263
2498
  }
2499
+
2500
+ // src/ui/use-audio-recorder.ts
2501
+ import * as React10 from "react";
2502
+ var RECORDING_CANDIDATES = ["audio/webm;codecs=opus", "audio/webm", "audio/mp4"];
2503
+ function recordingExtension(mimeType) {
2504
+ return mimeType.startsWith("audio/mp4") ? "m4a" : "webm";
2505
+ }
2506
+ function useAudioRecorder(onRecorded, options = {}) {
2507
+ const recorderRef = React10.useRef(null);
2508
+ const streamRef = React10.useRef(null);
2509
+ const onRecordedRef = React10.useRef(onRecorded);
2510
+ const onStreamChangeRef = React10.useRef(options.onStreamChange);
2511
+ onRecordedRef.current = onRecorded;
2512
+ onStreamChangeRef.current = options.onStreamChange;
2513
+ const [isRecording, setIsRecording] = React10.useState(false);
2514
+ const [error, setError] = React10.useState(null);
2515
+ const stop = React10.useCallback(() => recorderRef.current?.stop(), []);
2516
+ const start = React10.useCallback(async () => {
2517
+ setError(null);
2518
+ if (!navigator.mediaDevices?.getUserMedia || typeof MediaRecorder === "undefined") {
2519
+ setError("Audio recording is not supported in this browser.");
2520
+ return;
2521
+ }
2522
+ try {
2523
+ const mimeType = RECORDING_CANDIDATES.find((candidate) => MediaRecorder.isTypeSupported(candidate));
2524
+ if (!mimeType) {
2525
+ setError("Audio recording is not supported in this browser.");
2526
+ return;
2527
+ }
2528
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
2529
+ onStreamChangeRef.current?.(stream);
2530
+ const recorder = new MediaRecorder(stream, { mimeType });
2531
+ const chunks = [];
2532
+ recorder.ondataavailable = (event) => {
2533
+ if (event.data.size) chunks.push(event.data);
2534
+ };
2535
+ recorder.onstop = () => {
2536
+ const actualType = recorder.mimeType || "audio/webm";
2537
+ onRecordedRef.current(new File([new Blob(chunks, { type: actualType })], `recording-${Date.now()}.${recordingExtension(actualType)}`, { type: actualType }));
2538
+ stream.getTracks().forEach((track) => track.stop());
2539
+ onStreamChangeRef.current?.(null);
2540
+ recorderRef.current = null;
2541
+ streamRef.current = null;
2542
+ setIsRecording(false);
2543
+ };
2544
+ recorderRef.current = recorder;
2545
+ streamRef.current = stream;
2546
+ recorder.start();
2547
+ setIsRecording(true);
2548
+ } catch {
2549
+ streamRef.current?.getTracks().forEach((track) => track.stop());
2550
+ onStreamChangeRef.current?.(null);
2551
+ setError("Microphone access is unavailable.");
2552
+ }
2553
+ }, []);
2554
+ React10.useEffect(() => () => {
2555
+ recorderRef.current?.stop();
2556
+ streamRef.current?.getTracks().forEach((track) => track.stop());
2557
+ onStreamChangeRef.current?.(null);
2558
+ }, []);
2559
+ return { error, isRecording, start, stop };
2560
+ }
264
2561
  export {
2562
+ AgentChatActivityRow,
265
2563
  AgentChatComposer,
2564
+ AgentChatContextCompressionRow,
2565
+ AgentChatContextStatus,
266
2566
  AgentChatDefaultActions,
267
2567
  AgentChatMessage,
268
- DefaultPartRow as AgentChatPartRow,
2568
+ AgentChatModelTurnRow,
2569
+ AgentChatNodeActivityRow,
2570
+ AgentChatPart,
2571
+ AgentChatSubagentGroup,
2572
+ AgentChatToolGroup,
2573
+ AgentChatToolPart,
2574
+ AgentChatToolRow,
2575
+ AgentChatToolSession,
269
2576
  AgentChatUserMessageContent,
270
2577
  AgentResponseTimeline,
2578
+ AskUserInteraction,
271
2579
  Attachment,
272
2580
  AttachmentAction,
273
2581
  AttachmentActions,
@@ -304,14 +2612,21 @@ export {
304
2612
  MessageResponse,
305
2613
  Message as ShadcnMessage,
306
2614
  MessageContent as ShadcnMessageContent,
2615
+ agentChatToolLabel,
307
2616
  attachmentMediaVariants,
308
2617
  attachmentVariants,
309
2618
  bubbleVariants,
310
2619
  cn,
2620
+ contextStatusMetrics,
311
2621
  hitlActionLabel,
312
2622
  hitlTitle,
2623
+ isAgentChatSubagent,
2624
+ isAgentChatToolRunning,
2625
+ latestRunningAgentChatToolId,
313
2626
  markerVariants,
2627
+ partitionAgentChatToolTimeline,
314
2628
  resolvedHitlLabel,
315
- shouldCollapseUserMessage
2629
+ shouldCollapseUserMessage,
2630
+ useAudioRecorder
316
2631
  };
317
2632
  //# sourceMappingURL=index.js.map