@agents24/chat-react 0.5.0 → 0.5.2

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