agentic-ui-kit 0.7.0 → 0.8.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.
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  "use client";
2
- import { A as awaitingToolCalls, C as widgetLoaderMessages, D as toBlocks, F as settleToolCall, L as salvageJson, O as activityTokens, T as readEventStream, _ as replacedId, b as resolveReplaces, d as isStateRef, f as matchesSchema, g as uiActionSchema, i as parseUiMarkup, k as applyEvent } from "./src-D01l3nBc.js";
2
+ import { A as widgetLoaderMessages, C as readAskUserResult, D as resolveReplaces, F as activityTokens, H as settleToolCall, I as applyEvent, L as awaitingToolCalls, M as readEventStream, P as toBlocks, S as readAskUserInput, W as salvageJson, b as uiActionSchema, c as parseUiMarkup, g as matchesSchema, h as isStateRef, r as uiSpecSet, w as replacedId } from "./src-BrPYkHdt.js";
3
3
  import { clsx } from "clsx";
4
4
  import { extendTailwindMerge } from "tailwind-merge";
5
5
  import { Children, Component, Fragment, createContext, createElement, isValidElement, memo, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
6
6
  import { Autocomplete, Button as Button$1, Dialog, DialogTrigger, DialogTrigger as DialogTrigger$1, Disclosure as Disclosure$1, DisclosurePanel, Focusable, Header, Heading, Input, Keyboard, Link, Menu as Menu$1, MenuItem as MenuItem$1, MenuSection as MenuSection$1, MenuTrigger, MenuTrigger as MenuTrigger$1, Modal as Modal$1, ModalOverlay, Popover as Popover$1, SearchField, Separator, SubmenuTrigger, Text, ToggleButton, ToggleButtonGroup, Tooltip as Tooltip$1, TooltipTrigger, useFilter } from "react-aria-components";
7
7
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
8
- import { AlertTriangle, ArrowDown, ArrowUp, Check, ChevronDown, ChevronRight, ChevronsUpDown, Copy, FileText, Folder, FolderOpen, FolderPlus, Globe, Info, Mic, MoreHorizontal, PanelLeft, Pencil, Pin, PinOff, Plus, RefreshCw, Share, ShieldQuestion, Square, ThumbsDown, ThumbsUp, Trash2, Volume2, X } from "lucide-react";
8
+ import { AlertTriangle, ArrowDown, ArrowRight, ArrowUp, Check, ChevronDown, ChevronLeft, ChevronRight, ChevronsUpDown, Copy, FileText, Folder, FolderOpen, FolderPlus, Globe, GripVertical, Info, MessageSquare, Mic, MoreHorizontal, PanelLeft, Pencil, Pin, PinOff, Plus, RefreshCw, Share, ShieldQuestion, Square, ThumbsDown, ThumbsUp, Trash2, Volume2, X } from "lucide-react";
9
9
  import { UNSAFE_PortalProvider } from "react-aria";
10
10
  import { createMathPlugin } from "@streamdown/math";
11
11
  import { Streamdown, defaultComponents, defaultRehypePlugins } from "streamdown";
@@ -204,6 +204,10 @@ const defaultIcons = {
204
204
  info: /* @__PURE__ */ jsx(Info, {}),
205
205
  warning: /* @__PURE__ */ jsx(AlertTriangle, {}),
206
206
  approval: /* @__PURE__ */ jsx(ShieldQuestion, {}),
207
+ askPrevious: /* @__PURE__ */ jsx(ChevronLeft, {}),
208
+ askPagerNext: /* @__PURE__ */ jsx(ChevronRight, {}),
209
+ askNext: /* @__PURE__ */ jsx(ArrowRight, {}),
210
+ dragHandle: /* @__PURE__ */ jsx(GripVertical, {}),
207
211
  web: /* @__PURE__ */ jsx(Globe, {}),
208
212
  pin: /* @__PURE__ */ jsx(Pin, {}),
209
213
  unpin: /* @__PURE__ */ jsx(PinOff, {}),
@@ -216,7 +220,8 @@ const defaultIcons = {
216
220
  folder: /* @__PURE__ */ jsx(Folder, {}),
217
221
  folderOpen: /* @__PURE__ */ jsx(FolderOpen, {}),
218
222
  file: /* @__PURE__ */ jsx(FileText, {}),
219
- remove: /* @__PURE__ */ jsx(X, {})
223
+ remove: /* @__PURE__ */ jsx(X, {}),
224
+ chatList: /* @__PURE__ */ jsx(MessageSquare, {})
220
225
  };
221
226
  const IconsContext = createContext(defaultIcons);
222
227
  /** Merges `icons` over the icons of the enclosing provider (or the defaults). */
@@ -300,6 +305,24 @@ const defaultLabels = {
300
305
  tokenCount: (count) => `${count} tokens`,
301
306
  replyPlaceholder: "Reply…",
302
307
  replyDirectly: "Or reply directly…",
308
+ askUserPrompt: "Question for you",
309
+ askUserPager: (current, total) => `${current} of ${total}`,
310
+ askUserPrevious: "Previous question",
311
+ askUserNext: "Next question",
312
+ askUserContinue: "Continue",
313
+ askUserSubmit: "Send answers",
314
+ askUserClose: "Close and decline",
315
+ askUserSomethingElse: "Something else",
316
+ askUserSkip: "Skip",
317
+ askUserSelected: (count) => `${count} selected`,
318
+ askUserRankHint: "Drag to re-order",
319
+ askUserReorder: (option) => `Move ${option}`,
320
+ askUserReorderKeys: "Press Alt with the up or down arrow to move",
321
+ askUserPicked: (options) => options.join(", "),
322
+ askUserRanked: (options) => `Ranked: ${options.map((o, i) => `${i + 1}. ${o}`).join(" ")}`,
323
+ askUserSkipped: "Skipped",
324
+ askUserDeclined: "You declined to answer",
325
+ askUserAnswers: "Your answers",
303
326
  message: "Message",
304
327
  stop: "Stop",
305
328
  send: "Send",
@@ -718,7 +741,7 @@ function Drawer({ isOpen, onOpenChange, side = "left", children, className, ...p
718
741
  * A titled region that fills its container and lets the body scroll: a dock panel, an agent pane in a
719
742
  * grid, a settings card. A `ChatView` placed in the body sizes itself to the panel.
720
743
  */
721
- function Panel({ title, actions, footer, children, variant = "card", className, ...props }) {
744
+ function Panel({ title, actions, footer, children, variant = "card", padded = false, className, ...props }) {
722
745
  return /* @__PURE__ */ jsxs("section", {
723
746
  "aria-label": props["aria-label"],
724
747
  className: cx("flex min-h-0 min-w-0 flex-col overflow-hidden bg-chat-bg font-chat-sans text-chat-fg", variant === "card" && "rounded-chat-md border border-chat-border", className),
@@ -734,7 +757,7 @@ function Panel({ title, actions, footer, children, variant = "card", className,
734
757
  })]
735
758
  }),
736
759
  /* @__PURE__ */ jsx("div", {
737
- className: "min-h-0 flex-1 overflow-y-auto [scrollbar-width:thin]",
760
+ className: cx("min-h-0 flex-1 overflow-y-auto [scrollbar-width:thin]", padded && "p-3"),
738
761
  children
739
762
  }),
740
763
  footer && /* @__PURE__ */ jsx("footer", {
@@ -794,8 +817,25 @@ function useReducedMotion() {
794
817
  return useCallback(() => shouldReduceMotion(motion), [motion]);
795
818
  }
796
819
  // src/primitives/highlight.ts
820
+ /**
821
+ * Shiki with its JavaScript regex engine, not the default Oniguruma WebAssembly one, so a page needs no
822
+ * `'wasm-unsafe-eval'` in its Content Security Policy. Languages and themes load on demand.
823
+ */
797
824
  let shiki;
798
- const loadShiki = () => shiki ??= import("shiki");
825
+ const loadShiki = () => shiki ??= (async () => {
826
+ const [lib, { createJavaScriptRegexEngine }] = await Promise.all([import("shiki"), import("shiki/engine/javascript")]);
827
+ return {
828
+ lib,
829
+ highlighter: await lib.createHighlighter({
830
+ themes: [],
831
+ langs: [],
832
+ engine: createJavaScriptRegexEngine({ forgiving: true })
833
+ })
834
+ };
835
+ })().catch((error) => {
836
+ shiki = void 0;
837
+ throw error;
838
+ });
799
839
  const cache = /* @__PURE__ */ new Map();
800
840
  const warned = /* @__PURE__ */ new Set();
801
841
  /** The theme id if Shiki bundles it; otherwise the default, with one console.warn per unknown id. */
@@ -808,6 +848,13 @@ function pick(id, fallback, known) {
808
848
  }
809
849
  return fallback;
810
850
  }
851
+ let failedWarned = false;
852
+ /** One console.warn when highlighting breaks (a blocked chunk, a CSP violation), instead of silently showing plain code. */
853
+ function warnFailed(error) {
854
+ if (failedWarned) return;
855
+ failedWarned = true;
856
+ console.warn("agentic-ui-kit: code highlighting failed, showing plain code.", error);
857
+ }
811
858
  /** Parses "--a:#fff;--b:#000" into an object. */
812
859
  function parseVars(css) {
813
860
  const vars = {};
@@ -821,15 +868,17 @@ async function highlight(code, language, light, dark) {
821
868
  const key = `${light}\n${dark}\n${language}\n${code}`;
822
869
  const hit = cache.get(key);
823
870
  if (hit) return hit;
824
- const { codeToTokens, bundledLanguages, bundledLanguagesAlias, bundledThemes } = await loadShiki();
871
+ const { lib: { bundledLanguages, bundledLanguagesAlias, bundledThemes }, highlighter } = await loadShiki();
825
872
  const lang = language.toLowerCase();
826
873
  if (!(lang in bundledLanguages) && !(lang in bundledLanguagesAlias)) return void 0;
827
- const { tokens, bg, fg } = await codeToTokens(code, {
874
+ const themes = {
875
+ light: pick(light, DEFAULT_CODE_THEMES.light, bundledThemes),
876
+ dark: pick(dark, DEFAULT_CODE_THEMES.dark, bundledThemes)
877
+ };
878
+ await Promise.all([highlighter.loadLanguage(lang), highlighter.loadTheme(themes.light, themes.dark)]);
879
+ const { tokens, bg, fg } = highlighter.codeToTokens(code, {
828
880
  lang,
829
- themes: {
830
- light: pick(light, DEFAULT_CODE_THEMES.light, bundledThemes),
831
- dark: pick(dark, DEFAULT_CODE_THEMES.dark, bundledThemes)
832
- },
881
+ themes,
833
882
  defaultColor: false
834
883
  });
835
884
  const result = {
@@ -854,7 +903,10 @@ function useHighlight(code, language) {
854
903
  useEffect(() => {
855
904
  if (!language) return setResult(void 0);
856
905
  let cancelled = false;
857
- highlight(code, language, light, dark).then((r) => !cancelled && setResult(r), () => !cancelled && setResult(void 0));
906
+ highlight(code, language, light, dark).then((r) => !cancelled && setResult(r), (error) => {
907
+ warnFailed(error);
908
+ if (!cancelled) setResult(void 0);
909
+ });
858
910
  return () => {
859
911
  cancelled = true;
860
912
  };
@@ -900,12 +952,15 @@ function CodeBlock({ code, language, label = language, variant = "plain", copy =
900
952
  /**
901
953
  * Applies a chat theme to part of a page. Menus, tooltips and dialogs opened inside render into this
902
954
  * element instead of `<body>`, so they get the same theme. An app themed as a whole (variables on
903
- * `:root`) does not need it for colors, but still uses it for `codeThemes` and `motion`.
955
+ * `:root`) does not need it for colors, but still uses it for `codeThemes` and `motion`. The element is
956
+ * `height: 100%` of its parent by default (so `ChatView` has a height without Tailwind); a `className` such
957
+ * as `h-screen`, or a style rule of your own, replaces that.
904
958
  */
905
959
  function ChatTheme({ theme, codeThemes, motion, labels, icons, children, className }) {
906
960
  const portal = useRef(null);
907
961
  const getContainer = useCallback(() => portal.current, []);
908
962
  return /* @__PURE__ */ jsxs("div", {
963
+ "data-chat-root": "",
909
964
  "data-chat-theme": theme,
910
965
  "data-chat-motion": motion,
911
966
  className: clsx("bg-chat-bg font-chat-sans text-chat-fg", className),
@@ -1037,32 +1092,36 @@ function userMessage(text, parentId, attachments = []) {
1037
1092
  };
1038
1093
  }
1039
1094
  const wantsApproval = (part, tool) => part.approval === void 0 && (part.needsApproval === true || (typeof tool?.approval === "function" ? tool.approval(part.input) : tool?.approval === true));
1095
+ const wantsPrompt = (part, promptTools, tool) => part.approval !== "denied" && (promptTools.includes(part.name) || tool?.view?.Prompt !== void 0);
1040
1096
  const DECLINED = "Declined by the user";
1041
1097
  /**
1042
1098
  * A linear conversation driven by a Transport: sends, streams the reply through `applyEvent`, stops and
1043
1099
  * retries. With an `AgentProvider` it also runs the agent loop: client tools run in the page, approvals
1044
1100
  * wait for the user, then the reply continues; every finished tool call reaches its tool's `onResult`.
1045
1101
  */
1046
- function useChat({ transport, conversationId, initialMessages = [], onTitle, agent, maxSteps = 12, resume = false }) {
1102
+ function useChat({ transport, conversationId, initialMessages = [], onTitle, agent, maxSteps = 12, resume = false, promptTools = ["ask_user"] }) {
1047
1103
  const contextAgent = useAgentRegistry();
1048
1104
  const registry = agent === void 0 ? contextAgent : agent;
1049
1105
  const [messages, setMessages] = useState(initialMessages);
1050
1106
  const [busy, setBusy] = useState(false);
1051
1107
  const [approvals, setApprovals] = useState([]);
1108
+ const [prompts, setPrompts] = useState([]);
1052
1109
  const run = useRef(null);
1053
1110
  const latest = useRef({
1054
1111
  messages,
1055
1112
  transport,
1056
1113
  conversationId,
1057
1114
  onTitle,
1058
- registry
1115
+ registry,
1116
+ promptTools
1059
1117
  });
1060
1118
  latest.current = {
1061
1119
  messages,
1062
1120
  transport,
1063
1121
  conversationId,
1064
1122
  onTitle,
1065
- registry
1123
+ registry,
1124
+ promptTools
1066
1125
  };
1067
1126
  const loop = useRef({
1068
1127
  options: {},
@@ -1177,6 +1236,15 @@ function useChat({ transport, conversationId, initialMessages = [], onTitle, age
1177
1236
  setBusy(false);
1178
1237
  return;
1179
1238
  }
1239
+ const prompted = awaiting.filter((p) => wantsPrompt(p, latest.current.promptTools, registry?.tool(p.name)));
1240
+ if (prompted.length > 0) {
1241
+ setPrompts(prompted.map((part) => ({
1242
+ messageId: reply.id,
1243
+ part
1244
+ })));
1245
+ setBusy(false);
1246
+ return;
1247
+ }
1180
1248
  const controller = new AbortController();
1181
1249
  const current = {
1182
1250
  controller,
@@ -1265,6 +1333,29 @@ function useChat({ transport, conversationId, initialMessages = [], onTitle, age
1265
1333
  advance,
1266
1334
  commit
1267
1335
  ]);
1336
+ const settlePrompt = useCallback((partId, fields) => {
1337
+ const pending = prompts.find((p) => p.part.id === partId);
1338
+ if (!pending) return;
1339
+ const message = latest.current.messages.find((m) => m.id === pending.messageId);
1340
+ if (!message) return;
1341
+ const next = settleToolCall(message, partId, fields);
1342
+ commit(next);
1343
+ const rest = prompts.filter((p) => p.part.id !== partId);
1344
+ setPrompts(rest);
1345
+ if (rest.length === 0) advance(next);
1346
+ }, [
1347
+ prompts,
1348
+ advance,
1349
+ commit
1350
+ ]);
1351
+ const answer = useCallback((partId, result) => settlePrompt(partId, {
1352
+ status: "done",
1353
+ result
1354
+ }), [settlePrompt]);
1355
+ const decline = useCallback((partId) => settlePrompt(partId, {
1356
+ status: "error",
1357
+ error: DECLINED
1358
+ }), [settlePrompt]);
1268
1359
  /** Declines whatever is waiting, e.g. because the user moved on. */
1269
1360
  const declineAll = useCallback(() => {
1270
1361
  for (const { messageId, part } of approvals) {
@@ -1280,6 +1371,7 @@ function useChat({ transport, conversationId, initialMessages = [], onTitle, age
1280
1371
  error: DECLINED
1281
1372
  }));
1282
1373
  setApprovals([]);
1374
+ setPrompts([]);
1283
1375
  }, [approvals, commit]);
1284
1376
  const send = useCallback((text, options = {}) => {
1285
1377
  const attachments = options.attachments ?? [];
@@ -1304,6 +1396,7 @@ function useChat({ transport, conversationId, initialMessages = [], onTitle, age
1304
1396
  const index = messages.findIndex((m) => m.id === assistantId);
1305
1397
  if (index < 1 || messages[index - 1].role !== "user") return;
1306
1398
  setApprovals([]);
1399
+ setPrompts([]);
1307
1400
  loop.current = {
1308
1401
  options,
1309
1402
  step: 0
@@ -1339,6 +1432,9 @@ function useChat({ transport, conversationId, initialMessages = [], onTitle, age
1339
1432
  busy,
1340
1433
  approvals,
1341
1434
  respond,
1435
+ prompts,
1436
+ answer,
1437
+ decline,
1342
1438
  send,
1343
1439
  stop,
1344
1440
  retry
@@ -2178,17 +2274,18 @@ function Node({ node, registry }) {
2178
2274
  }) });
2179
2275
  }
2180
2276
  /** Draws a `render_ui` tree with the registered components. Unknown components and bad props render nothing. */
2181
- function UiTree({ id, root, state: initial, streaming = false }) {
2277
+ function UiTree({ id, stateKey, root, state: initial, streaming = false }) {
2278
+ const storeKey = stateKey ?? id;
2182
2279
  const registry = useContext(RegistryContext);
2183
2280
  const host = useWidgetHost();
2184
- const [local, setLocal] = useState(() => registry?.persist?.load(id) ?? {});
2281
+ const [local, setLocal] = useState(() => registry?.persist?.load(storeKey) ?? {});
2185
2282
  const changed = useRef(false);
2186
2283
  const latest = useRef(host);
2187
2284
  latest.current = host;
2188
2285
  useEffect(() => {
2189
- if (changed.current) registry?.persist?.save(id, local);
2286
+ if (changed.current) registry?.persist?.save(storeKey, local);
2190
2287
  }, [
2191
- id,
2288
+ storeKey,
2192
2289
  local,
2193
2290
  registry
2194
2291
  ]);
@@ -2239,15 +2336,20 @@ function useUiMarkupLanguage() {
2239
2336
  return registry && registry.markup !== false ? registry.markup : void 0;
2240
2337
  }
2241
2338
  /** A ```ui block from a reply, drawn with the registered components. Safe on a block that is still streaming. */
2242
- function UiMarkupBlock({ id, source, streaming = false }) {
2339
+ function UiMarkupBlock({ id, stateKey, source, streaming = false }) {
2243
2340
  const { nodes, state } = useMemo(() => parseUiMarkup(source), [source]);
2244
2341
  return /* @__PURE__ */ jsx(UiTree, {
2245
2342
  id,
2343
+ ...stateKey && { stateKey },
2246
2344
  root: nodes,
2247
2345
  state,
2248
2346
  streaming
2249
2347
  });
2250
2348
  }
2349
+ function stateKeyOf(options, part) {
2350
+ const stateKey = options.stateKey?.(part);
2351
+ return stateKey ? { stateKey } : {};
2352
+ }
2251
2353
  /**
2252
2354
  * A tool view that draws a call's (streaming) JSX markup with your registered components, for whatever tool
2253
2355
  * your harness uses; the suggested one is core's `showUiTool`. Register it under your tool's name:
@@ -2256,12 +2358,13 @@ function UiMarkupBlock({ id, source, streaming = false }) {
2256
2358
  function uiMarkupToolView(read = (input) => {
2257
2359
  const markup = input?.markup;
2258
2360
  return typeof markup === "string" ? markup : void 0;
2259
- }) {
2361
+ }, options = {}) {
2260
2362
  function UiMarkupToolBlock({ part }) {
2261
2363
  const markup = read(part.input ?? part.partialInput);
2262
2364
  if (!markup) return null;
2263
2365
  return /* @__PURE__ */ jsx(UiMarkupBlock, {
2264
2366
  id: part.id,
2367
+ ...stateKeyOf(options, part),
2265
2368
  source: markup,
2266
2369
  streaming: part.status === "streaming" || part.status === "input"
2267
2370
  });
@@ -2285,13 +2388,14 @@ function readTreeInput(input) {
2285
2388
  * your tool's input to the tree and its starting state; the default reads `{ root, state }`, the shape of
2286
2389
  * the suggested `renderUiTool`.
2287
2390
  */
2288
- function uiTreeToolView(read = readTreeInput) {
2391
+ function uiTreeToolView(read = readTreeInput, options = {}) {
2289
2392
  function UiTreeToolBlock({ part }) {
2290
2393
  const { root, state } = read(part.input ?? part.partialInput);
2291
2394
  if (!root) return null;
2292
2395
  const ok = typeof state === "object" && state !== null;
2293
2396
  return /* @__PURE__ */ jsx(UiTree, {
2294
2397
  id: part.id,
2398
+ ...stateKeyOf(options, part),
2295
2399
  root,
2296
2400
  ...ok && { state },
2297
2401
  streaming: part.status === "streaming" || part.status === "input"
@@ -2384,7 +2488,19 @@ function rehypeWordFade() {
2384
2488
  return (tree) => wrap(tree);
2385
2489
  }
2386
2490
  // src/components/markdown.tsx
2387
- const fadePlugins = [...Object.values(defaultRehypePlugins), rehypeWordFade];
2491
+ const [sanitize, sanitizeSchema] = defaultRehypePlugins.sanitize;
2492
+ const rehypePlugins = Object.values({
2493
+ ...defaultRehypePlugins,
2494
+ sanitize: [sanitize, {
2495
+ ...sanitizeSchema,
2496
+ tagNames: sanitizeSchema.tagNames?.filter((t) => t !== "source"),
2497
+ protocols: {
2498
+ ...sanitizeSchema.protocols,
2499
+ src: [...sanitizeSchema.protocols?.src ?? [], "data"]
2500
+ }
2501
+ }]
2502
+ });
2503
+ const fadePlugins = [...rehypePlugins, rehypeWordFade];
2388
2504
  const math = createMathPlugin({ singleDollarTextMath: true });
2389
2505
  /** Streamdown's own utility classes get this prefix, so neither they nor the host's classes apply. */
2390
2506
  const PREFIX = "llmchat";
@@ -2417,6 +2533,15 @@ function Code({ node, className, children }) {
2417
2533
  });
2418
2534
  }
2419
2535
  const components = {
2536
+ ...Object.fromEntries([
2537
+ "source",
2538
+ "video",
2539
+ "audio",
2540
+ "track",
2541
+ "iframe",
2542
+ "embed",
2543
+ "object"
2544
+ ].map((t) => [t, () => null])),
2420
2545
  pre: ({ children }) => /* @__PURE__ */ jsx(Fragment$1, { children }),
2421
2546
  code: ({ node, className, children }) => /* @__PURE__ */ jsx(Code, {
2422
2547
  node,
@@ -2425,11 +2550,18 @@ const components = {
2425
2550
  })
2426
2551
  };
2427
2552
  const LinksContext = createContext(void 0);
2428
- function MarkdownLinksProvider({ renderLink, renderLinkList, children }) {
2553
+ function MarkdownLinksProvider({ renderLink, renderLinkList, renderImage, imageOrigins, children }) {
2429
2554
  const value = useMemo(() => ({
2430
2555
  renderLink,
2431
- renderLinkList
2432
- }), [renderLink, renderLinkList]);
2556
+ renderLinkList,
2557
+ renderImage,
2558
+ imageOrigins
2559
+ }), [
2560
+ renderLink,
2561
+ renderLinkList,
2562
+ renderImage,
2563
+ imageOrigins
2564
+ ]);
2433
2565
  return /* @__PURE__ */ jsx(LinksContext.Provider, {
2434
2566
  value,
2435
2567
  children
@@ -2446,6 +2578,32 @@ function soleLink(children) {
2446
2578
  };
2447
2579
  return props.node?.tagName === "p" ? soleLink(props.children) : void 0;
2448
2580
  }
2581
+ const originOf = (url, base) => {
2582
+ try {
2583
+ return new URL(url, base).origin;
2584
+ } catch {
2585
+ return;
2586
+ }
2587
+ };
2588
+ /** Whether an image may load on render. Relative and protocol-relative URLs count as the page's origin or the one they name. */
2589
+ function imageAllowed(src, origins) {
2590
+ if (/^data:image\//i.test(src)) return true;
2591
+ if (origins === "all") return /^(https?:)?\/\//i.test(src) || !/^[a-z][a-z\d+.-]*:/i.test(src);
2592
+ if (!origins?.length) return false;
2593
+ const origin = originOf(src, typeof location === "undefined" ? void 0 : location.href);
2594
+ return origin !== void 0 && /^https?:/.test(origin) && origins.some((o) => (originOf(o) ?? o) === origin);
2595
+ }
2596
+ /** What an image the host did not allow becomes: a link to it (alt text or host name), opened only by the reader. */
2597
+ function ImageLink({ src, alt }) {
2598
+ const url = URL.canParse(src) ? new URL(src) : void 0;
2599
+ if (url?.protocol !== "http:" && url?.protocol !== "https:") return /* @__PURE__ */ jsx(Fragment$1, { children: alt });
2600
+ return /* @__PURE__ */ jsx("a", {
2601
+ href: src,
2602
+ target: "_blank",
2603
+ rel: "noopener noreferrer",
2604
+ children: alt || url.host
2605
+ });
2606
+ }
2449
2607
  function listOf(ordered, props, renderLinkList) {
2450
2608
  const Default = ordered ? defaultComponents.ol : defaultComponents.ul;
2451
2609
  const { children } = props;
@@ -2488,9 +2646,27 @@ function BlockScope({ id, streaming, children }) {
2488
2646
  });
2489
2647
  }
2490
2648
  const MarkdownBody = memo(function MarkdownBody({ text, animating, fade }) {
2491
- const { renderLink, renderLinkList } = useContext(LinksContext) ?? {};
2649
+ const { renderLink, renderLinkList, renderImage, imageOrigins } = useContext(LinksContext) ?? {};
2492
2650
  const withLinks = useMemo(() => ({
2493
2651
  ...components,
2652
+ img: ({ src, alt }) => {
2653
+ if (typeof src !== "string" || !src) return /* @__PURE__ */ jsx(Fragment$1, { children: alt });
2654
+ if (renderImage) return /* @__PURE__ */ jsx(Fragment$1, { children: renderImage({
2655
+ src,
2656
+ alt: alt ?? ""
2657
+ }) });
2658
+ if (imageAllowed(src, imageOrigins)) return /* @__PURE__ */ jsx("img", {
2659
+ src,
2660
+ alt: alt ?? "",
2661
+ loading: "lazy",
2662
+ referrerPolicy: "no-referrer",
2663
+ className: "max-w-full rounded-chat-md"
2664
+ });
2665
+ return /* @__PURE__ */ jsx(ImageLink, {
2666
+ src,
2667
+ alt: alt ?? ""
2668
+ });
2669
+ },
2494
2670
  ...renderLink && { a: ({ href, children }) => {
2495
2671
  const custom = href ? renderLink({
2496
2672
  href,
@@ -2507,11 +2683,16 @@ const MarkdownBody = memo(function MarkdownBody({ text, animating, fade }) {
2507
2683
  ul: (props) => listOf(false, props, renderLinkList),
2508
2684
  ol: (props) => listOf(true, props, renderLinkList)
2509
2685
  }
2510
- }), [renderLink, renderLinkList]);
2686
+ }), [
2687
+ renderLink,
2688
+ renderLinkList,
2689
+ renderImage,
2690
+ imageOrigins
2691
+ ]);
2511
2692
  return /* @__PURE__ */ jsx(Streamdown, {
2512
2693
  prefix: PREFIX,
2513
2694
  mode: animating || fade ? "streaming" : "static",
2514
- ...fade && { rehypePlugins: fadePlugins },
2695
+ rehypePlugins: fade ? fadePlugins : rehypePlugins,
2515
2696
  plugins: { math },
2516
2697
  components: withLinks,
2517
2698
  controls: false,
@@ -2520,6 +2701,406 @@ const MarkdownBody = memo(function MarkdownBody({ text, animating, fade }) {
2520
2701
  children: text
2521
2702
  });
2522
2703
  });
2704
+ // src/components/ask-user.tsx
2705
+ const rowClass$1 = "flex w-full cursor-pointer items-center gap-3 rounded-chat-md px-2.5 py-2 text-left text-(length:--chat-ui-font-size) leading-5 text-chat-fg outline-none hover:bg-chat-hover focus-visible:ring-2 focus-visible:ring-chat-focus";
2706
+ /** Moves focus to the previous or next answer row of a question, without wrapping. */
2707
+ function moveFocus(root, from, delta) {
2708
+ const rows = [...root.querySelectorAll("[data-ask-row]")];
2709
+ const i = rows.findIndex((row) => row === from || row.contains(from));
2710
+ rows[Math.min(rows.length - 1, Math.max(0, i + delta))]?.focus();
2711
+ }
2712
+ const isTextField = (target) => target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement;
2713
+ /** The 1-based number a plain digit key stands for, or undefined. */
2714
+ function digitOf(e) {
2715
+ if (e.altKey || e.ctrlKey || e.metaKey || isTextField(e.target)) return void 0;
2716
+ return /^[1-9]$/.test(e.key) ? Number(e.key) : void 0;
2717
+ }
2718
+ function Num({ children, active }) {
2719
+ return /* @__PURE__ */ jsx("span", {
2720
+ "aria-hidden": true,
2721
+ className: clsx("flex size-6 shrink-0 items-center justify-center rounded-chat-sm border text-(length:--chat-ui-font-size-sm) tabular-nums", active ? "border-chat-accent bg-chat-accent text-chat-accent-fg" : "border-chat-border-strong text-chat-muted"),
2722
+ children
2723
+ });
2724
+ }
2725
+ function Check$1({ checked }) {
2726
+ const icons = useIcons();
2727
+ return /* @__PURE__ */ jsx("span", {
2728
+ "aria-hidden": true,
2729
+ className: clsx("flex size-5 shrink-0 items-center justify-center rounded-chat-sm border [&_svg]:size-3.5", checked ? "border-chat-accent bg-chat-accent text-chat-accent-fg" : "border-chat-border-strong"),
2730
+ children: checked && icons.check
2731
+ });
2732
+ }
2733
+ /** The "Something else" row: a text input the number key after the last option focuses. */
2734
+ function OtherRow({ number, value, onChange, onEnter, leading }) {
2735
+ const labels = useLabels();
2736
+ return /* @__PURE__ */ jsxs("div", {
2737
+ className: clsx(rowClass$1, "cursor-text"),
2738
+ children: [leading, /* @__PURE__ */ jsx("input", {
2739
+ "data-ask-row": true,
2740
+ "data-ask-other": true,
2741
+ value,
2742
+ onChange: (e) => onChange(e.target.value),
2743
+ onKeyDown: (e) => {
2744
+ if (e.key === "Enter" && !e.nativeEvent.isComposing) {
2745
+ e.preventDefault();
2746
+ onEnter();
2747
+ }
2748
+ },
2749
+ placeholder: labels.askUserSomethingElse,
2750
+ "aria-label": labels.askUserSomethingElse,
2751
+ "aria-keyshortcuts": String(number),
2752
+ className: "min-w-0 flex-1 bg-transparent text-chat-fg outline-none placeholder:text-chat-subtle"
2753
+ })]
2754
+ });
2755
+ }
2756
+ function Footer({ start, onSkip, next }) {
2757
+ const labels = useLabels();
2758
+ const icons = useIcons();
2759
+ return /* @__PURE__ */ jsxs("div", {
2760
+ className: "mt-2 flex items-center gap-2 px-1",
2761
+ children: [
2762
+ /* @__PURE__ */ jsx("span", {
2763
+ className: "min-w-0 flex-1 truncate text-(length:--chat-ui-font-size-sm) leading-4 text-chat-subtle",
2764
+ children: start
2765
+ }),
2766
+ /* @__PURE__ */ jsx(Button, {
2767
+ size: "sm",
2768
+ variant: "ghost",
2769
+ className: "text-chat-muted",
2770
+ onPress: onSkip,
2771
+ children: labels.askUserSkip
2772
+ }),
2773
+ next && /* @__PURE__ */ jsx(IconButton, {
2774
+ label: next.label,
2775
+ variant: "primary",
2776
+ size: "sm",
2777
+ isDisabled: next.disabled,
2778
+ onPress: next.onPress,
2779
+ children: icons.askNext
2780
+ })
2781
+ ]
2782
+ });
2783
+ }
2784
+ function SingleQuestion({ question, initial, onAnswer, onSkip }) {
2785
+ const { options } = question;
2786
+ const chosen = typeof initial?.answer === "string" ? initial.answer : void 0;
2787
+ const [other, setOther] = useState(chosen !== void 0 && !options.includes(chosen) ? chosen : "");
2788
+ const root = useRef(null);
2789
+ const submitOther = () => other.trim() && onAnswer(other.trim());
2790
+ return /* @__PURE__ */ jsxs("div", {
2791
+ ref: root,
2792
+ role: "group",
2793
+ "aria-label": question.question,
2794
+ onKeyDown: (e) => {
2795
+ const n = digitOf(e);
2796
+ if (n !== void 0 && n <= options.length) onAnswer(options[n - 1]);
2797
+ else if (n === options.length + 1) root.current?.querySelector("[data-ask-other]")?.focus();
2798
+ else return;
2799
+ e.preventDefault();
2800
+ },
2801
+ children: [
2802
+ options.map((option, i) => /* @__PURE__ */ jsxs("button", {
2803
+ type: "button",
2804
+ "data-ask-row": true,
2805
+ onClick: () => onAnswer(option),
2806
+ className: rowClass$1,
2807
+ children: [/* @__PURE__ */ jsx(Num, {
2808
+ active: option === chosen,
2809
+ children: i + 1
2810
+ }), /* @__PURE__ */ jsx("span", {
2811
+ className: "min-w-0 flex-1",
2812
+ children: option
2813
+ })]
2814
+ }, i)),
2815
+ /* @__PURE__ */ jsx(OtherRow, {
2816
+ number: options.length + 1,
2817
+ value: other,
2818
+ onChange: setOther,
2819
+ onEnter: submitOther,
2820
+ leading: /* @__PURE__ */ jsx(Num, {
2821
+ active: other !== "" && other === chosen,
2822
+ children: options.length + 1
2823
+ })
2824
+ }),
2825
+ /* @__PURE__ */ jsx(Footer, { onSkip })
2826
+ ]
2827
+ });
2828
+ }
2829
+ function MultipleQuestion({ question, initial, last, onAnswer, onSkip }) {
2830
+ const labels = useLabels();
2831
+ const { options } = question;
2832
+ const before = Array.isArray(initial?.answer) ? initial.answer : [];
2833
+ const [picked, setPicked] = useState(() => new Set(options.flatMap((o, i) => before.includes(o) ? [i] : [])));
2834
+ const [other, setOther] = useState(before.find((a) => !options.includes(a)) ?? "");
2835
+ const root = useRef(null);
2836
+ const answer = [...options.filter((_, i) => picked.has(i)), ...other.trim() ? [other.trim()] : []];
2837
+ const toggle = (i) => setPicked((set) => (set.delete(i) || set.add(i), new Set(set)));
2838
+ const next = () => answer.length > 0 && onAnswer(answer);
2839
+ return /* @__PURE__ */ jsxs("div", {
2840
+ ref: root,
2841
+ role: "group",
2842
+ "aria-label": question.question,
2843
+ onKeyDown: (e) => {
2844
+ const n = digitOf(e);
2845
+ if (n !== void 0 && n <= options.length) toggle(n - 1);
2846
+ else if (n === options.length + 1) root.current?.querySelector("[data-ask-other]")?.focus();
2847
+ else return;
2848
+ e.preventDefault();
2849
+ },
2850
+ children: [
2851
+ options.map((option, i) => /* @__PURE__ */ jsxs("button", {
2852
+ type: "button",
2853
+ role: "checkbox",
2854
+ "aria-checked": picked.has(i),
2855
+ "data-ask-row": true,
2856
+ onClick: () => toggle(i),
2857
+ onKeyDown: (e) => {
2858
+ if (e.key !== "Enter" || answer.length === 0) return;
2859
+ e.preventDefault();
2860
+ e.stopPropagation();
2861
+ next();
2862
+ },
2863
+ className: rowClass$1,
2864
+ children: [/* @__PURE__ */ jsx(Check$1, { checked: picked.has(i) }), /* @__PURE__ */ jsx("span", {
2865
+ className: "min-w-0 flex-1",
2866
+ children: option
2867
+ })]
2868
+ }, i)),
2869
+ /* @__PURE__ */ jsx(OtherRow, {
2870
+ number: options.length + 1,
2871
+ value: other,
2872
+ onChange: setOther,
2873
+ onEnter: next,
2874
+ leading: /* @__PURE__ */ jsx(Check$1, { checked: other.trim() !== "" })
2875
+ }),
2876
+ /* @__PURE__ */ jsx(Footer, {
2877
+ start: labels.askUserSelected(answer.length),
2878
+ onSkip,
2879
+ next: {
2880
+ label: last ? labels.askUserSubmit : labels.askUserContinue,
2881
+ disabled: answer.length === 0,
2882
+ onPress: next
2883
+ }
2884
+ })
2885
+ ]
2886
+ });
2887
+ }
2888
+ function RankQuestion({ question, initial, last, onAnswer, onSkip }) {
2889
+ const labels = useLabels();
2890
+ const icons = useIcons();
2891
+ const { options } = question;
2892
+ const [order, setOrder] = useState(() => {
2893
+ const before = Array.isArray(initial?.answer) ? initial.answer : [];
2894
+ const pool = options.map((o, i) => [o, i]);
2895
+ const restored = before.flatMap((text) => {
2896
+ const at = pool.findIndex(([o]) => o === text);
2897
+ return at === -1 ? [] : pool.splice(at, 1).map(([, i]) => i);
2898
+ });
2899
+ return restored.length === options.length ? restored : options.map((_, i) => i);
2900
+ });
2901
+ const rows = useRef(/* @__PURE__ */ new Map());
2902
+ const focusAfter = useRef(null);
2903
+ const dragging = useRef(null);
2904
+ useLayoutEffect(() => {
2905
+ if (focusAfter.current !== null) rows.current.get(focusAfter.current)?.focus();
2906
+ focusAfter.current = null;
2907
+ }, [order]);
2908
+ const move = (from, to) => {
2909
+ if (to < 0 || to >= order.length || from === to) return;
2910
+ setOrder((list) => {
2911
+ const next = [...list];
2912
+ next.splice(to, 0, next.splice(from, 1)[0]);
2913
+ return next;
2914
+ });
2915
+ };
2916
+ const send = () => onAnswer(order.map((i) => options[i]));
2917
+ const onDragOver = (position) => (e) => {
2918
+ e.preventDefault();
2919
+ if (dragging.current === null || dragging.current === position) return;
2920
+ move(dragging.current, position);
2921
+ dragging.current = position;
2922
+ };
2923
+ return /* @__PURE__ */ jsxs("div", {
2924
+ role: "group",
2925
+ "aria-label": question.question,
2926
+ children: [/* @__PURE__ */ jsx("ol", { children: order.map((option, position) => /* @__PURE__ */ jsxs("li", {
2927
+ ref: (el) => void (el ? rows.current.set(option, el) : rows.current.delete(option)),
2928
+ "data-ask-row": true,
2929
+ tabIndex: 0,
2930
+ draggable: true,
2931
+ "aria-label": `${position + 1}. ${options[option]}`,
2932
+ "aria-description": labels.askUserReorderKeys,
2933
+ "aria-keyshortcuts": "Alt+ArrowUp Alt+ArrowDown",
2934
+ onDragStart: () => dragging.current = position,
2935
+ onDragOver: onDragOver(position),
2936
+ onDragEnd: () => dragging.current = null,
2937
+ onDrop: (e) => e.preventDefault(),
2938
+ onKeyDown: (e) => {
2939
+ if (e.key === "Enter") send();
2940
+ else if (e.key === "ArrowUp" || e.key === "ArrowDown") {
2941
+ const delta = e.key === "ArrowUp" ? -1 : 1;
2942
+ if (e.altKey) {
2943
+ focusAfter.current = option;
2944
+ move(position, position + delta);
2945
+ } else moveFocus(e.currentTarget.parentElement, e.currentTarget, delta);
2946
+ } else return;
2947
+ e.preventDefault();
2948
+ },
2949
+ className: clsx(rowClass$1, "cursor-grab"),
2950
+ children: [
2951
+ /* @__PURE__ */ jsx("span", {
2952
+ "aria-hidden": true,
2953
+ className: "text-chat-subtle",
2954
+ children: /* @__PURE__ */ jsx(IconSlot, {
2955
+ className: "[&_svg]:size-4",
2956
+ children: icons.dragHandle
2957
+ })
2958
+ }),
2959
+ /* @__PURE__ */ jsx(Num, { children: position + 1 }),
2960
+ /* @__PURE__ */ jsx("span", {
2961
+ className: "min-w-0 flex-1",
2962
+ children: options[option]
2963
+ })
2964
+ ]
2965
+ }, option)) }), /* @__PURE__ */ jsx(Footer, {
2966
+ start: labels.askUserRankHint,
2967
+ onSkip,
2968
+ next: {
2969
+ label: last ? labels.askUserSubmit : labels.askUserContinue,
2970
+ disabled: false,
2971
+ onPress: send
2972
+ }
2973
+ })]
2974
+ });
2975
+ }
2976
+ const questionViews = {
2977
+ single: SingleQuestion,
2978
+ multiple: MultipleQuestion,
2979
+ rank: RankQuestion
2980
+ };
2981
+ /**
2982
+ * The `ask_user` prompt: the model's questions one at a time with a pager, answered by clicking or the keyboard
2983
+ * (number keys, arrows, Enter; Alt+arrows reorder a rank). Submits `{ answers }` (an `AskUserResult`) after the
2984
+ * last question; Skip leaves a question out, the close button or Escape declines everything.
2985
+ */
2986
+ function AskUserPrompt({ part, onSubmit, onDecline }) {
2987
+ const labels = useLabels();
2988
+ const icons = useIcons();
2989
+ const questions = readAskUserInput(part.input);
2990
+ const [index, setIndex] = useState(0);
2991
+ const [answers, setAnswers] = useState([]);
2992
+ const root = useRef(null);
2993
+ useEffect(() => {
2994
+ root.current?.querySelector("[data-ask-row]:not([data-ask-other])")?.focus();
2995
+ }, [index]);
2996
+ const question = questions[index];
2997
+ if (!question) return null;
2998
+ const Question = questionViews[question.kind];
2999
+ const settle = (answer) => {
3000
+ const next = answers.slice();
3001
+ next[index] = {
3002
+ question: question.question,
3003
+ kind: question.kind,
3004
+ ...answer
3005
+ };
3006
+ setAnswers(next);
3007
+ for (let step = 1; step <= questions.length; step++) {
3008
+ const at = (index + step) % questions.length;
3009
+ if (next[at] === void 0) return setIndex(at);
3010
+ }
3011
+ onSubmit({ answers: questions.map((_, i) => next[i]) });
3012
+ };
3013
+ return /* @__PURE__ */ jsxs("div", {
3014
+ ref: root,
3015
+ role: "dialog",
3016
+ "aria-label": labels.askUserPrompt,
3017
+ onKeyDown: (e) => {
3018
+ if (e.key !== "Escape") return;
3019
+ e.preventDefault();
3020
+ onDecline();
3021
+ },
3022
+ className: "flex flex-col gap-1 rounded-chat-lg border border-chat-border-strong bg-chat-surface p-3 font-chat-sans text-chat-fg shadow-chat-popover",
3023
+ children: [/* @__PURE__ */ jsxs("div", {
3024
+ className: "flex items-start gap-2 px-1 pb-1",
3025
+ children: [
3026
+ /* @__PURE__ */ jsx("p", {
3027
+ className: "min-w-0 flex-1 text-(length:--chat-ui-font-size) leading-5 font-medium",
3028
+ children: question.question
3029
+ }),
3030
+ questions.length > 1 && /* @__PURE__ */ jsxs("div", {
3031
+ className: "flex shrink-0 items-center gap-0.5 text-(length:--chat-ui-font-size-sm) text-chat-muted",
3032
+ children: [
3033
+ /* @__PURE__ */ jsx(IconButton, {
3034
+ label: labels.askUserPrevious,
3035
+ size: "sm",
3036
+ isDisabled: index === 0,
3037
+ onPress: () => setIndex(index - 1),
3038
+ children: icons.askPrevious
3039
+ }),
3040
+ /* @__PURE__ */ jsx("span", {
3041
+ className: "tabular-nums",
3042
+ children: labels.askUserPager(index + 1, questions.length)
3043
+ }),
3044
+ /* @__PURE__ */ jsx(IconButton, {
3045
+ label: labels.askUserNext,
3046
+ size: "sm",
3047
+ isDisabled: index === questions.length - 1,
3048
+ onPress: () => setIndex(index + 1),
3049
+ children: icons.askPagerNext
3050
+ })
3051
+ ]
3052
+ }),
3053
+ /* @__PURE__ */ jsx(IconButton, {
3054
+ label: labels.askUserClose,
3055
+ size: "sm",
3056
+ onPress: onDecline,
3057
+ children: icons.remove
3058
+ })
3059
+ ]
3060
+ }), /* @__PURE__ */ jsx(Question, {
3061
+ question,
3062
+ initial: answers[index],
3063
+ last: answers.filter((a, i) => a !== void 0 && i !== index).length === questions.length - 1,
3064
+ onAnswer: (answer) => settle({ answer }),
3065
+ onSkip: () => settle({ skipped: true })
3066
+ }, index)]
3067
+ });
3068
+ }
3069
+ /** The user's answers as a card in the message: each question with its answer, "Ranked: 1. …", or "Skipped". */
3070
+ function AskUserAnswers({ part }) {
3071
+ const labels = useLabels();
3072
+ if (part.status === "error") return /* @__PURE__ */ jsx("p", {
3073
+ className: "font-chat-sans text-(length:--chat-ui-font-size) leading-5 text-chat-muted",
3074
+ children: labels.askUserDeclined
3075
+ });
3076
+ const answers = part.status === "done" ? readAskUserResult(part.result) : void 0;
3077
+ if (!answers) return null;
3078
+ const text = (a) => {
3079
+ if (a.skipped || a.answer === void 0) return labels.askUserSkipped;
3080
+ if (typeof a.answer === "string") return a.answer;
3081
+ return a.kind === "rank" ? labels.askUserRanked(a.answer) : labels.askUserPicked(a.answer);
3082
+ };
3083
+ return /* @__PURE__ */ jsx("ul", {
3084
+ "aria-label": labels.askUserAnswers,
3085
+ className: "flex flex-col gap-3 rounded-chat-lg border border-chat-border bg-chat-surface px-4 py-3 font-chat-sans text-(length:--chat-ui-font-size) leading-5",
3086
+ children: answers.map((a, i) => /* @__PURE__ */ jsxs("li", { children: [/* @__PURE__ */ jsx("p", {
3087
+ className: "text-chat-muted",
3088
+ children: a.question
3089
+ }), /* @__PURE__ */ jsx("p", {
3090
+ className: clsx("text-chat-fg", (a.skipped || a.answer === void 0) && "text-chat-subtle"),
3091
+ children: text(a)
3092
+ })] }, i))
3093
+ });
3094
+ }
3095
+ /**
3096
+ * The view of the `ask_user` tool (`askUserTool` in `agentic-ui-kit/core`): the prompt over the composer while
3097
+ * the call awaits the user, then the answers card in the message. Registered in `defaultToolViews`.
3098
+ */
3099
+ const askUserToolView = {
3100
+ label: (_, labels) => labels.askUserPrompt,
3101
+ Prompt: AskUserPrompt,
3102
+ Block: AskUserAnswers
3103
+ };
2523
3104
  // src/components/tools.tsx
2524
3105
  const ToolViewsContext = createContext(void 0);
2525
3106
  function ToolViewsProvider({ tools, children }) {
@@ -2700,6 +3281,7 @@ const memoryTool = {
2700
3281
  summary: (n, labels) => labels.recalledMemory(n)
2701
3282
  };
2702
3283
  const defaultToolViews = {
3284
+ ask_user: askUserToolView,
2703
3285
  bash: commandTool,
2704
3286
  shell: commandTool,
2705
3287
  web_search: webSearchTool,
@@ -2786,10 +3368,12 @@ function TokenCounter({ tokens, variant = "standalone", className }) {
2786
3368
  * row per step. Each row expands on its own; tool rows render through the registered `ToolView`.
2787
3369
  * A group of one step skips the rows: the heading is the step's own label and the box holds its detail.
2788
3370
  */
2789
- function ActivityGroup({ parts, defaultExpanded = false, className }) {
3371
+ function ActivityGroup({ parts: all, defaultExpanded = false, className }) {
3372
+ const parts = all.filter((p) => !bareThought(p) || p.title || p.durationMs);
2790
3373
  const views = useToolViews();
2791
3374
  const labels = useLabels();
2792
3375
  const [expanded, setExpanded] = useState(defaultExpanded);
3376
+ if (parts.length === 0) return null;
2793
3377
  const live = parts.some(isWorking);
2794
3378
  const tokens = activityTokens(parts);
2795
3379
  const after = live && tokens > 0 && /* @__PURE__ */ jsx(TokenCounter, {
@@ -2855,8 +3439,16 @@ function Shimmer({ children }) {
2855
3439
  children
2856
3440
  });
2857
3441
  }
3442
+ /** A finished thought with nothing to open: hidden reasoning, or no text. It is a plain line, not a disclosure. */
3443
+ function bareThought(part) {
3444
+ return part.type === "thinking" && part.status === "done" && (part.visibility === "hidden" || !part.text);
3445
+ }
2858
3446
  function SingleStep({ part, live, after, expanded, onExpandedChange }) {
2859
3447
  const { summary, detail } = useStepContent(part);
3448
+ if (bareThought(part)) return /* @__PURE__ */ jsxs("div", {
3449
+ className: "flex items-center gap-1.5 text-chat-muted text-(length:--chat-activity-font-size)",
3450
+ children: [/* @__PURE__ */ jsx(Lead, { live }), summary]
3451
+ });
2860
3452
  return /* @__PURE__ */ jsx(Disclosure, {
2861
3453
  isExpanded: expanded,
2862
3454
  onExpandedChange,
@@ -2879,6 +3471,10 @@ function SingleStep({ part, live, after, expanded, onExpandedChange }) {
2879
3471
  function Step({ part }) {
2880
3472
  const { summary, detail } = useStepContent(part);
2881
3473
  const live = isWorking(part);
3474
+ if (bareThought(part)) return /* @__PURE__ */ jsx("div", {
3475
+ className: "px-3 py-2 text-chat-muted text-(length:--chat-activity-font-size)",
3476
+ children: summary
3477
+ });
2882
3478
  return /* @__PURE__ */ jsx(Disclosure, {
2883
3479
  className: "px-3 py-2",
2884
3480
  summaryClassName: "text-(length:--chat-activity-font-size)",
@@ -3347,6 +3943,246 @@ function AssistantMessage({ message, onRetry, onFeedback, onReadAloud, renderPar
3347
3943
  ]
3348
3944
  });
3349
3945
  }
3946
+ // src/components/tool-prompt.tsx
3947
+ /**
3948
+ * Shows the `Prompt` of a pending call's tool view, above the composer (`ChatView` does this for you). Apps
3949
+ * that lay out the chat themselves render it from `useChat().prompts[0]`. Renders nothing if the tool's view
3950
+ * has no `Prompt`.
3951
+ */
3952
+ function ToolPromptCard({ prompt, onAnswer, onDecline }) {
3953
+ const { part } = prompt;
3954
+ const Prompt = useToolView(part.name).Prompt;
3955
+ if (!Prompt) return null;
3956
+ return /* @__PURE__ */ jsx(Prompt, {
3957
+ part,
3958
+ onSubmit: (result) => onAnswer(part.id, result),
3959
+ onDecline: () => onDecline(part.id)
3960
+ }, part.id);
3961
+ }
3962
+ // src/components/ui-components.tsx
3963
+ const toneClass = {
3964
+ default: "text-chat-fg",
3965
+ accent: "text-chat-accent",
3966
+ good: "text-chat-check",
3967
+ warning: "text-chat-warning",
3968
+ bad: "text-chat-danger"
3969
+ };
3970
+ const fillClass = {
3971
+ default: "bg-chat-fg",
3972
+ accent: "bg-chat-accent",
3973
+ good: "bg-chat-check",
3974
+ warning: "bg-chat-warning",
3975
+ bad: "bg-chat-danger"
3976
+ };
3977
+ /** "New" / "Changed": what the model added stands out in the accent. */
3978
+ function Tag({ children, quiet = false }) {
3979
+ return /* @__PURE__ */ jsx("span", {
3980
+ className: `rounded-chat-sm px-1.5 text-(length:--chat-ui-font-size-sm) font-medium ${quiet ? "border border-chat-border-strong text-chat-muted" : "bg-chat-accent text-chat-accent-fg"}`,
3981
+ children
3982
+ });
3983
+ }
3984
+ const Stack = ({ direction = "column", gap = "md", children }) => /* @__PURE__ */ jsx("div", {
3985
+ className: `flex ${direction === "row" ? "flex-row flex-wrap" : "flex-col"} ${{
3986
+ sm: "gap-1.5",
3987
+ md: "gap-3",
3988
+ lg: "gap-5"
3989
+ }[gap]}`,
3990
+ children
3991
+ });
3992
+ const Grid = ({ columns = 2, children }) => /* @__PURE__ */ jsx("div", {
3993
+ className: "grid gap-3",
3994
+ style: { gridTemplateColumns: `repeat(auto-fit, minmax(min(100%, ${Math.floor(36 / columns)}rem), 1fr))` },
3995
+ children
3996
+ });
3997
+ const Card = ({ title, subtitle, tag, tone = "default", children }) => /* @__PURE__ */ jsxs("section", {
3998
+ className: `rounded-chat-lg border p-3 ${tone === "accent" ? "border-chat-accent" : "border-chat-border"}`,
3999
+ children: [
4000
+ (title || tag) && /* @__PURE__ */ jsxs("header", {
4001
+ className: "mb-1 flex items-center gap-2",
4002
+ children: [title && /* @__PURE__ */ jsx("h3", {
4003
+ className: "font-chat-sans text-(length:--chat-ui-font-size) font-semibold",
4004
+ children: title
4005
+ }), tag && /* @__PURE__ */ jsx(Tag, {
4006
+ quiet: tone !== "accent",
4007
+ children: tag
4008
+ })]
4009
+ }),
4010
+ subtitle && /* @__PURE__ */ jsx("p", {
4011
+ className: "mb-2 text-(length:--chat-ui-font-size-sm) text-chat-muted",
4012
+ children: subtitle
4013
+ }),
4014
+ children
4015
+ ]
4016
+ });
4017
+ const Heading$1 = ({ text }) => /* @__PURE__ */ jsx("h2", {
4018
+ className: "font-chat-sans text-(length:--chat-ui-font-size) font-semibold",
4019
+ children: text
4020
+ });
4021
+ const Text$1 = ({ text, tone = "default" }) => /* @__PURE__ */ jsx("p", {
4022
+ className: `font-chat-sans text-(length:--chat-ui-font-size) leading-relaxed ${tone === "muted" ? "text-chat-muted" : ""}`,
4023
+ children: text
4024
+ });
4025
+ const Metric = ({ label, value, note, tone = "default" }) => /* @__PURE__ */ jsxs("div", {
4026
+ className: "min-w-24",
4027
+ children: [
4028
+ /* @__PURE__ */ jsx("div", {
4029
+ className: "text-(length:--chat-ui-font-size-sm) text-chat-muted",
4030
+ children: label
4031
+ }),
4032
+ /* @__PURE__ */ jsx("div", {
4033
+ className: `font-chat-sans text-xl font-semibold tabular-nums ${toneClass[tone]}`,
4034
+ children: value
4035
+ }),
4036
+ note && /* @__PURE__ */ jsx("div", {
4037
+ className: "text-(length:--chat-ui-font-size-sm) text-chat-subtle",
4038
+ children: note
4039
+ })
4040
+ ]
4041
+ });
4042
+ const List = ({ items }) => /* @__PURE__ */ jsx("ul", {
4043
+ className: "divide-y divide-chat-divider",
4044
+ children: items.map((item, i) => /* @__PURE__ */ jsxs("li", {
4045
+ className: "flex items-baseline gap-2 py-1.5 font-chat-sans text-(length:--chat-ui-font-size)",
4046
+ children: [
4047
+ /* @__PURE__ */ jsx("span", {
4048
+ className: item.tag ? "text-chat-accent" : "",
4049
+ children: item.label
4050
+ }),
4051
+ item.tag && /* @__PURE__ */ jsx(Tag, { children: item.tag }),
4052
+ item.detail && /* @__PURE__ */ jsx("span", {
4053
+ className: "ml-auto text-chat-muted tabular-nums",
4054
+ children: item.detail
4055
+ })
4056
+ ]
4057
+ }, i))
4058
+ });
4059
+ const Table = ({ columns, rows }) => /* @__PURE__ */ jsx("div", {
4060
+ className: "max-w-full overflow-x-auto",
4061
+ children: /* @__PURE__ */ jsxs("table", {
4062
+ className: "w-full border-collapse text-left font-chat-sans text-(length:--chat-ui-font-size)",
4063
+ children: [/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", {
4064
+ className: "border-b border-chat-border-strong",
4065
+ children: columns.map((c, i) => /* @__PURE__ */ jsx("th", {
4066
+ scope: "col",
4067
+ className: "px-2 py-1.5 font-medium text-chat-muted",
4068
+ children: c
4069
+ }, i))
4070
+ }) }), /* @__PURE__ */ jsx("tbody", {
4071
+ className: "divide-y divide-chat-divider",
4072
+ children: rows.map((row, r) => /* @__PURE__ */ jsx("tr", { children: columns.map((_, c) => /* @__PURE__ */ jsx("td", {
4073
+ className: `px-2 py-1.5 ${typeof row[c] === "number" ? "tabular-nums" : ""}`,
4074
+ children: row[c]
4075
+ }, c)) }, r))
4076
+ })]
4077
+ })
4078
+ });
4079
+ const Badge$1 = ({ label, tone = "default" }) => /* @__PURE__ */ jsx("span", {
4080
+ className: `inline-block self-start rounded-chat-sm border border-current px-1.5 font-chat-sans text-(length:--chat-ui-font-size-sm) font-medium ${toneClass[tone]}`,
4081
+ children: label
4082
+ });
4083
+ const Progress = ({ label, value, max = 100, tone = "accent" }) => {
4084
+ const share = Math.min(1, Math.max(0, value / max));
4085
+ return /* @__PURE__ */ jsxs("div", {
4086
+ className: "flex flex-col gap-1",
4087
+ children: [label && /* @__PURE__ */ jsxs("div", {
4088
+ className: "flex justify-between text-(length:--chat-ui-font-size-sm) text-chat-muted",
4089
+ children: [/* @__PURE__ */ jsx("span", { children: label }), /* @__PURE__ */ jsxs("span", {
4090
+ className: "tabular-nums",
4091
+ children: [Math.round(share * 100), "%"]
4092
+ })]
4093
+ }), /* @__PURE__ */ jsx("div", {
4094
+ role: "progressbar",
4095
+ "aria-label": label ?? "Progress",
4096
+ "aria-valuemin": 0,
4097
+ "aria-valuemax": max,
4098
+ "aria-valuenow": Math.min(value, max),
4099
+ className: "h-1.5 overflow-hidden rounded-full bg-chat-border",
4100
+ children: /* @__PURE__ */ jsx("div", {
4101
+ className: `h-full rounded-full ${fillClass[tone]}`,
4102
+ style: { width: `${share * 100}%` }
4103
+ })
4104
+ })]
4105
+ });
4106
+ };
4107
+ const Checklist = function Checklist({ stateKey, items }) {
4108
+ const [state, set] = useUiState();
4109
+ const done = state[stateKey] ?? [];
4110
+ return /* @__PURE__ */ jsx("ul", {
4111
+ className: "flex flex-col gap-1",
4112
+ children: items.map((item) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("label", {
4113
+ className: "flex items-center gap-2 font-chat-sans text-(length:--chat-ui-font-size)",
4114
+ children: [/* @__PURE__ */ jsx("input", {
4115
+ type: "checkbox",
4116
+ className: "accent-(--chat-accent)",
4117
+ checked: done.includes(item),
4118
+ onChange: (e) => set(stateKey, e.target.checked ? [...done, item] : done.filter((d) => d !== item))
4119
+ }), /* @__PURE__ */ jsx("span", {
4120
+ className: done.includes(item) ? "text-chat-subtle line-through" : "",
4121
+ children: item
4122
+ })]
4123
+ }) }, item))
4124
+ });
4125
+ };
4126
+ const Tabs = function Tabs({ stateKey, value, options }) {
4127
+ const [, set] = useUiState();
4128
+ const current = value ?? options[0].id;
4129
+ return /* @__PURE__ */ jsx("div", {
4130
+ role: "tablist",
4131
+ className: "inline-flex max-w-full flex-wrap gap-1 self-start rounded-chat-md border border-chat-border p-0.5",
4132
+ children: options.map((o) => /* @__PURE__ */ jsx(Button, {
4133
+ size: "sm",
4134
+ variant: o.id === current ? "subtle" : "ghost",
4135
+ onPress: () => set(stateKey, o.id),
4136
+ "aria-selected": o.id === current,
4137
+ children: o.label
4138
+ }, o.id))
4139
+ });
4140
+ };
4141
+ const Show = ({ value, equals, fallback, children }) => value === equals || value === void 0 && fallback ? /* @__PURE__ */ jsx(Fragment$1, { children }) : null;
4142
+ /**
4143
+ * The drawing of each component, by name and apart from its spec. Take one to reuse in your own
4144
+ * component: `{ ...uiSpecs.Card, render: uiRenders.Card }`.
4145
+ */
4146
+ const uiRenders = {
4147
+ Stack,
4148
+ Grid,
4149
+ Card,
4150
+ Heading: Heading$1,
4151
+ Text: Text$1,
4152
+ Metric,
4153
+ List,
4154
+ Table,
4155
+ Badge: Badge$1,
4156
+ Progress,
4157
+ Checklist,
4158
+ Tabs,
4159
+ Show,
4160
+ Button: function UiButton({ label, variant = "secondary", onPress }) {
4161
+ const run = useUiAction();
4162
+ return /* @__PURE__ */ jsx(Button, {
4163
+ size: "sm",
4164
+ className: "self-start",
4165
+ variant: variant === "primary" ? "primary" : "subtle",
4166
+ onPress: () => run(onPress),
4167
+ children: label
4168
+ });
4169
+ }
4170
+ };
4171
+ /**
4172
+ * The ready components for `UiComponentsProvider`, in the order of `uiComponentSpecs`. Everything by default;
4173
+ * `include` and `exclude` take component names or the groups `"display"` (Stack, Grid, Card, Heading, Text,
4174
+ * Metric, List, Table, Badge, Progress) and `"interactive"` (Checklist, Tabs, Show, Button). `renders` swaps the
4175
+ * drawing of single components. Build the model's tool from the same picks so it only hears of what you
4176
+ * registered: `uiSpecSet({ include })` in `agentic-ui-kit/core`, or strip the renders from this list.
4177
+ */
4178
+ function uiComponentSet({ renders = {}, ...selection } = {}) {
4179
+ return uiSpecSet(selection).map((spec) => ({
4180
+ ...spec,
4181
+ render: renders[spec.name] ?? uiRenders[spec.name]
4182
+ }));
4183
+ }
4184
+ /** The whole set: `uiComponentSet()`. */
4185
+ const uiComponents = uiComponentSet();
3350
4186
  // src/components/approval-card.tsx
3351
4187
  /**
3352
4188
  * Asks the user before a tool runs, above the composer. Shows the tool's label and what it will do: its
@@ -4572,6 +5408,34 @@ function ChatList({ sections, activeId, onSelect, rowMenu, className, ...props }
4572
5408
  }, section.id))
4573
5409
  });
4574
5410
  }
5411
+ /**
5412
+ * An icon button that opens a `ChatList` in a popover, for `Sidebar`'s `railActions`: the list itself is
5413
+ * hidden on the collapsed rail, so this keeps recent chats reachable. Picking a chat closes the popover.
5414
+ */
5415
+ function ChatListRailButton({ label, popoverProps, onSelect, className, ...list }) {
5416
+ const icons = useIcons();
5417
+ const labels = useLabels();
5418
+ const name = label ?? labels.chats;
5419
+ return /* @__PURE__ */ jsxs(DialogTrigger, { children: [/* @__PURE__ */ jsx(IconButton, {
5420
+ label: name,
5421
+ children: icons.chatList
5422
+ }), /* @__PURE__ */ jsx(Popover, {
5423
+ placement: "right top",
5424
+ ...popoverProps,
5425
+ children: /* @__PURE__ */ jsx(Dialog, {
5426
+ "aria-label": name,
5427
+ className: "max-h-[70dvh] w-64 overflow-y-auto p-2 outline-none [scrollbar-width:thin]",
5428
+ children: ({ close }) => /* @__PURE__ */ jsx(ChatList, {
5429
+ ...list,
5430
+ className,
5431
+ onSelect: (id) => {
5432
+ onSelect?.(id);
5433
+ close();
5434
+ }
5435
+ })
5436
+ })
5437
+ })] });
5438
+ }
4575
5439
  function ChatRow({ chat, active, nested, onSelect, menu }) {
4576
5440
  const icons = useIcons();
4577
5441
  const [menuOpen, setMenuOpen] = useState(false);
@@ -4677,7 +5541,7 @@ const densityVars = {
4677
5541
  }
4678
5542
  };
4679
5543
  /** A whole chat: header, the conversation, the docked composer. An empty chat shows `emptyState`. */
4680
- function ChatView({ messages, busy = false, onSend, onStop, approvals, onApprove, onRetry, onEdit, onFeedback, onReadAloud, messageActions, renderPart, widgets, header, composer, emptyState, disclaimer, renderComposer, density = "full", contentWidth, contentPadding, className }) {
5544
+ function ChatView({ messages, busy = false, onSend, onStop, approvals, onApprove, prompts, onAnswer, onDecline, onRetry, onEdit, onFeedback, onReadAloud, messageActions, renderPart, widgets, header, composer, emptyState, disclaimer, renderComposer, density = "full", contentWidth, contentPadding, className }) {
4681
5545
  const style = {
4682
5546
  ...densityVars[density],
4683
5547
  ...contentWidth && { "--chat-content-width": contentWidth },
@@ -4692,6 +5556,7 @@ function ChatView({ messages, busy = false, onSend, onStop, approvals, onApprove
4692
5556
  const [body, short] = useShortPane();
4693
5557
  const labels = useLabels();
4694
5558
  const pending = onApprove && approvals?.[0];
5559
+ const asking = !pending && onAnswer ? prompts?.[0] : void 0;
4695
5560
  const box = (variant) => {
4696
5561
  if (composer === false) return null;
4697
5562
  const element = /* @__PURE__ */ jsx(Composer, {
@@ -4700,7 +5565,7 @@ function ChatView({ messages, busy = false, onSend, onStop, approvals, onApprove
4700
5565
  onSubmit: onSend,
4701
5566
  ...onStop && { onStop },
4702
5567
  ...composer,
4703
- ...pending && { placeholder: labels.replyDirectly }
5568
+ ...(pending || asking) && { placeholder: labels.replyDirectly }
4704
5569
  });
4705
5570
  return renderComposer ? renderComposer(element, variant === "centered" ? "empty" : "docked") : element;
4706
5571
  };
@@ -4737,6 +5602,14 @@ function ChatView({ messages, busy = false, onSend, onStop, approvals, onApprove
4737
5602
  onRespond: onApprove,
4738
5603
  className: "mb-2"
4739
5604
  }, pending.part.id),
5605
+ asking && /* @__PURE__ */ jsx("div", {
5606
+ className: "mb-2",
5607
+ children: /* @__PURE__ */ jsx(ToolPromptCard, {
5608
+ prompt: asking,
5609
+ onAnswer,
5610
+ onDecline: onDecline ?? (() => {})
5611
+ })
5612
+ }),
4740
5613
  box("docked"),
4741
5614
  /* @__PURE__ */ jsx("div", {
4742
5615
  className: clsx("flex items-center justify-center py-1 pb-[max(0.25rem,env(safe-area-inset-bottom))] text-center font-chat-sans text-(length:--chat-ui-font-size-sm) leading-4 text-chat-subtle", short ? "min-h-2" : "min-h-6"),
@@ -5108,7 +5981,7 @@ function SidebarItem({ icon, label, onPress, href, active, shortcut, badge, desc
5108
5981
  }
5109
5982
  const initials = (name) => name.split(/\s+/).slice(0, 2).map((w) => w[0]?.toUpperCase() ?? "").join("");
5110
5983
  /** The account row at the bottom: avatar, name, plan, and a menu. */
5111
- function SidebarUser({ name, detail, avatarUrl, menu }) {
5984
+ function SidebarUser({ name, detail, avatarUrl, menu, popoverProps }) {
5112
5985
  const icons = useIcons();
5113
5986
  const { collapsed } = useSidebar();
5114
5987
  const avatar = avatarUrl ? /* @__PURE__ */ jsx("img", {
@@ -5140,7 +6013,8 @@ function SidebarUser({ name, detail, avatarUrl, menu }) {
5140
6013
  if (!menu) return button;
5141
6014
  return /* @__PURE__ */ jsxs(MenuTrigger, { children: [button, /* @__PURE__ */ jsx(Popover, {
5142
6015
  placement: "top start",
6016
+ ...popoverProps,
5143
6017
  children: menu
5144
6018
  })] });
5145
6019
  }
5146
- export { ActivityGroup, AgentProvider, AgentRegistry, AppShell, ApprovalCard, AssistantMessage, AttachButton, AttachmentCard, AttachmentRow, Badge, Button, Caret, ChatHeader, ChatList, ChatNotice, ChatTheme, ChatTitleMenu, ChatTranscript, ChatView, CodeBlock, Composer, ContextMenu, ContextMeter, ContextPanel, CopyButton, CopyField, DRAWER_QUERY, DialogTrigger, DictationButton, DictationControls, Disclosure, Drawer, EffortButton, EffortSlider, EmptyState, IconButton, Markdown, MarkdownLinksProvider, Menu, MenuItem, MenuNote, MenuSearch, MenuSection, MenuSeparator, MenuTrigger, MessageList, Modal, ModelPicker, Panel, PastedTextDialog, Popover, PopoverTrigger, SegmentedControl, Sidebar, SidebarItem, SidebarList, SidebarToggle, SidebarUser, SubmenuTrigger, TokenCounter, ToolViewsProvider, Tooltip, UiComponentsProvider, UiMarkupBlock, UiTree, UserMessage, VoiceMeter, WIDGET_MAX_HEIGHT, WidgetFrame, WidgetHostProvider, WidgetPartView, activityHeading, activityLabel, commandTool, defaultIcons, defaultLabels, defaultToolViews, encodeWav, formatDuration, formatRelativeTime, formatTokens, htmlComponent, intersectAllow, memoryTool, messageText, openInNewTab, readTreeInput, readVisualizeInput, sseTransport, uiMarkupToolView, uiTreeToolView, useAgentContext, useAgentRegistry, useAgentSnapshot, useAgentTool, useAttachments, useChat, useChatTool, useCountUp, useDictation, useIcons, useLabels, useMediaQuery, useSidebar, useSmoothText, useToolView, useToolViews, useUiAction, useUiMarkupLanguage, useUiState, useWidgetHost, userMessage, webFetchTool, webSearchTool, widgetCsp, widgetToolView };
6020
+ export { ActivityGroup, AgentProvider, AgentRegistry, AppShell, ApprovalCard, AskUserAnswers, AskUserPrompt, AssistantMessage, AttachButton, AttachmentCard, AttachmentRow, Badge, Button, Caret, ChatHeader, ChatList, ChatListRailButton, ChatNotice, ChatTheme, ChatTitleMenu, ChatTranscript, ChatView, CodeBlock, Composer, ContextMenu, ContextMeter, ContextPanel, CopyButton, CopyField, DRAWER_QUERY, DialogTrigger, DictationButton, DictationControls, Disclosure, Drawer, EffortButton, EffortSlider, EmptyState, IconButton, Markdown, MarkdownLinksProvider, Menu, MenuItem, MenuNote, MenuSearch, MenuSection, MenuSeparator, MenuTrigger, MessageList, Modal, ModelPicker, Panel, PastedTextDialog, Popover, PopoverTrigger, SegmentedControl, Sidebar, SidebarItem, SidebarList, SidebarToggle, SidebarUser, SubmenuTrigger, TokenCounter, ToolPromptCard, ToolViewsProvider, Tooltip, UiComponentsProvider, UiMarkupBlock, UiTree, UserMessage, VoiceMeter, WIDGET_MAX_HEIGHT, WidgetFrame, WidgetHostProvider, WidgetPartView, activityHeading, activityLabel, askUserToolView, commandTool, defaultIcons, defaultLabels, defaultToolViews, encodeWav, formatDuration, formatRelativeTime, formatTokens, htmlComponent, intersectAllow, memoryTool, messageText, openInNewTab, readTreeInput, readVisualizeInput, sseTransport, uiComponentSet, uiComponents, uiMarkupToolView, uiRenders, uiTreeToolView, useAgentContext, useAgentRegistry, useAgentSnapshot, useAgentTool, useAttachments, useChat, useChatTool, useCountUp, useDictation, useIcons, useLabels, useMediaQuery, useSidebar, useSmoothText, useToolView, useToolViews, useUiAction, useUiMarkupLanguage, useUiState, useWidgetHost, userMessage, webFetchTool, webSearchTool, widgetCsp, widgetToolView };