@agents24/chat-react 0.3.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/LICENSE +19 -0
  2. package/README.md +24 -64
  3. package/compatibility.json +54 -0
  4. package/dist/active-content.d.ts +10 -0
  5. package/dist/{chunk-EWZO4QJI.js → chunk-CHAU7FNW.js} +3 -3
  6. package/dist/chunk-CHAU7FNW.js.map +1 -0
  7. package/dist/{chunk-FFO6KEB4.js → chunk-LI67M74F.js} +98 -12
  8. package/dist/chunk-LI67M74F.js.map +1 -0
  9. package/dist/chunk-R7WFL3YR.js +1327 -0
  10. package/dist/chunk-R7WFL3YR.js.map +1 -0
  11. package/dist/index.cjs +66 -1852
  12. package/dist/index.cjs.map +1 -1
  13. package/dist/index.d.ts +0 -6
  14. package/dist/index.js +6 -1841
  15. package/dist/index.js.map +1 -1
  16. package/dist/latest-thread-scroller.d.ts +7 -7
  17. package/dist/mcp-oauth.d.ts +3 -1
  18. package/dist/renderers.d.ts +3 -3
  19. package/dist/styles.css +493 -0
  20. package/dist/templates/agent-chat-app.d.ts +18 -0
  21. package/dist/templates/agent-chat-layout.d.ts +77 -0
  22. package/dist/templates/agent-chat-shell.d.ts +1 -1
  23. package/dist/templates/appearance-control.d.ts +5 -0
  24. package/dist/templates/index.cjs +2028 -18
  25. package/dist/templates/index.cjs.map +1 -1
  26. package/dist/templates/index.d.ts +3 -0
  27. package/dist/templates/index.js +652 -11
  28. package/dist/templates/index.js.map +1 -1
  29. package/dist/types.d.ts +12 -380
  30. package/dist/ui/agent-chat-actions.d.ts +1 -2
  31. package/dist/ui/agent-chat-composer.d.ts +2 -1
  32. package/dist/ui/agent-chat-message.d.ts +3 -3
  33. package/dist/ui/agent-response-timeline.d.ts +2 -2
  34. package/dist/ui/attachment.d.ts +9 -9
  35. package/dist/ui/bubble.d.ts +4 -4
  36. package/dist/ui/index.cjs +138 -44
  37. package/dist/ui/index.cjs.map +1 -1
  38. package/dist/ui/index.js +62 -1220
  39. package/dist/ui/index.js.map +1 -1
  40. package/dist/ui/marker.d.ts +3 -3
  41. package/dist/ui/mcp-connect-required-card.d.ts +1 -1
  42. package/dist/ui/message-response.d.ts +1 -1
  43. package/dist/ui/message.d.ts +6 -6
  44. package/package.json +27 -8
  45. package/dist/chunk-EWZO4QJI.js.map +0 -1
  46. package/dist/chunk-FFO6KEB4.js.map +0 -1
  47. package/dist/chunk-UU7OXHL3.js +0 -11
  48. package/dist/chunk-UU7OXHL3.js.map +0 -1
  49. package/dist/context-window.d.ts +0 -38
  50. package/dist/controller-actions.d.ts +0 -45
  51. package/dist/controller-helpers.d.ts +0 -27
  52. package/dist/controller-hitl.d.ts +0 -22
  53. package/dist/controller-options.d.ts +0 -15
  54. package/dist/controller-thread-events.d.ts +0 -7
  55. package/dist/controller.d.ts +0 -5
  56. package/dist/message-lifecycle.d.ts +0 -15
  57. package/dist/model.d.ts +0 -30
  58. package/dist/sse.d.ts +0 -5
  59. package/dist/transport.d.ts +0 -43
