@agents24/chat-react 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/LICENSE +19 -0
  2. package/README.md +21 -66
  3. package/compatibility.json +54 -0
  4. package/dist/active-content.d.ts +10 -0
  5. package/dist/{chunk-EWZO4QJI.js → chunk-CHAU7FNW.js} +3 -3
  6. package/dist/chunk-CHAU7FNW.js.map +1 -0
  7. package/dist/{chunk-FFO6KEB4.js → chunk-LI67M74F.js} +98 -12
  8. package/dist/chunk-LI67M74F.js.map +1 -0
  9. package/dist/chunk-NLEOWTZG.js +1327 -0
  10. package/dist/chunk-NLEOWTZG.js.map +1 -0
  11. package/dist/index.cjs +66 -1852
  12. package/dist/index.cjs.map +1 -1
  13. package/dist/index.d.ts +0 -6
  14. package/dist/index.js +6 -1841
  15. package/dist/index.js.map +1 -1
  16. package/dist/latest-thread-scroller.d.ts +7 -7
  17. package/dist/mcp-oauth.d.ts +3 -1
  18. package/dist/renderers.d.ts +3 -3
  19. package/dist/styles.css +407 -0
  20. package/dist/templates/agent-chat-app.d.ts +18 -0
  21. package/dist/templates/agent-chat-layout.d.ts +69 -0
  22. package/dist/templates/agent-chat-shell.d.ts +1 -1
  23. package/dist/templates/index.cjs +1904 -18
  24. package/dist/templates/index.cjs.map +1 -1
  25. package/dist/templates/index.d.ts +2 -0
  26. package/dist/templates/index.js +529 -11
  27. package/dist/templates/index.js.map +1 -1
  28. package/dist/types.d.ts +12 -380
  29. package/dist/ui/agent-chat-actions.d.ts +1 -2
  30. package/dist/ui/agent-chat-composer.d.ts +2 -1
  31. package/dist/ui/agent-chat-message.d.ts +3 -3
  32. package/dist/ui/agent-response-timeline.d.ts +2 -2
  33. package/dist/ui/attachment.d.ts +9 -9
  34. package/dist/ui/bubble.d.ts +4 -4
  35. package/dist/ui/index.cjs +137 -43
  36. package/dist/ui/index.cjs.map +1 -1
  37. package/dist/ui/index.js +62 -1220
  38. package/dist/ui/index.js.map +1 -1
  39. package/dist/ui/marker.d.ts +3 -3
  40. package/dist/ui/mcp-connect-required-card.d.ts +1 -1
  41. package/dist/ui/message-response.d.ts +1 -1
  42. package/dist/ui/message.d.ts +6 -6
  43. package/package.json +27 -8
  44. package/dist/chunk-EWZO4QJI.js.map +0 -1
  45. package/dist/chunk-FFO6KEB4.js.map +0 -1
  46. package/dist/chunk-UU7OXHL3.js +0 -11
  47. package/dist/chunk-UU7OXHL3.js.map +0 -1
  48. package/dist/context-window.d.ts +0 -38
  49. package/dist/controller-actions.d.ts +0 -45
  50. package/dist/controller-helpers.d.ts +0 -27
  51. package/dist/controller-hitl.d.ts +0 -22
  52. package/dist/controller-options.d.ts +0 -15
  53. package/dist/controller-thread-events.d.ts +0 -7
  54. package/dist/controller.d.ts +0 -5
  55. package/dist/message-lifecycle.d.ts +0 -15
  56. package/dist/model.d.ts +0 -30
  57. package/dist/sse.d.ts +0 -5
  58. package/dist/transport.d.ts +0 -43
@@ -1,9 +1,15 @@
1
1
  import {
2
- LatestThreadScroller
3
- } from "../chunk-FFO6KEB4.js";
2
+ LatestThreadScroller,
3
+ validateMcpCallbackResult,
4
+ waitForMcpOauthComplete
5
+ } from "../chunk-LI67M74F.js";
4
6
  import {
7
+ AgentChatComposer,
8
+ AgentChatDefaultActions,
9
+ AgentChatMessage,
5
10
  cn
6
- } from "../chunk-UU7OXHL3.js";
11
+ } from "../chunk-NLEOWTZG.js";
12
+ import "../chunk-CHAU7FNW.js";
7
13
 
8
14
  // src/templates/agent-chat-shell.tsx
9
15
  import * as React from "react";