@@ -6,18 +6,18 @@ declare const attachmentVariants: (props?: ({
6
6
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
7
7
  declare function Attachment({ className, state, size, orientation, ...props }: React.ComponentProps<"div"> & VariantProps<typeof attachmentVariants> & {
8
8
  state?: "idle" | "uploading" | "processing" | "error" | "done";
9
- }): React.JSX.Element;
9
+ }): import("react/jsx-runtime").JSX.Element;
10
10
  declare const attachmentMediaVariants: (props?: ({
11
11
  variant?: "image" | "icon" | null | undefined;
12
12
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
13
- declare function AttachmentMedia({ className, variant, ...props }: React.ComponentProps<"div"> & VariantProps<typeof attachmentMediaVariants>): React.JSX.Element;
14
- declare function AttachmentContent({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
15
- declare function AttachmentTitle({ className, ...props }: React.ComponentProps<"span">): React.JSX.Element;
16
- declare function AttachmentDescription({ className, ...props }: React.ComponentProps<"span">): React.JSX.Element;
17
- declare function AttachmentActions({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
18
- declare function AttachmentAction({ className, type, ...props }: React.ComponentProps<"button">): React.JSX.Element;
13
+ declare function AttachmentMedia({ className, variant, ...props }: React.ComponentProps<"div"> & VariantProps<typeof attachmentMediaVariants>): import("react/jsx-runtime").JSX.Element;
14
+ declare function AttachmentContent({ className, ...props }: React.ComponentProps<"div">): import("react/jsx-runtime").JSX.Element;
15
+ declare function AttachmentTitle({ className, ...props }: React.ComponentProps<"span">): import("react/jsx-runtime").JSX.Element;
16
+ declare function AttachmentDescription({ className, ...props }: React.ComponentProps<"span">): import("react/jsx-runtime").JSX.Element;
17
+ declare function AttachmentActions({ className, ...props }: React.ComponentProps<"div">): import("react/jsx-runtime").JSX.Element;
18
+ declare function AttachmentAction({ className, type, ...props }: React.ComponentProps<"button">): import("react/jsx-runtime").JSX.Element;
19
19
  declare function AttachmentTrigger({ className, asChild, type, ...props }: React.ComponentProps<"button"> & {
20
20
  asChild?: boolean;
21
- }): React.JSX.Element;
22
- declare function AttachmentGroup({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
21
+ }): import("react/jsx-runtime").JSX.Element;
22
+ declare function AttachmentGroup({ className, ...props }: React.ComponentProps<"div">): import("react/jsx-runtime").JSX.Element;
23
23
  export { Attachment, AttachmentGroup, AttachmentMedia, AttachmentContent, AttachmentTitle, AttachmentDescription, AttachmentActions, AttachmentAction, AttachmentTrigger, attachmentVariants, attachmentMediaVariants, };
@@ -1,17 +1,17 @@
1
1
  import * as React from "react";
2
2
  import { type VariantProps } from "class-variance-authority";
3
- declare function BubbleGroup({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
3
+ declare function BubbleGroup({ className, ...props }: React.ComponentProps<"div">): import("react/jsx-runtime").JSX.Element;
4
4
  declare const bubbleVariants: (props?: ({
5
5
  variant?: "default" | "secondary" | "muted" | "tinted" | "outline" | "ghost" | "destructive" | null | undefined;
6
6
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
7
7
  declare function Bubble({ variant, align, className, ...props }: React.ComponentProps<"div"> & VariantProps<typeof bubbleVariants> & {
8
8
  align?: "start" | "end";
9
- }): React.JSX.Element;
9
+ }): import("react/jsx-runtime").JSX.Element;
10
10
  declare function BubbleContent({ asChild, className, ...props }: React.ComponentProps<"div"> & {
11
11
  asChild?: boolean;
12
- }): React.JSX.Element;
12
+ }): import("react/jsx-runtime").JSX.Element;
13
13
  declare function BubbleReactions({ side, align, className, ...props }: React.ComponentProps<"div"> & {
14
14
  align?: "start" | "end";
15
15
  side?: "top" | "bottom";
16
- }): React.JSX.Element;
16
+ }): import("react/jsx-runtime").JSX.Element;
17
17
  export { BubbleGroup, Bubble, BubbleContent, BubbleReactions, bubbleVariants };
package/dist/ui/index.cjs CHANGED
@@ -104,7 +104,8 @@ function createComposerFile(file) {
104
104
  type: "file",
105
105
  url: URL.createObjectURL(file),
106
106
  filename: file.name || "attachment",
107
- mediaType: file.type || "application/octet-stream"
107
+ mediaType: file.type || "application/octet-stream",
108
+ source: file
108
109
  };
109
110
  }
110
111
  function revokeComposerFiles(files) {
@@ -120,7 +121,7 @@ function formatFileCount(files) {
120
121
  return `${files.length} files`;
121
122
  }
122
123
  var pillButtonClass = "inline-flex size-8 shrink-0 items-center justify-center gap-2 rounded-full border-0 bg-neutral-100 text-sm text-neutral-600 shadow-none transition-colors hover:bg-neutral-200 hover:text-neutral-800 focus-visible:border-neutral-300 focus-visible:outline-none focus-visible:ring-0 disabled:pointer-events-none disabled:opacity-50 dark:bg-neutral-800 dark:text-neutral-300 dark:hover:bg-neutral-700";
123
- var submitButtonClass = "inline-flex size-9 shrink-0 items-center justify-center gap-2 rounded-full border-0 bg-neutral-900 text-sm text-white shadow-none transition-all duration-200 hover:bg-neutral-800 hover:text-white focus-visible:border-neutral-300 focus-visible:outline-none focus-visible:ring-0 disabled:pointer-events-none disabled:opacity-50 dark:bg-neutral-100 dark:text-neutral-900 dark:hover:bg-white";
124
+ var submitButtonClass = "inline-flex size-9 shrink-0 items-center justify-center gap-2 rounded-full border-0 bg-neutral-900 text-sm text-white shadow-none transition-[background-color,color,opacity,transform] duration-200 hover:bg-neutral-800 hover:text-white focus-visible:border-neutral-300 focus-visible:outline-none focus-visible:ring-0 disabled:pointer-events-none disabled:opacity-50 dark:bg-neutral-100 dark:text-neutral-900 dark:hover:bg-white";
124
125
  function AgentChatComposer({
125
126
  accept,
126
127
  allowAttachments = true,
@@ -132,7 +133,7 @@ function AgentChatComposer({
132
133
  onAttachmentCountChange,
133
134
  onStop,
134
135
  onSubmit,
135
- placeholder = "Send follow-up",
136
+ placeholder = "Send follow-up\u2026",
136
137
  textareaRef
137
138
  }) {
138
139
  const fileInputRef = React.useRef(null);
@@ -206,19 +207,20 @@ function AgentChatComposer({
206
207
  void submit();
207
208
  }
208
209
  };
209
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: cn("relative mx-auto w-full max-w-3xl overflow-visible", className), children: [
210
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: cn("a24-composer relative mx-auto w-full max-w-3xl overflow-visible", className), children: [
210
211
  files.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
211
212
  "div",
212
213
  {
213
214
  "data-testid": "agent-chat-composer-attachments",
215
+ "data-layout": attachmentLayout,
214
216
  className: cn(
215
- "flex min-w-0 gap-2 py-1",
217
+ "a24-composer__attachments flex min-w-0 gap-2 py-1",
216
218
  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"
217
219
  ),
218
220
  children: files.map((file) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
219
221
  "div",
220
222
  {
221
- className: "group relative flex h-8 max-w-[min(20rem,calc(100vw-2rem))] cursor-default select-none items-center gap-1.5 rounded-md border border-neutral-200 bg-white px-1.5 text-sm font-medium text-foreground transition-colors hover:bg-neutral-50",
223
+ className: "a24-composer__attachment group relative flex h-8 max-w-[min(20rem,calc(100vw-2rem))] cursor-default select-none items-center gap-1.5 rounded-md border border-neutral-200 bg-white px-1.5 text-sm font-medium text-foreground transition-colors hover:bg-neutral-50",
222
224
  children: [
223
225
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "flex size-5 shrink-0 items-center justify-center rounded bg-background text-muted-foreground", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Paperclip, { className: "size-3" }) }),
224
226
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "min-w-0 flex-1 truncate", children: file.filename }),
@@ -226,7 +228,7 @@ function AgentChatComposer({
226
228
  "button",
227
229
  {
228
230
  "aria-label": `Remove ${file.filename}`,
229
- className: "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",
231
+ 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",
230
232
  onClick: () => removeFile(file.id),
231
233
  type: "button",
232
234
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.X, { className: "size-3" })
@@ -241,14 +243,15 @@ function AgentChatComposer({
241
243
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
242
244
  "div",
243
245
  {
246
+ "data-expanded": isExpanded ? "" : void 0,
244
247
  className: cn(
245
- "relative w-full border border-neutral-200 bg-white shadow-sm transition-colors duration-200 focus-within:border-neutral-300 dark:border-neutral-700 dark:bg-card dark:focus-within:border-neutral-600",
248
+ "a24-composer__surface relative w-full border border-neutral-200 bg-white shadow-sm transition-colors duration-200 focus-within:border-neutral-300 dark:border-neutral-700 dark:bg-card dark:focus-within:border-neutral-600",
246
249
  isExpanded ? "rounded-[18px] p-2 pb-1.5" : "rounded-full px-2 py-[3px]"
247
250
  ),
248
251
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
249
252
  "div",
250
253
  {
251
- className: "w-full",
254
+ className: "a24-composer__grid w-full",
252
255
  style: {
253
256
  alignItems: "center",
254
257
  display: "grid",
@@ -257,7 +260,7 @@ function AgentChatComposer({
257
260
  gap: isExpanded ? "3px 10px" : "8px"
258
261
  },
259
262
  children: [
260
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { gridArea: "plus" }, className: "flex shrink-0 items-center justify-center", children: [
263
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { gridArea: "plus" }, className: "a24-composer__attach-slot flex shrink-0 items-center justify-center", children: [
261
264
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
262
265
  "input",
263
266
  {
@@ -266,6 +269,7 @@ function AgentChatComposer({
266
269
  "aria-label": "Attach files",
267
270
  className: "hidden",
268
271
  multiple: true,
272
+ name: "attachments",
269
273
  onChange: handleFilesChange,
270
274
  type: "file"
271
275
  }
@@ -274,7 +278,7 @@ function AgentChatComposer({
274
278
  "button",
275
279
  {
276
280
  "aria-label": "Attach files",
277
- className: pillButtonClass,
281
+ className: cn("a24-composer__attach", pillButtonClass),
278
282
  disabled: disabled || isSubmitting || isRunning || !allowAttachments,
279
283
  onClick: () => fileInputRef.current?.click(),
280
284
  title: "Attach files",
@@ -283,16 +287,18 @@ function AgentChatComposer({
283
287
  }
284
288
  )
285
289
  ] }),
286
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { gridArea: "textarea" }, className: "min-w-0 w-full", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
290
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { gridArea: "textarea" }, className: "a24-composer__textarea-slot min-w-0 w-full", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
287
291
  "textarea",
288
292
  {
289
293
  ref: setTextareaRef,
290
294
  "aria-label": "Message",
295
+ autoComplete: "off",
291
296
  className: cn(
292
- "w-full resize-none border-0 bg-transparent px-1 text-[15px] leading-relaxed text-foreground shadow-none outline-none placeholder:text-neutral-400 focus-visible:ring-0 scrollbar-thin",
297
+ "a24-composer__textarea w-full resize-none border-0 bg-transparent px-1 text-[15px] leading-relaxed text-foreground shadow-none outline-none placeholder:text-neutral-400 focus-visible:ring-0 scrollbar-thin",
293
298
  isExpanded ? "min-h-9 max-h-[224px] py-1" : "min-h-9 h-9 max-h-9 py-2"
294
299
  ),
295
300
  disabled: disabled || isSubmitting || isRunning,
301
+ name: "message",
296
302
  onChange: (event) => setText(event.target.value),
297
303
  onKeyDown: handleKeyDown,
298
304
  placeholder,
@@ -300,14 +306,14 @@ function AgentChatComposer({
300
306
  value: text
301
307
  }
302
308
  ) }),
303
- isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { gridArea: "toolbar" }, className: "flex min-w-0 items-center gap-1.5", children: inputToolbarContent }) : null,
309
+ isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { gridArea: "toolbar" }, className: "a24-composer__toolbar flex min-w-0 items-center gap-1.5", children: inputToolbarContent }) : null,
304
310
  !isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { gridArea: "composer" }, className: "flex min-w-0 items-center gap-1.5" }) : null,
305
311
  !isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { gridArea: "mic" }, className: "flex shrink-0 items-center justify-end" }) : null,
306
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { gridArea: "submit" }, className: "flex shrink-0 items-center justify-end", children: isRunning ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
312
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { gridArea: "submit" }, className: "a24-composer__submit-slot flex shrink-0 items-center justify-end", children: isRunning ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
307
313
  "button",
308
314
  {
309
315
  "aria-label": "Stop generating",
310
- className: submitButtonClass,
316
+ className: cn("a24-composer__submit", submitButtonClass),
311
317
  disabled: disabled || !onStop,
312
318
  onClick: onStop,
313
319
  title: "Stop",
@@ -318,7 +324,7 @@ function AgentChatComposer({
318
324
  "button",
319
325
  {
320
326
  "aria-label": files.length > 0 ? `Send ${formatFileCount(files)}` : "Send message",
321
- className: cn(submitButtonClass, text.trim().length > 0 && "hover:scale-105"),
327
+ className: cn("a24-composer__submit", submitButtonClass, text.trim().length > 0 && "hover:scale-105"),
322
328
  disabled: !canSubmit,
323
329
  onClick: () => void submit(),
324
330
  title: "Send",
@@ -331,7 +337,7 @@ function AgentChatComposer({
331
337
  )
332
338
  }
333
339
  ),
334
- !isExpanded && inputToolbarContent ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "mt-2 flex items-center gap-2 px-1", children: inputToolbarContent }) : null
340
+ !isExpanded && inputToolbarContent ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "a24-composer__toolbar a24-composer__toolbar--external mt-2 flex items-center gap-2 px-1", children: inputToolbarContent }) : null
335
341
  ] });
336
342
  }
337
343
 
@@ -615,8 +621,8 @@ function ChatMessage(input) {
615
621
  const messageProps = Object.assign({}, props);
616
622
  messageProps.align = resolvedAlign;
617
623
  messageProps.className = cn(
618
- "group flex w-full max-w-[80%] flex-col gap-2",
619
- isUser ? cn("is-user justify-end", isRtl ? "mr-auto" : "ml-auto") : "is-assistant",
624
+ "group flex w-full flex-col gap-2",
625
+ isUser ? cn("is-user max-w-[80%] justify-end", isRtl ? "mr-auto" : "ml-auto") : "is-assistant max-w-full",
620
626
  className
621
627
  );
622
628
  return React2.createElement(Message, messageProps);
@@ -627,13 +633,13 @@ function ChatMessageContent({
627
633
  }) {
628
634
  return React2.createElement("div", {
629
635
  className: cn(
630
- "is-user:dark flex w-fit max-w-full min-w-0 flex-col gap-2 overflow-hidden text-sm",
636
+ "is-user:dark flex w-full max-w-full min-w-0 flex-col gap-2 overflow-hidden text-sm group-[.is-user]:w-fit group-[.is-user]:overflow-visible",
631
637
  "group-[.is-user]:rounded-lg group-[.is-user]:bg-secondary group-[.is-user]:px-4 group-[.is-user]:py-3 group-[.is-user]:text-foreground",
632
638
  "group-[.is-assistant]:w-full group-[.is-assistant]:max-w-full group-[.is-assistant]:text-foreground",
633
639
  props.dir === "rtl" ? "group-[.is-user]:mr-auto" : "group-[.is-user]:ml-auto",
634
640
  className
635
641
  ),
636
- "data-slot": "message-content",
642
+ "data-slot": "bubble-content",
637
643
  ...props
638
644
  });
639
645
  }
@@ -804,6 +810,27 @@ var import_lucide_react3 = require("lucide-react");
804
810
  var React3 = __toESM(require("react"), 1);
805
811
  var import_streamdown = require("streamdown");
806
812
 
813
+ // src/active-content.ts
814
+ var CHAT_MARKDOWN_HARDEN_OPTIONS = Object.freeze({
815
+ allowDataImages: false,
816
+ allowedImagePrefixes: [],
817
+ allowedLinkPrefixes: ["*"],
818
+ allowedProtocols: [],
819
+ defaultOrigin: void 0,
820
+ imageBlockPolicy: "text-only",
821
+ linkBlockPolicy: "text-only"
822
+ });
823
+ function safeChatMarkdownHref(value) {
824
+ if (!value) return void 0;
825
+ if (value.startsWith("#")) return value;
826
+ try {
827
+ const url = new URL(value);
828
+ return url.protocol === "https:" || url.protocol === "http:" ? url.href : void 0;
829
+ } catch {
830
+ return void 0;
831
+ }
832
+ }
833
+
807
834
  // src/streaming-text.ts
808
835
  var import_react = require("react");
809
836
  var DEFAULT_COMPLETED_TEXT_CACHE_SIZE = 200;
@@ -830,7 +857,7 @@ function useStreamingText({
830
857
  const cacheAdapter = cache === false ? null : cache;
831
858
  const [displayedText, setDisplayedText] = (0, import_react.useState)(() => {
832
859
  const cachedText = cacheAdapter?.get(id);
833
- return isStreaming && cachedText !== text ? "" : text;
860
+ return cachedText && text.startsWith(cachedText) ? cachedText : text;
834
861
  });
835
862
  const targetRef = (0, import_react.useRef)(text);
836
863
  const displayedRef = (0, import_react.useRef)(displayedText);
@@ -849,7 +876,7 @@ function useStreamingText({
849
876
  if (idRef.current === id) return;
850
877
  idRef.current = id;
851
878
  const cachedText = cacheAdapter?.get(id);
852
- const initial = isStreaming && cachedText !== text ? "" : text;
879
+ const initial = cachedText && text.startsWith(cachedText) ? cachedText : text;
853
880
  shouldAnimateRef.current = isStreaming && initial !== text;
854
881
  displayedRef.current = initial;
855
882
  setDisplayedText(initial);
@@ -935,6 +962,17 @@ function useStreamingText({
935
962
 
936
963
  // src/ui/message-response.tsx
937
964
  var import_jsx_runtime5 = require("react/jsx-runtime");
965
+ function SafeMarkdownLink({ children, href, ...props }) {
966
+ const domProps = { ...props };
967
+ delete domProps.node;
968
+ const safeHref = safeChatMarkdownHref(href);
969
+ if (!safeHref) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children });
970
+ if (safeHref.startsWith("#")) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("a", { ...domProps, href: safeHref, children });
971
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("a", { ...domProps, href: safeHref, rel: "noopener noreferrer", target: "_blank", children });
972
+ }
973
+ function BlockedMarkdownImage({ alt }) {
974
+ return alt ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { "aria-label": `Image blocked: ${alt}`, role: "img", children: alt }) : null;
975
+ }
938
976
  var BLOCK_MARKDOWN_TAGS = /* @__PURE__ */ new Set([
939
977
  "blockquote",
940
978
  "div",
@@ -985,6 +1023,8 @@ function MessageMarkdownParagraph({
985
1023
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { ...domProps, children });
986
1024
  }
987
1025
  var MESSAGE_RESPONSE_COMPONENTS = {
1026
+ a: SafeMarkdownLink,
1027
+ img: BlockedMarkdownImage,
988
1028
  p: MessageMarkdownParagraph
989
1029
  };
990
1030
  var streamdownDefaultRehypePlugins = import_streamdown.defaultRehypePlugins || {};
@@ -996,13 +1036,7 @@ var MESSAGE_RESPONSE_REHYPE_PLUGINS = [
996
1036
  ...streamdownHardenPlugin ? [
997
1037
  [
998
1038
  streamdownHardenPlugin,
999
- {
1000
- allowDataImages: true,
1001
- allowedImagePrefixes: ["*"],
1002
- allowedLinkPrefixes: ["*"],
1003
- allowedProtocols: ["*", "reshet:", "source:"],
1004
- defaultOrigin: void 0
1005
- }
1039
+ CHAT_MARKDOWN_HARDEN_OPTIONS
1006
1040
  ]
1007
1041
  ] : []
1008
1042
  ];
@@ -1031,7 +1065,7 @@ var MessageResponse = React3.memo(
1031
1065
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1032
1066
  import_streamdown.Streamdown,
1033
1067
  {
1034
- className: cn("size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0", className),
1068
+ className: cn("a24-message-response", className),
1035
1069
  components: streamdownComponents,
1036
1070
  rehypePlugins: rehypePlugins || MESSAGE_RESPONSE_REHYPE_PLUGINS,
1037
1071
  ...props,
@@ -1056,7 +1090,10 @@ function hitlActionLabel(part, action) {
1056
1090
  function hitlTitle(part) {
1057
1091
  const presentation = part.presentation || {};
1058
1092
  if (part.hitlKind === "tool_review") return `Run ${optionalString(presentation.tool_name) || "this tool"}?`;
1059
- if (part.hitlKind === "mcp_auth") return `Connect ${optionalString(presentation.server_name) || "this service"} to continue`;
1093
+ if (part.hitlKind === "mcp_auth") {
1094
+ const serverName = optionalString(presentation.server_name);
1095
+ return serverName ? `Connect ${serverName} to continue` : part.message;
1096
+ }
1060
1097
  if (part.hitlKind === "app_data_permission") return "Allow this data change?";
1061
1098
  return part.message;
1062
1099
  }
@@ -1065,7 +1102,7 @@ function resolvedHitlLabel(part) {
1065
1102
  return {
1066
1103
  approved: part.hitlKind === "user_approval" ? "Approved" : "Allowed",
1067
1104
  rejected: part.hitlKind === "user_approval" ? "Rejected" : "Not allowed",
1068
- connected: "Connected",
1105
+ connected: "connected",
1069
1106
  skipped: "Skipped",
1070
1107
  timeout: "Timed out",
1071
1108
  expired: "Timed out",
@@ -1183,30 +1220,39 @@ function HitlPartRow({
1183
1220
  const presentation = part.presentation || {};
1184
1221
  const requireComment = presentation.require_comment === true;
1185
1222
  const isTerminal = part.status !== "pending";
1223
+ const resolvedRef = React4.useRef(null);
1224
+ const wasTerminal = React4.useRef(isTerminal);
1225
+ React4.useEffect(() => {
1226
+ if (isTerminal && !wasTerminal.current) resolvedRef.current?.focus();
1227
+ wasTerminal.current = isTerminal;
1228
+ }, [isTerminal]);
1186
1229
  if (isTerminal) {
1187
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "px-1 py-1 text-sm text-muted-foreground", children: resolvedHitlLabel(part) });
1230
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "a24-hitl a24-hitl--resolved px-1 py-1 text-sm text-muted-foreground", ref: resolvedRef, role: "status", tabIndex: -1, children: resolvedHitlLabel(part) });
1188
1231
  }
1189
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "space-y-2.5 rounded-lg bg-muted/45 px-3 py-2.5", children: [
1190
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "text-sm font-medium text-foreground text-pretty", children: hitlTitle(part) }),
1232
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "a24-hitl space-y-2.5 px-1 py-2", children: [
1233
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "a24-hitl__title text-sm font-medium text-foreground text-pretty", children: hitlTitle(part) }),
1191
1234
  requireComment ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1192
1235
  "textarea",
1193
1236
  {
1194
1237
  "aria-label": "Approval comment",
1195
- className: "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",
1238
+ autoComplete: "off",
1239
+ 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",
1240
+ name: "hitl-comment",
1196
1241
  onChange: (event) => setComment(event.target.value),
1197
- placeholder: "Comment required",
1242
+ placeholder: "Comment required\u2026",
1198
1243
  value: comment
1199
1244
  }
1200
1245
  ) : null,
1201
- state?.error ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "text-sm text-destructive", children: state.error }) : null,
1202
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "flex flex-wrap items-center gap-1.5", children: part.allowedActions.map((action) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1246
+ state?.error ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "a24-hitl__error text-sm text-destructive", children: state.error }) : null,
1247
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "a24-hitl__actions flex flex-wrap items-center gap-1.5", children: part.allowedActions.map((action) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1203
1248
  "button",
1204
1249
  {
1205
1250
  "aria-label": hitlActionLabel(part, action),
1206
1251
  className: cn(
1207
- "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",
1252
+ "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",
1208
1253
  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"
1209
1254
  ),
1255
+ "data-primary": action === "approve" || action === "connect" ? "" : void 0,
1210
1256
  disabled: !onHitlAction || isLoading || state?.status === "connecting" || state?.status === "resuming" || requireComment && !comment.trim(),
1211
1257
  onClick: () => onHitlAction?.(part, action, comment.trim() || void 0),
1212
1258
  type: "button",
@@ -1234,7 +1280,7 @@ function DefaultPartRow({
1234
1280
  if (part.state === "output-error") {
1235
1281
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "text-sm text-destructive", children: part.errorText || "Failed to render UI content." });
1236
1282
  }
1237
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CompactTask, { label: part.state === "input-streaming" ? "Rendering UI block" : "Rendered UI block", loading: part.state === "input-streaming" });
1283
+ return part.bundle ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(SafeUiBlocks, { bundle: part.bundle }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CompactTask, { label: part.state === "input-streaming" ? "Rendering UI block" : "Rendered UI block", loading: part.state === "input-streaming" });
1238
1284
  }
1239
1285
  if (part.kind === "hitl") {
1240
1286
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
@@ -1247,10 +1293,58 @@ function DefaultPartRow({
1247
1293
  }
1248
1294
  );
1249
1295
  }
1250
- if (part.kind === "error") return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "text-sm text-destructive", children: part.errorText });
1296
+ if (part.kind === "error") return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "text-sm text-destructive", role: "alert", children: part.errorText });
1297
+ if (part.kind === "source") {
1298
+ const href = safeExternalUrl(part.url);
1299
+ return href ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("a", { className: "a24-source-link", href, rel: "noopener noreferrer", target: "_blank", children: part.title }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "a24-source-link", children: part.title });
1300
+ }
1301
+ if (part.kind === "citation") {
1302
+ const href = safeExternalUrl(part.url);
1303
+ const label = part.label || "Citation";
1304
+ return href ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("a", { className: "a24-citation", href, rel: "noopener noreferrer", target: "_blank", children: label }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "a24-citation", children: label });
1305
+ }
1306
+ if (part.kind === "attachment") return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "a24-output-attachment", children: part.attachment.filename || "Attachment" });
1251
1307
  if (part.kind === "data") return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { "data-agents24-data-part": part.name, className: "hidden", children: message.id });
1252
1308
  return null;
1253
1309
  }
1310
+ function safeExternalUrl(value) {
1311
+ if (!value) return null;
1312
+ try {
1313
+ const url = new URL(value);
1314
+ return url.protocol === "https:" || url.protocol === "http:" ? url.href : null;
1315
+ } catch {
1316
+ return null;
1317
+ }
1318
+ }
1319
+ function SafeUiBlocks({ bundle }) {
1320
+ const rows = Array.isArray(bundle.rows) ? bundle.rows : [];
1321
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("section", { "aria-label": typeof bundle.title === "string" ? bundle.title : "Agent result", className: "a24-ui-blocks", children: [
1322
+ typeof bundle.title === "string" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h3", { children: bundle.title }) : null,
1323
+ rows.map((rawRow, rowIndex) => {
1324
+ const row = rawRow && typeof rawRow === "object" && !Array.isArray(rawRow) ? rawRow : {};
1325
+ const blocks = Array.isArray(row.blocks) ? row.blocks : [];
1326
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "a24-ui-blocks__row", children: blocks.map((rawBlock, blockIndex) => {
1327
+ const block = rawBlock && typeof rawBlock === "object" && !Array.isArray(rawBlock) ? rawBlock : {};
1328
+ const title = typeof block.title === "string" ? block.title : "Result";
1329
+ const kind = String(block.kind || "note");
1330
+ if (kind === "table" && Array.isArray(block.columns) && Array.isArray(block.rows)) {
1331
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "a24-ui-block", children: [
1332
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h4", { children: title }),
1333
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "a24-ui-table-scroll", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("table", { children: [
1334
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("tr", { children: block.columns.map((column, index) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("th", { children: String(column) }, index)) }) }),
1335
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("tbody", { children: block.rows.map((rawCells, index) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("tr", { children: (Array.isArray(rawCells) ? rawCells : []).map((cell, cellIndex) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("td", { children: String(cell) }, cellIndex)) }, index)) })
1336
+ ] }) })
1337
+ ] }, String(block.id || blockIndex));
1338
+ }
1339
+ const value = block.value ?? block.text ?? "";
1340
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "a24-ui-block", children: [
1341
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h4", { children: title }),
1342
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { children: String(value) })
1343
+ ] }, String(block.id || blockIndex));
1344
+ }) }, `row-${rowIndex}`);
1345
+ })
1346
+ ] });
1347
+ }
1254
1348
  function AgentResponseTimeline({
1255
1349
  getHitlActionState,
1256
1350
  isLoading = false,