@@ -120,21 +126,21 @@ function AgentChatShell({
120
126
  const bottomPadding = hideComposer ? 32 : (composerOverlayHeight ?? measuredOverlayHeight) + 72;
121
127
  const isEmptyState = messages.length === 0;
122
128
  if (isEmptyState) {
123
- return /* @__PURE__ */ jsx("div", { className: cn("relative flex h-full min-h-0 w-full flex-col overflow-visible px-4", className), dir, children: renderEmptyState ? renderEmptyState({ composer: hideComposer ? void 0 : composer, title: emptyTitle }) : /* @__PURE__ */ jsx("div", { className: "relative z-10 flex h-full flex-1 flex-col justify-center p-0 pt-13", children: /* @__PURE__ */ jsxs("div", { className: "flex w-full flex-col items-center pb-34 text-center animate-in fade-in slide-in-from-bottom-4 duration-500", children: [
129
+ return /* @__PURE__ */ 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__ */ jsx("div", { className: "a24-shell__empty relative z-10 flex h-full flex-1 flex-col justify-center p-0 pt-13", children: /* @__PURE__ */ 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: [
124
130
  /* @__PURE__ */ jsx(
125
131
  "p",
126
132
  {
127
133
  className: cn(
128
- "pb-6 text-3xl font-semibold transition-transform duration-200",
134
+ "a24-shell__empty-title pb-6 text-3xl font-semibold transition-transform duration-200",
129
135
  emptyInputAttachmentCount > 0 && "-translate-y-12"
130
136
  ),
131
137
  children: emptyTitle
132
138
  }
133
139
  ),
134
- !hideComposer && composer ? /* @__PURE__ */ jsx("div", { className: "mx-auto w-full max-w-3xl px-4", children: composer }) : null
140
+ !hideComposer && composer ? /* @__PURE__ */ jsx("div", { className: "a24-shell__empty-composer mx-auto w-full max-w-3xl px-4", children: composer }) : null
135
141
  ] }) }) });
136
142
  }
137
- return /* @__PURE__ */ jsx("div", { className: cn("relative flex h-full min-h-0 w-full flex-col", className), dir, children: /* @__PURE__ */ jsxs(LatestThreadScroller.Provider, { children: [
143
+ return /* @__PURE__ */ jsx("div", { className: cn("a24-shell relative flex h-full min-h-0 w-full flex-col", className), dir, children: /* @__PURE__ */ jsxs(LatestThreadScroller.Provider, { children: [
138
144
  /* @__PURE__ */ jsxs(LatestThreadScroller.Root, { children: [
139
145
  /* @__PURE__ */ jsx(
140
146
  LatestThreadScroller.Viewport,
@@ -149,7 +155,7 @@ function AgentChatShell({
149
155
  {
150
156
  ref: contentRef,
151
157
  className: cn(
152
- "mx-auto flex min-h-full w-full max-w-3xl flex-col gap-8 px-4 pt-12",
158
+ "a24-shell__content mx-auto flex min-h-full w-full max-w-3xl flex-col gap-8 px-4 pt-12",
153
159
  contentClassName
154
160
  ),
155
161
  style: { paddingBottom: bottomPadding },
@@ -183,22 +189,534 @@ function AgentChatShell({
183
189
  {
184
190
  ref: overlayRef,
185
191
  className: cn(
186
- "pointer-events-none absolute inset-x-0 bottom-0 z-20 overflow-visible px-4 pb-4 pt-10",
192
+ "a24-shell__composer-overlay pointer-events-none absolute inset-x-0 bottom-0 z-20 overflow-visible px-4 pb-4 pt-10",
187
193
  composerOverlayClassName
188
194
  ),
189
195
  style: {
190
196
  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%)"
191
197
  },
192
198
  children: [
193
- /* @__PURE__ */ jsx("div", { className: "pointer-events-auto absolute inset-x-0 top-0 flex -translate-y-full justify-center pb-3", children: /* @__PURE__ */ jsx(LatestThreadScroller.Button, {}) }),
194
- /* @__PURE__ */ jsx("div", { className: "pointer-events-auto mx-auto w-full max-w-3xl px-4", children: composer })
199
+ /* @__PURE__ */ 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__ */ jsx(LatestThreadScroller.Button, {}) }),
200
+ /* @__PURE__ */ jsx("div", { className: "a24-shell__composer-inner pointer-events-auto mx-auto w-full max-w-3xl px-4", children: composer })
195
201
  ]
196
202
  }
197
203
  ) : null
198
204
  ] }) });
199
205
  }
206
+
207
+ // src/templates/agent-chat-layout.tsx
208
+ import * as React2 from "react";
209
+ import {
210
+ ChevronDown,
211
+ Menu,
212
+ MessageSquare,
213
+ MoreHorizontal,
214
+ Plus,
215
+ RefreshCw,
216
+ Search,
217
+ Trash2,
218
+ Wifi,
219
+ WifiOff,
220
+ X
221
+ } from "lucide-react";
222
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
223
+ var stateCopy = {
224
+ loading: { title: "Loading chat", detail: "Your conversation is being prepared." },
225
+ reconnecting: { title: "Reconnecting", detail: "Messages are safe. Trying the connection again." },
226
+ offline: { title: "You\u2019re offline", detail: "Reconnect to continue this conversation." },
227
+ denied: { title: "Access denied", detail: "This deployment does not allow this request." },
228
+ quota: { title: "Usage limit reached", detail: "Try again after the deployment limit resets." },
229
+ failed: { title: "Something went wrong", detail: "The request could not be completed safely." },
230
+ expired: { title: "Session expired", detail: "Reconnect to start a new secure session." }
231
+ };
232
+ function AgentChatStatePanel({
233
+ errorMessage,
234
+ onRetry,
235
+ state
236
+ }) {
237
+ const copy = stateCopy[state];
238
+ const retryable = state === "offline" || state === "failed" || state === "expired";
239
+ return /* @__PURE__ */ jsxs2("div", { className: "a24-state", "data-state": state, role: state === "failed" ? "alert" : "status", children: [
240
+ /* @__PURE__ */ jsx2("div", { className: "a24-state__icon", "aria-hidden": "true", children: state === "offline" ? /* @__PURE__ */ jsx2(WifiOff, {}) : state === "reconnecting" ? /* @__PURE__ */ jsx2(RefreshCw, {}) : /* @__PURE__ */ jsx2(MessageSquare, {}) }),
241
+ /* @__PURE__ */ jsx2("h2", { children: copy.title }),
242
+ /* @__PURE__ */ jsx2("p", { children: errorMessage || copy.detail }),
243
+ retryable && onRetry ? /* @__PURE__ */ jsx2("button", { className: "a24-button a24-button--primary", onClick: () => void onRetry(), type: "button", children: "Try again" }) : null
244
+ ] });
245
+ }
246
+ function connectionLabel(state) {
247
+ if (state === "connected") return "Connected";
248
+ if (state === "connecting") return "Connecting";
249
+ if (state === "reconnecting") return "Reconnecting";
250
+ if (state === "expired") return "Session expired";
251
+ return "Offline";
252
+ }
253
+ function AgentAvatar({ agent }) {
254
+ return agent.avatarUrl ? /* @__PURE__ */ jsx2("img", { alt: "", className: "a24-avatar", height: 36, src: agent.avatarUrl, width: 36 }) : /* @__PURE__ */ jsx2("span", { "aria-hidden": "true", className: "a24-avatar a24-avatar--fallback", children: agent.name.trim().slice(0, 1).toUpperCase() || "A" });
255
+ }
256
+ function ThreadNavigation({
257
+ activeThreadId,
258
+ close,
259
+ hasMoreThreads,
260
+ isDeletingThread,
261
+ isLoadingThreads,
262
+ onDeleteThread,
263
+ onLoadMoreThreads,
264
+ onNewThread,
265
+ onOpenThread,
266
+ slots,
267
+ threads
268
+ }) {
269
+ const [query, setQuery] = React2.useState("");
270
+ const normalizedQuery = query.trim().toLocaleLowerCase();
271
+ const visibleThreads = normalizedQuery ? threads.filter((thread) => String(thread.title || "New chat").toLocaleLowerCase().includes(normalizedQuery)) : threads;
272
+ return /* @__PURE__ */ jsxs2("div", { className: "a24-sidebar__inner", children: [
273
+ /* @__PURE__ */ jsxs2("div", { className: "a24-sidebar__top", children: [
274
+ /* @__PURE__ */ jsxs2(
275
+ "button",
276
+ {
277
+ "aria-label": "New chat",
278
+ className: "a24-button a24-button--primary a24-sidebar__new",
279
+ onClick: () => {
280
+ onNewThread();
281
+ close();
282
+ },
283
+ type: "button",
284
+ children: [
285
+ /* @__PURE__ */ jsx2(Plus, { "aria-hidden": "true" }),
286
+ "New chat"
287
+ ]
288
+ }
289
+ ),
290
+ /* @__PURE__ */ jsx2("button", { "aria-label": "Close conversations", className: "a24-icon-button a24-sidebar__mobile-close", onClick: close, type: "button", children: /* @__PURE__ */ jsx2(X, { "aria-hidden": "true" }) })
291
+ ] }),
292
+ /* @__PURE__ */ jsxs2("label", { className: "a24-search", children: [
293
+ /* @__PURE__ */ jsx2(Search, { "aria-hidden": "true" }),
294
+ /* @__PURE__ */ jsx2("span", { className: "a24-sr-only", children: "Search conversations" }),
295
+ /* @__PURE__ */ jsx2(
296
+ "input",
297
+ {
298
+ autoComplete: "off",
299
+ name: "conversation-search",
300
+ onChange: (event) => setQuery(event.target.value),
301
+ placeholder: "Search conversations\u2026",
302
+ type: "search",
303
+ value: query
304
+ }
305
+ )
306
+ ] }),
307
+ /* @__PURE__ */ jsxs2("nav", { "aria-label": "Conversations", className: "a24-thread-list", children: [
308
+ isLoadingThreads && threads.length === 0 ? /* @__PURE__ */ jsx2("p", { className: "a24-sidebar__hint", role: "status", children: "Loading conversations\u2026" }) : null,
309
+ !isLoadingThreads && visibleThreads.length === 0 ? /* @__PURE__ */ jsx2("p", { className: "a24-sidebar__hint", children: "No conversations yet." }) : null,
310
+ visibleThreads.map((thread) => {
311
+ const selected = thread.id === activeThreadId;
312
+ return /* @__PURE__ */ jsxs2("div", { className: "a24-thread", "data-selected": selected || void 0, children: [
313
+ /* @__PURE__ */ jsxs2(
314
+ "button",
315
+ {
316
+ "aria-current": selected ? "page" : void 0,
317
+ className: "a24-thread__open",
318
+ onClick: () => {
319
+ void onOpenThread(thread.id);
320
+ close();
321
+ },
322
+ type: "button",
323
+ children: [
324
+ /* @__PURE__ */ jsx2("span", { className: "a24-thread__title", children: thread.title || "New chat" }),
325
+ thread.status ? /* @__PURE__ */ jsx2("span", { className: "a24-thread__status", children: thread.status }) : null
326
+ ]
327
+ }
328
+ ),
329
+ slots?.threadActions ? slots.threadActions(thread) : onDeleteThread ? /* @__PURE__ */ jsx2(
330
+ "button",
331
+ {
332
+ "aria-label": `Delete ${thread.title || "conversation"}`,
333
+ className: "a24-icon-button a24-thread__delete",
334
+ disabled: isDeletingThread,
335
+ onClick: () => void onDeleteThread(thread.id),
336
+ type: "button",
337
+ children: /* @__PURE__ */ jsx2(Trash2, { "aria-hidden": "true" })
338
+ }
339
+ ) : null
340
+ ] }, thread.id);
341
+ })
342
+ ] }),
343
+ hasMoreThreads && onLoadMoreThreads ? /* @__PURE__ */ jsxs2("button", { className: "a24-button a24-button--quiet a24-sidebar__more", onClick: () => void onLoadMoreThreads(), type: "button", children: [
344
+ /* @__PURE__ */ jsx2(MoreHorizontal, { "aria-hidden": "true" }),
345
+ "Load more"
346
+ ] }) : null
347
+ ] });
348
+ }
349
+ function AgentChatLayout({
350
+ activeThreadId,
351
+ agent,
352
+ allowAttachments = true,
353
+ className,
354
+ connectionState = "connected",
355
+ dir = "ltr",
356
+ errorMessage,
357
+ hasMoreThreads,
358
+ isDeletingThread,
359
+ isLoadingOlder,
360
+ isLoadingThreads,
361
+ isRunning = false,
362
+ messages,
363
+ modelId,
364
+ models = [],
365
+ onDeleteThread,
366
+ onHitlAction,
367
+ onLoadMoreThreads,
368
+ onLoadOlder,
369
+ onModelChange,
370
+ onNewThread,
371
+ onOpenThread,
372
+ onRetryConnection,
373
+ onStop,
374
+ onSubmit,
375
+ placeholder,
376
+ slots,
377
+ state = "ready",
378
+ streamingMessageId,
379
+ threads,
380
+ title
381
+ }) {
382
+ const [sidebarOpen, setSidebarOpen] = React2.useState(false);
383
+ const menuButtonRef = React2.useRef(null);
384
+ const closeSidebar = React2.useCallback(() => {
385
+ setSidebarOpen(false);
386
+ menuButtonRef.current?.focus();
387
+ }, []);
388
+ React2.useEffect(() => {
389
+ if (!sidebarOpen) return void 0;
390
+ const onKeyDown = (event) => {
391
+ if (event.key !== "Escape") return;
392
+ event.preventDefault();
393
+ closeSidebar();
394
+ };
395
+ window.addEventListener("keydown", onKeyDown);
396
+ return () => window.removeEventListener("keydown", onKeyDown);
397
+ }, [closeSidebar, sidebarOpen]);
398
+ const shellState = state === "streaming" || state === "paused" ? "ready" : state;
399
+ const showState = shellState !== "ready";
400
+ const liveLabel = state === "streaming" ? "Agent is responding" : state === "paused" ? "Waiting for your response" : connectionLabel(connectionState);
401
+ const composer = /* @__PURE__ */ jsx2(
402
+ AgentChatComposer,
403
+ {
404
+ allowAttachments,
405
+ disabled: state === "denied" || state === "quota" || state === "expired" || state === "offline",
406
+ inputToolbarContent: models.length > 0 ? /* @__PURE__ */ jsxs2("label", { className: "a24-model-select", children: [
407
+ /* @__PURE__ */ jsx2("span", { className: "a24-sr-only", children: "Model" }),
408
+ /* @__PURE__ */ jsx2("select", { "aria-label": "Model", onChange: (event) => onModelChange?.(event.target.value), value: modelId || models[0]?.id, children: models.map((model) => /* @__PURE__ */ jsx2("option", { value: model.id, children: model.label }, model.id)) }),
409
+ /* @__PURE__ */ jsx2(ChevronDown, { "aria-hidden": "true" })
410
+ ] }) : void 0,
411
+ isRunning,
412
+ onStop,
413
+ onSubmit,
414
+ placeholder
415
+ }
416
+ );
417
+ return /* @__PURE__ */ jsxs2("section", { className: cn("a24-chat", className), "data-agents24-chat-layout": "", dir, children: [
418
+ /* @__PURE__ */ jsx2("a", { className: "a24-skip-link", href: "#agents24-chat-main", children: "Skip to messages" }),
419
+ /* @__PURE__ */ jsx2("nav", { "aria-label": "Chat navigation", className: "a24-sidebar", "data-open": sidebarOpen || void 0, children: /* @__PURE__ */ jsx2(
420
+ ThreadNavigation,
421
+ {
422
+ activeThreadId,
423
+ close: closeSidebar,
424
+ hasMoreThreads,
425
+ isDeletingThread,
426
+ isLoadingThreads,
427
+ onDeleteThread,
428
+ onLoadMoreThreads,
429
+ onNewThread,
430
+ onOpenThread,
431
+ slots,
432
+ threads
433
+ }
434
+ ) }),
435
+ sidebarOpen ? /* @__PURE__ */ jsx2("button", { "aria-label": "Close conversations", className: "a24-sidebar-backdrop", onClick: closeSidebar, type: "button" }) : null,
436
+ /* @__PURE__ */ jsxs2("div", { className: "a24-chat__body", children: [
437
+ /* @__PURE__ */ jsxs2("header", { className: "a24-header", role: "banner", children: [
438
+ /* @__PURE__ */ jsx2("button", { "aria-label": "Open conversations", className: "a24-icon-button a24-header__menu", onClick: () => setSidebarOpen(true), ref: menuButtonRef, type: "button", children: /* @__PURE__ */ jsx2(Menu, { "aria-hidden": "true" }) }),
439
+ /* @__PURE__ */ jsx2(AgentAvatar, { agent }),
440
+ /* @__PURE__ */ jsxs2("div", { className: "a24-header__identity", children: [
441
+ /* @__PURE__ */ jsx2("h1", { children: title || agent.name }),
442
+ /* @__PURE__ */ jsxs2("div", { className: "a24-connection", "data-state": connectionState, children: [
443
+ connectionState === "offline" || connectionState === "expired" ? /* @__PURE__ */ jsx2(WifiOff, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx2(Wifi, { "aria-hidden": "true" }),
444
+ /* @__PURE__ */ jsx2("span", { children: connectionLabel(connectionState) })
445
+ ] })
446
+ ] }),
447
+ /* @__PURE__ */ jsx2("div", { className: "a24-header__actions", children: slots?.headerActions })
448
+ ] }),
449
+ /* @__PURE__ */ jsx2("main", { "aria-busy": state === "loading" || state === "reconnecting", className: "a24-main", id: "agents24-chat-main", children: showState ? /* @__PURE__ */ jsx2(AgentChatStatePanel, { errorMessage, onRetry: onRetryConnection, state: shellState }) : /* @__PURE__ */ jsx2(
450
+ AgentChatShell,
451
+ {
452
+ composer,
453
+ dir,
454
+ isLoadingOlder,
455
+ messages,
456
+ onLoadOlder,
457
+ renderEmptyState: slots?.empty ? () => slots.empty : void 0,
458
+ renderMessage: (message) => /* @__PURE__ */ jsx2(
459
+ AgentChatMessage,
460
+ {
461
+ actions: slots?.messageActions?.(message),
462
+ message,
463
+ onHitlAction: (part, action, comment) => void onHitlAction?.(part, action, comment),
464
+ renderAssistantContent: slots?.assistantBody,
465
+ streamingMessageId
466
+ }
467
+ )
468
+ }
469
+ ) }),
470
+ /* @__PURE__ */ jsx2("div", { "aria-live": "polite", "aria-atomic": "true", className: "a24-sr-only", children: liveLabel })
471
+ ] })
472
+ ] });
473
+ }
474
+
475
+ // src/templates/agent-chat-app.tsx
476
+ import {
477
+ Agents24Provider,
478
+ useAgentAttachments,
479
+ useAgentChat,
480
+ useAgentMcp
481
+ } from "@agents24/react";
482
+ import * as React3 from "react";
483
+ import { jsx as jsx3 } from "react/jsx-runtime";
484
+ function browserCrypto() {
485
+ const value = globalThis.crypto;
486
+ if (!value?.subtle || !value.randomUUID) throw new Error("Secure browser crypto is required for MCP authorization.");
487
+ return value;
488
+ }
489
+ function base64Url(bytes) {
490
+ let binary = "";
491
+ bytes.forEach((byte) => {
492
+ binary += String.fromCharCode(byte);
493
+ });
494
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
495
+ }
496
+ async function createPkceProof() {
497
+ const crypto = browserCrypto();
498
+ const verifier = base64Url(crypto.getRandomValues(new Uint8Array(32)));
499
+ const bytes = new TextEncoder().encode(verifier);
500
+ const digest = await crypto.subtle.digest("SHA-256", bytes);
501
+ return { verifier, challenge: base64Url(new Uint8Array(digest)) };
502
+ }
503
+ function stateFromError(error) {
504
+ const typed = error;
505
+ if (typed?.kind === "authentication") return "expired";
506
+ if (typed?.kind === "authorization") return "denied";
507
+ if (typed?.kind === "quota" || typed?.kind === "rate_limit") return "quota";
508
+ if (typed?.kind === "network") return "offline";
509
+ return "failed";
510
+ }
511
+ function BoundAgentChatApp({
512
+ agent: agentOverride,
513
+ client,
514
+ completeMcpAuthorization,
515
+ mcpRedirectUri,
516
+ modelId,
517
+ models: modelOverrides,
518
+ onModelChange,
519
+ slots,
520
+ ...layoutProps
521
+ }) {
522
+ const controller = useAgentChat();
523
+ const attachments = useAgentAttachments();
524
+ const mcp = useAgentMcp();
525
+ const [agent, setAgent] = React3.useState(agentOverride || { name: "Agent" });
526
+ const [capabilities, setCapabilities] = React3.useState([]);
527
+ const [bootstrapModels, setBootstrapModels] = React3.useState([]);
528
+ const [selectedModelId, setSelectedModelId] = React3.useState(modelId || null);
529
+ const [bootstrapLoading, setBootstrapLoading] = React3.useState(true);
530
+ const [localError, setLocalError] = React3.useState(null);
531
+ const [liked, setLiked] = React3.useState({});
532
+ const [disliked, setDisliked] = React3.useState({});
533
+ const loadBootstrap = React3.useCallback(async () => {
534
+ setBootstrapLoading(true);
535
+ setLocalError(null);
536
+ try {
537
+ const bootstrap = await client.bootstrap();
538
+ if (!agentOverride) setAgent({ name: bootstrap.deployment.name || "Agent" });
539
+ setCapabilities(bootstrap.capabilities);
540
+ const modelFeatures = bootstrap.features.models;
541
+ const allowedModelIds = Array.isArray(modelFeatures?.allowed_ids) ? modelFeatures.allowed_ids.filter((value) => typeof value === "string") : [];
542
+ if (bootstrap.capabilities.includes("models.select") && modelFeatures?.selectable) {
543
+ setBootstrapModels(allowedModelIds.map((id) => ({ id, label: id })));
544
+ if (!modelId) {
545
+ const defaultId = typeof modelFeatures.default_id === "string" ? modelFeatures.default_id : allowedModelIds[0];
546
+ setSelectedModelId(defaultId || null);
547
+ }
548
+ } else {
549
+ setBootstrapModels([]);
550
+ }
551
+ await controller.refreshThreads();
552
+ } catch (error2) {
553
+ setLocalError(error2);
554
+ } finally {
555
+ setBootstrapLoading(false);
556
+ }
557
+ }, [agentOverride, client, controller.refreshThreads, modelId]);
558
+ React3.useEffect(() => {
559
+ if (modelId !== void 0) setSelectedModelId(modelId);
560
+ }, [modelId]);
561
+ React3.useEffect(() => {
562
+ void loadBootstrap();
563
+ }, [loadBootstrap]);
564
+ const submit = React3.useCallback(async (message) => {
565
+ setLocalError(null);
566
+ try {
567
+ const uploaded = await Promise.all(message.files.map((file) => attachments.upload({
568
+ data: file.source,
569
+ name: file.filename,
570
+ mediaType: file.mediaType,
571
+ threadId: controller.activeThreadId || void 0
572
+ })));
573
+ await controller.submit({
574
+ text: message.text,
575
+ attachmentIds: uploaded.flatMap((attachment) => attachment.id ? [attachment.id] : []),
576
+ attachments: uploaded,
577
+ requestedModelId: selectedModelId || void 0
578
+ });
579
+ } catch (error2) {
580
+ setLocalError(error2);
581
+ }
582
+ }, [attachments, controller, selectedModelId]);
583
+ const deleteThread = React3.useCallback(async (threadId) => {
584
+ if (!window.confirm("Delete this conversation? This cannot be undone.")) return;
585
+ await controller.deleteThread(threadId);
586
+ }, [controller.deleteThread]);
587
+ const changeModel = React3.useCallback((nextModelId) => {
588
+ if (modelId === void 0) setSelectedModelId(nextModelId);
589
+ onModelChange?.(nextModelId);
590
+ }, [modelId, onModelChange]);
591
+ const handleMcp = React3.useCallback(async (part) => {
592
+ if (!mcpRedirectUri) throw new Error("mcpRedirectUri is required for MCP Connect.");
593
+ const serverId = String(part.presentation.server_id || "").trim();
594
+ if (!serverId) throw new Error("MCP authorization is missing its server identity.");
595
+ const runId = controller.activeRunId || controller.messages.find((message) => message.parts.includes(part))?.runId;
596
+ if (!runId) throw new Error("MCP authorization is missing its paused run.");
597
+ const popupNonce = browserCrypto().randomUUID();
598
+ const proof = await createPkceProof();
599
+ const popup = window.open("about:blank", "agents24-mcp", "popup,width=560,height=720");
600
+ try {
601
+ const start = await mcp.startAuthorization({
602
+ runId,
603
+ serverId,
604
+ interruptId: part.interruptId,
605
+ redirectUri: mcpRedirectUri,
606
+ popupNonce,
607
+ codeChallenge: proof.challenge
608
+ });
609
+ if (!start.callback_origin) throw new Error("MCP authorization did not return a callback origin.");
610
+ if (popup) popup.location.replace(start.authorization_url);
611
+ const completion = await waitForMcpOauthComplete({
612
+ popup,
613
+ callbackOrigin: start.callback_origin,
614
+ popupNonce,
615
+ requireConnectionId: Boolean(completeMcpAuthorization),
616
+ serverId
617
+ });
618
+ if (completeMcpAuthorization) {
619
+ await completeMcpAuthorization({ completion, codeVerifier: proof.verifier, serverId });
620
+ } else {
621
+ const redeemed = await mcp.redeemCallback({ code: completion.code, codeVerifier: proof.verifier });
622
+ validateMcpCallbackResult(redeemed, serverId);
623
+ }
624
+ popup?.close();
625
+ await controller.resumeHitl(part, "connect");
626
+ } catch (error2) {
627
+ popup?.close();
628
+ throw error2;
629
+ }
630
+ }, [completeMcpAuthorization, controller, mcp, mcpRedirectUri]);
631
+ const onHitlAction = React3.useCallback(async (part, action, comment) => {
632
+ setLocalError(null);
633
+ try {
634
+ if (part.hitlKind === "mcp_auth" && action === "connect") await handleMcp(part);
635
+ else await controller.resumeHitl(part, action, comment);
636
+ } catch (error2) {
637
+ setLocalError(error2);
638
+ }
639
+ }, [controller, handleMcp]);
640
+ const error = localError || controller.error;
641
+ const state = bootstrapLoading ? "loading" : error ? stateFromError(error) : controller.runState === "streaming" ? "streaming" : controller.runState === "reconnecting" ? "reconnecting" : controller.runState === "paused" ? "paused" : "ready";
642
+ const connectionState = state === "offline" ? "offline" : state === "expired" ? "expired" : state === "reconnecting" ? "reconnecting" : bootstrapLoading ? "connecting" : "connected";
643
+ const defaultMessageActions = (message) => message.role === "assistant" ? /* @__PURE__ */ jsx3(
644
+ AgentChatDefaultActions,
645
+ {
646
+ handlers: {
647
+ copied: false,
648
+ disliked: Boolean(disliked[message.id]),
649
+ liked: Boolean(liked[message.id]),
650
+ onCopy: async (content) => navigator.clipboard.writeText(content),
651
+ onDislike: () => {
652
+ setDisliked((current) => ({ ...current, [message.id]: !current[message.id] }));
653
+ setLiked((current) => ({ ...current, [message.id]: false }));
654
+ },
655
+ onLike: () => {
656
+ setLiked((current) => ({ ...current, [message.id]: !current[message.id] }));
657
+ setDisliked((current) => ({ ...current, [message.id]: false }));
658
+ },
659
+ onRetry: () => controller.regenerate(message)
660
+ },
661
+ message
662
+ }
663
+ ) : null;
664
+ return /* @__PURE__ */ jsx3(
665
+ AgentChatLayout,
666
+ {
667
+ ...layoutProps,
668
+ activeThreadId: controller.activeThreadId,
669
+ agent,
670
+ allowAttachments: layoutProps.allowAttachments ?? capabilities.includes("attachments.write"),
671
+ connectionState,
672
+ errorMessage: error instanceof Error ? error.message : null,
673
+ isLoadingOlder: controller.isLoadingOlder,
674
+ isLoadingThreads: controller.isLoadingThreads,
675
+ isRunning: controller.runState === "streaming" || controller.isSubmitting,
676
+ messages: controller.messages,
677
+ hasMoreThreads: controller.hasMoreThreads,
678
+ modelId: selectedModelId,
679
+ models: modelOverrides ?? bootstrapModels,
680
+ onDeleteThread: deleteThread,
681
+ onHitlAction,
682
+ onLoadOlder: controller.hasOlderTurns ? controller.loadOlder : void 0,
683
+ onLoadMoreThreads: controller.hasMoreThreads ? controller.loadMoreThreads : void 0,
684
+ onModelChange: changeModel,
685
+ onNewThread: controller.startNewThread,
686
+ onOpenThread: controller.openThread,
687
+ onRetryConnection: loadBootstrap,
688
+ onStop: () => void controller.cancelRun(),
689
+ onSubmit: submit,
690
+ slots: { ...slots, messageActions: slots?.messageActions || defaultMessageActions },
691
+ state,
692
+ streamingMessageId: controller.streamingMessageId,
693
+ threads: controller.threads.map((thread) => ({ id: thread.id, title: thread.title, status: thread.last_run_status || thread.status, updatedAt: thread.updated_at }))
694
+ }
695
+ );
696
+ }
697
+ function AgentChatApp({
698
+ client,
699
+ createAbortController,
700
+ createId,
701
+ storage,
702
+ ...props
703
+ }) {
704
+ return /* @__PURE__ */ jsx3(
705
+ Agents24Provider,
706
+ {
707
+ client,
708
+ createAbortController: createAbortController || (() => new AbortController()),
709
+ createId,
710
+ storage,
711
+ children: /* @__PURE__ */ jsx3(BoundAgentChatApp, { client, ...props })
712
+ }
713
+ );
714
+ }
200
715
  export {
716
+ AgentChatApp,
717
+ AgentChatLayout,
201
718
  AgentChatShell,
719
+ AgentChatStatePanel,
202
720
  buildAgentChatOutlineItems,
203
721
  getAgentChatShellScrollState
204
722
  };