@candor-ui/react 1.5.0 → 1.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.
Files changed (72) hide show
  1. package/dist/adapters/ag-ui.d.ts +24 -24
  2. package/dist/adapters/ai-sdk.d.ts +6 -6
  3. package/dist/adapters/claude-agent-sdk.d.ts +51 -34
  4. package/dist/adapters/claude-agent-sdk.js +46 -2
  5. package/dist/adapters/claude-agent-server.d.ts +34 -34
  6. package/dist/adapters/use-ag-ui.d.ts +6 -6
  7. package/dist/adapters/use-ag-ui.js +14 -2
  8. package/dist/adapters/use-candor-chat.d.ts +1 -1
  9. package/dist/adapters/use-candor-chat.js +1 -1
  10. package/dist/adapters/use-claude-agent.d.ts +10 -10
  11. package/dist/adapters/use-claude-agent.js +1 -1
  12. package/dist/blocks/copy-field.d.ts +1 -1
  13. package/dist/blocks/copy-field.js +1 -1
  14. package/dist/blocks/degraded-panel.d.ts +5 -5
  15. package/dist/blocks/locale.d.ts +6 -6
  16. package/dist/blocks/locked.d.ts +8 -8
  17. package/dist/blocks/relative-time.d.ts +6 -6
  18. package/dist/blocks/status-dot.d.ts +3 -3
  19. package/dist/blocks/step-list.d.ts +3 -3
  20. package/dist/blocks/streaming-text.d.ts +3 -3
  21. package/dist/chat/agent-avatar.d.ts +4 -4
  22. package/dist/chat/ai-disclosure.d.ts +5 -5
  23. package/dist/chat/ai-disclosure.js +1 -1
  24. package/dist/chat/attachment-preview.d.ts +6 -6
  25. package/dist/chat/attachment-preview.js +1 -1
  26. package/dist/chat/card-registry.js +1 -1
  27. package/dist/chat/citation.d.ts +3 -3
  28. package/dist/chat/citation.js +3 -3
  29. package/dist/chat/composer.d.ts +37 -20
  30. package/dist/chat/composer.js +13 -6
  31. package/dist/chat/context-meter.d.ts +1 -1
  32. package/dist/chat/context-meter.js +1 -1
  33. package/dist/chat/entity-chip.d.ts +3 -3
  34. package/dist/chat/entity-chip.js +1 -1
  35. package/dist/chat/folded-turns.d.ts +11 -6
  36. package/dist/chat/folded-turns.js +2 -2
  37. package/dist/chat/message-actions.d.ts +6 -6
  38. package/dist/chat/message-actions.js +1 -1
  39. package/dist/chat/message-content.d.ts +13 -7
  40. package/dist/chat/message-content.js +10 -5
  41. package/dist/chat/message.d.ts +64 -37
  42. package/dist/chat/message.js +17 -11
  43. package/dist/chat/payload.d.ts +4 -4
  44. package/dist/chat/payload.js +2 -2
  45. package/dist/chat/permission-prompt.d.ts +23 -23
  46. package/dist/chat/prompt-suggestions.d.ts +4 -4
  47. package/dist/chat/prompt-suggestions.js +1 -1
  48. package/dist/chat/reasoning.d.ts +3 -3
  49. package/dist/chat/reasoning.js +1 -1
  50. package/dist/chat/thread-divider.d.ts +3 -3
  51. package/dist/chat/thread-event.d.ts +10 -5
  52. package/dist/chat/thread-event.js +3 -2
  53. package/dist/chat/thread.d.ts +23 -7
  54. package/dist/chat/thread.js +28 -5
  55. package/dist/chat/tool-call.d.ts +2 -2
  56. package/dist/chat/tool-call.js +2 -2
  57. package/dist/index.d.ts +1 -0
  58. package/dist/index.js +1 -0
  59. package/dist/lib/chat.d.ts +42 -42
  60. package/dist/lib/know.d.ts +14 -12
  61. package/dist/lib/mcp-input.d.ts +316 -0
  62. package/dist/lib/mcp-input.js +369 -0
  63. package/dist/lib/render-prop.d.ts +4 -4
  64. package/dist/lib/safe-markdown.d.ts +1 -1
  65. package/dist/lib/status.d.ts +21 -0
  66. package/dist/lib/status.js +17 -0
  67. package/dist/lib/use-shortcut.d.ts +1 -1
  68. package/dist/lib/use-virtual-list.d.ts +6 -6
  69. package/dist/styles.css +4 -1
  70. package/dist/ui/button.d.ts +1 -1
  71. package/dist/ui/button.js +1 -1
  72. package/package.json +1 -1
@@ -25,9 +25,9 @@ declare function Reasoning({ entries, thinking, thoughtFor, className, }: {
25
25
  /** The steps, each verified (with evidence), believed or assumed. */
26
26
  entries: ReasoningEntry[];
27
27
  /** The agent is still thinking; opens the panel and shows progress. */
28
- thinking?: boolean;
28
+ thinking?: boolean | undefined;
29
29
  /** How long it thought, once known, e.g. "6s". */
30
- thoughtFor?: string;
31
- className?: string;
30
+ thoughtFor?: string | undefined;
31
+ className?: string | undefined;
32
32
  }): React.JSX.Element;
33
33
  export { Reasoning };
@@ -35,6 +35,6 @@ function Reasoning({ entries, thinking = false, thoughtFor, className, }) {
35
35
  React.useEffect(() => setOpen(thinking), [thinking]);
36
36
  const assumed = entries.filter((e) => e.basis === "assumed").length;
37
37
  const summary = thinking ? null : (_jsxs(_Fragment, { children: [t("reasoning"), thoughtFor && _jsxs(_Fragment, { children: [" \u00B7 ", thoughtFor] }), " \u00B7 ", t("steps", { count: entries.length }), assumed > 0 && _jsxs("span", { className: "text-warning-text", children: [" \u00B7 ", t("assumedCount", { count: assumed })] })] }));
38
- return (_jsxs(Collapsible, { open: open, onOpenChange: setOpen, "data-slot": "reasoning", className: cn("group/reasoning", className), children: [_jsxs(CollapsibleTrigger, { className: "relative after:absolute after:inset-x-0 after:-inset-y-0.5 inline-flex items-center gap-1.5 rounded-md py-0.5 pe-1.5 text-xs text-muted-foreground outline-none transition-colors duration-150 hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/40", children: [_jsx(Brain, { "aria-hidden": true, className: cn("size-3.5", thinking && "text-accent-text") }), thinking ? _jsx(ShimmerLabel, { className: "text-xs", children: t("thinking") }) : summary, _jsx(ChevronRight, { "aria-hidden": true, className: "rtl:-scale-x-100 size-3 transition-transform duration-200 ease-standard group-data-[state=open]/reasoning:[transform:rotate(90deg)]" })] }), _jsx(CollapsibleContent, { children: _jsx("ol", { className: "mt-2 ms-1.5 flex flex-col gap-3 border-s ps-4", children: entries.map((e) => (_jsxs("li", { className: "relative animate-rise-in", children: [_jsx("span", { "aria-hidden": true, className: "absolute top-1.5 -start-[1.2rem] size-1.5 rounded-full bg-border-strong" }), _jsxs("div", { className: "flex flex-wrap items-baseline gap-x-2 gap-y-1", children: [_jsx("p", { className: "text-sm font-medium", children: e.title }), _jsx("span", { className: cn("rounded-xs px-1 text-2xs font-medium", basisTone[e.basis]), children: t(e.basis) })] }), e.body && _jsx("p", { className: "mt-0.5 text-sm text-muted-foreground", children: e.body }), _jsx("p", { className: "mt-1 text-xs text-faint-foreground", children: e.basis === "verified" ? _jsxs(_Fragment, { children: [t("evidence"), " ", e.evidence] }) : _jsxs(_Fragment, { children: [t("assumption"), " ", e.assumption] }) })] }, e.id))) }) })] }));
38
+ return (_jsxs(Collapsible, { open: open, onOpenChange: setOpen, "data-slot": "reasoning", className: cn("group/reasoning", className), children: [_jsxs(CollapsibleTrigger, { className: "relative after:absolute after:inset-x-0 after:-inset-y-0.5 inline-flex items-center gap-1.5 rounded-md py-0.5 pe-1.5 text-xs text-muted-foreground outline-none transition-colors duration-150 hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/80", children: [_jsx(Brain, { "aria-hidden": true, className: cn("size-3.5", thinking && "text-accent-text") }), thinking ? _jsx(ShimmerLabel, { className: "text-xs", children: t("thinking") }) : summary, _jsx(ChevronRight, { "aria-hidden": true, className: "rtl:-scale-x-100 size-3 transition-transform duration-200 ease-standard group-data-[state=open]/reasoning:[transform:rotate(90deg)]" })] }), _jsx(CollapsibleContent, { children: _jsx("ol", { className: "mt-2 ms-1.5 flex flex-col gap-3 border-s ps-4", children: entries.map((e) => (_jsxs("li", { className: "relative animate-rise-in", children: [_jsx("span", { "aria-hidden": true, className: "absolute top-1.5 -start-[1.2rem] size-1.5 rounded-full bg-border-strong" }), _jsxs("div", { className: "flex flex-wrap items-baseline gap-x-2 gap-y-1", children: [_jsx("p", { className: "text-sm font-medium", children: e.title }), _jsx("span", { className: cn("rounded-xs px-1 text-2xs font-medium", basisTone[e.basis]), children: t(e.basis) })] }), e.body && _jsx("p", { className: "mt-0.5 text-sm text-muted-foreground", children: e.body }), _jsx("p", { className: "mt-1 text-xs text-faint-foreground", children: e.basis === "verified" ? _jsxs(_Fragment, { children: [t("evidence"), " ", e.evidence] }) : _jsxs(_Fragment, { children: [t("assumption"), " ", e.assumption] }) })] }, e.id))) }) })] }));
39
39
  }
40
40
  export { Reasoning };
@@ -10,9 +10,9 @@ export declare const threadDividerStrings: {
10
10
  */
11
11
  declare function ThreadDivider({ date, label, className, }: {
12
12
  /** A day to mark, shown as Today, Yesterday or the date in the locale. With neither this nor `label`, a plain rule. */
13
- date?: Date | string;
13
+ date?: Date | string | undefined;
14
14
  /** A chapter or any other label; wins over `date`. */
15
- label?: string;
16
- className?: string;
15
+ label?: string | undefined;
16
+ className?: string | undefined;
17
17
  }): import("react").JSX.Element;
18
18
  export { ThreadDivider };
@@ -1,19 +1,24 @@
1
1
  import * as React from "react";
2
2
  import { type LucideIcon } from "lucide-react";
3
+ import type { DateStyle } from "../blocks/locale.js";
3
4
  /**
4
5
  * A quiet, centred line in a thread for something that happened rather than something said: "Sam joined", "Switched
5
6
  * to the staging environment", "Run stopped by a guardrail". System messages are drawn with it too, so app events and
6
7
  * system lines look the same. Text here is the app's own, never model output.
7
8
  */
8
- declare function ThreadEvent({ icon: Icon, tone, at, children, className, ...props }: {
9
+ declare function ThreadEvent({ icon: Icon, tone, at, timeFormat, align, children, className, ...props }: {
9
10
  /** The glyph before the text; `null` for none. Default: an info mark. */
10
- icon?: LucideIcon | null;
11
+ icon?: LucideIcon | null | undefined;
11
12
  /** "quiet" (the default) or "attention", for an event a reader shouldn't miss. */
12
- tone?: "quiet" | "attention";
13
+ tone?: "quiet" | "attention" | undefined;
13
14
  /** When it happened, shown after the text as a relative time. */
14
- at?: string;
15
+ at?: string | undefined;
16
+ /** "relative" (the default) or an absolute "time", "date" or "datetime" in the locale. */
17
+ timeFormat?: "relative" | DateStyle | undefined;
18
+ /** "center" (the default), or "start": a left-aligned line (right-aligned in RTL) with the time at its end. */
19
+ align?: "center" | "start" | undefined;
15
20
  /** What happened, in a short line. */
16
21
  children: React.ReactNode;
17
- className?: string;
22
+ className?: string | undefined;
18
23
  } & Omit<React.ComponentProps<"div">, "children">): React.JSX.Element;
19
24
  export { ThreadEvent };
@@ -9,7 +9,8 @@ import { RelativeTime } from "../blocks/relative-time.js";
9
9
  * to the staging environment", "Run stopped by a guardrail". System messages are drawn with it too, so app events and
10
10
  * system lines look the same. Text here is the app's own, never model output.
11
11
  */
12
- function ThreadEvent({ icon: Icon = Info, tone = "quiet", at, children, className, ...props }) {
13
- return (_jsxs("div", { "data-slot": "thread-event", "data-tone": tone, className: cn("flex items-center justify-center gap-1.5 py-1 text-center text-xs", tone === "attention" ? "text-attention-text" : "text-faint-foreground", className), ...props, children: [Icon && _jsx(Icon, { "aria-hidden": true, className: "size-3 shrink-0" }), children, at && (_jsxs(_Fragment, { children: [_jsx("span", { "aria-hidden": true, children: "\u00B7" }), _jsx(RelativeTime, { date: at })] }))] }));
12
+ function ThreadEvent({ icon: Icon = Info, tone = "quiet", at, timeFormat, align = "center", children, className, ...props }) {
13
+ return (_jsxs("div", { "data-slot": "thread-event", "data-tone": tone, "data-align": align, className: cn("flex items-center gap-1.5 py-1 text-xs", align === "start" ? "justify-start text-start" : "justify-center text-center", tone === "attention" ? "text-attention-text" : "text-faint-foreground", className), ...props, children: [Icon && _jsx(Icon, { "aria-hidden": true, className: "size-3 shrink-0" }), children, at &&
14
+ (align === "start" ? (_jsx(RelativeTime, { date: at, format: timeFormat, className: "ms-auto shrink-0 tabular-nums" })) : (_jsxs(_Fragment, { children: [_jsx("span", { "aria-hidden": true, children: "\u00B7" }), _jsx(RelativeTime, { date: at, format: timeFormat })] })))] }));
14
15
  }
15
16
  export { ThreadEvent };
@@ -13,17 +13,33 @@ export declare const threadStrings: {
13
13
  * For long histories, `virtualize` renders only the messages in or near view
14
14
  * (each child is one item, keyed by its `key`). The column stays a `log`, the
15
15
  * message holding focus stays rendered, and Jump to latest works the same.
16
+ *
17
+ * It opens at the bottom, or at `anchor` (the `id` of something in it, such as
18
+ * a chapter divider or the card that needs the reader), and then waits for the
19
+ * reader instead of following.
16
20
  */
17
- declare function Thread({ children, empty, footer, className, contentClassName, virtualize, }: {
18
- children?: React.ReactNode;
21
+ declare function Thread({ children, empty, footer, className, contentClassName, virtualize, anchor, scrollerRef, onDrop, fade, }: {
22
+ children?: React.ReactNode | undefined;
19
23
  /** Shown before the first message. */
20
- empty?: React.ReactNode;
24
+ empty?: React.ReactNode | undefined;
21
25
  /** Below the messages, usually the composer. */
22
- footer?: React.ReactNode;
23
- className?: string;
26
+ footer?: React.ReactNode | undefined;
27
+ className?: string | undefined;
24
28
  /** Classes for the message column. */
25
- contentClassName?: string;
29
+ contentClassName?: string | undefined;
26
30
  /** Render only the messages in or near view, for histories of hundreds of messages. Give each child a `key`. Defaults to false. */
27
- virtualize?: boolean;
31
+ virtualize?: boolean | undefined;
32
+ /**
33
+ * The `id` of an element in the thread to open at, instead of the bottom: a chapter divider, or the card that needs
34
+ * the reader. Read once, when the messages first appear; if it isn't among them, the thread opens at the bottom.
35
+ * Ignored with `virtualize`, where it may not be rendered yet.
36
+ */
37
+ anchor?: string | undefined;
38
+ /** The scrolling element, for your own scroll handling. */
39
+ scrollerRef?: React.Ref<HTMLDivElement> | undefined;
40
+ /** Files or other data dropped on the thread. Dragging over it is allowed only when this is given. */
41
+ onDrop?: ((event: React.DragEvent<HTMLDivElement>) => void) | undefined;
42
+ /** The soft fade at the bottom of the column. Defaults to true. */
43
+ fade?: boolean | undefined;
28
44
  }): React.JSX.Element;
29
45
  export { Thread };
@@ -19,18 +19,41 @@ export const threadStrings = {
19
19
  * For long histories, `virtualize` renders only the messages in or near view
20
20
  * (each child is one item, keyed by its `key`). The column stays a `log`, the
21
21
  * message holding focus stays rendered, and Jump to latest works the same.
22
+ *
23
+ * It opens at the bottom, or at `anchor` (the `id` of something in it, such as
24
+ * a chapter divider or the card that needs the reader), and then waits for the
25
+ * reader instead of following.
22
26
  */
23
- function Thread({ children, empty, footer, className, contentClassName, virtualize = false, }) {
27
+ function Thread({ children, empty, footer, className, contentClassName, virtualize = false, anchor, scrollerRef, onDrop, fade = true, }) {
24
28
  const t = useStrings("thread", threadStrings);
25
29
  const scroller = React.useRef(null);
26
30
  const content = React.useRef(null);
27
- const pinnedRef = React.useRef(true);
28
- const [pinned, setPinned] = React.useState(true);
31
+ // Opening at an anchor means not following the bottom until the reader gets there.
32
+ const anchoring = Boolean(anchor) && !virtualize;
33
+ const pinnedRef = React.useRef(!anchoring);
34
+ const [pinned, setPinned] = React.useState(!anchoring);
35
+ React.useImperativeHandle(scrollerRef, () => scroller.current, []);
29
36
  const toBottom = React.useCallback((behavior = "auto") => {
30
37
  const el = scroller.current;
31
38
  if (el)
32
39
  el.scrollTo({ top: el.scrollHeight, behavior });
33
40
  }, []);
41
+ // Open at the anchor once, when it first appears.
42
+ const anchored = React.useRef(false);
43
+ React.useLayoutEffect(() => {
44
+ if (!anchoring || anchored.current || !content.current?.childElementCount)
45
+ return;
46
+ anchored.current = true;
47
+ const target = content.current.querySelector(`#${CSS.escape(anchor)}`);
48
+ if (target) {
49
+ target.scrollIntoView?.({ block: "start" });
50
+ return;
51
+ }
52
+ // The anchor isn't in the thread: open at the bottom and follow, as without one.
53
+ pinnedRef.current = true;
54
+ setPinned(true);
55
+ toBottom();
56
+ });
34
57
  React.useEffect(() => {
35
58
  const el = content.current;
36
59
  if (!el)
@@ -75,7 +98,7 @@ function Thread({ children, empty, footer, className, contentClassName, virtuali
75
98
  pin: focused,
76
99
  gap: () => (content.current ? Number.parseFloat(getComputedStyle(content.current).rowGap) || 0 : 0),
77
100
  });
78
- return (_jsxs("div", { "data-slot": "thread", className: cn("flex min-h-0 flex-col", className), children: [_jsxs("div", { className: "relative min-h-0 flex-1", children: [_jsx("div", { ref: scroller, onScroll: onScroll, onFocus: virtualize
101
+ return (_jsxs("div", { "data-slot": "thread", className: cn("flex min-h-0 flex-col", className), children: [_jsxs("div", { className: "relative min-h-0 flex-1", children: [_jsx("div", { ref: scroller, onScroll: onScroll, onDragOver: onDrop ? (e) => e.preventDefault() : undefined, onDrop: onDrop, onFocus: virtualize
79
102
  ? (e) => {
80
103
  setFocused(e.target.closest("[data-virtual-key]")?.getAttribute("data-virtual-key") ?? null);
81
104
  }
@@ -83,6 +106,6 @@ function Thread({ children, empty, footer, className, contentClassName, virtuali
83
106
  pinnedRef.current = true;
84
107
  setPinned(true);
85
108
  toBottom("smooth");
86
- }, className: "absolute bottom-3 left-1/2 z-10 inline-flex -translate-x-1/2 items-center gap-1.5 rounded-full border bg-popover px-3 py-1.5 text-xs font-medium shadow-float animate-in fade-in-0 slide-in-from-bottom-1", children: [_jsx(ArrowDown, { "aria-hidden": true, className: "size-3" }), " ", t("jumpToLatest")] })), _jsx("div", { "aria-hidden": true, className: "pointer-events-none absolute inset-x-0 bottom-0 h-6 bg-gradient-to-t from-background to-transparent" })] }), footer && _jsx("div", { className: "mx-auto w-full max-w-(--cd-thread-width) px-4 pb-4", children: footer })] }));
109
+ }, className: "absolute bottom-3 left-1/2 z-10 inline-flex -translate-x-1/2 items-center gap-1.5 rounded-full border bg-popover px-3 py-1.5 text-xs font-medium shadow-float animate-in fade-in-0 slide-in-from-bottom-1", children: [_jsx(ArrowDown, { "aria-hidden": true, className: "size-3" }), " ", t("jumpToLatest")] })), fade && _jsx("div", { "aria-hidden": true, "data-slot": "thread-fade", className: "pointer-events-none absolute inset-x-0 bottom-0 h-6 bg-gradient-to-t from-background to-transparent" })] }), footer && _jsx("div", { className: "mx-auto w-full max-w-(--cd-thread-width) px-4 pb-4", children: footer })] }));
87
110
  }
88
111
  export { Thread };
@@ -25,12 +25,12 @@ export declare const toolCallStrings: {
25
25
  declare function ToolCall({ call, className, }: {
26
26
  /** The call: tool name, plain-words title, status, input, output, duration or error. */
27
27
  call: ToolCallData;
28
- className?: string;
28
+ className?: string | undefined;
29
29
  }): React.JSX.Element;
30
30
  /** A run of tool calls, with a count when it's long enough to fold. */
31
31
  declare function ToolCallGroup({ calls, className, }: {
32
32
  /** Several calls, summarised as a group. */
33
33
  calls: ToolCallData[];
34
- className?: string;
34
+ className?: string | undefined;
35
35
  }): React.JSX.Element;
36
36
  export { ToolCall, ToolCallGroup };
@@ -34,7 +34,7 @@ function ToolCall({ call, className, }) {
34
34
  const failed = call.status === "failed";
35
35
  const [open, setOpen] = React.useState(failed);
36
36
  const hasDetail = Boolean(call.input || call.output || call.error || call.approval?.note || call.approval?.originalInput);
37
- return (_jsxs(Collapsible, { open: open, onOpenChange: setOpen, "data-slot": "tool-call", "data-status": call.status, className: cn("group/tool", className), children: [_jsxs(CollapsibleTrigger, { disabled: !hasDetail, className: "flex w-full items-center gap-2.5 rounded-md px-1.5 py-1 text-start outline-none transition-colors duration-150 enabled:hover:bg-accent focus-visible:ring-[3px] focus-visible:ring-ring/40", children: [_jsx(StepMark, { status: call.status }), _jsxs("span", { className: "min-w-0 flex-1 truncate text-sm", children: [call.status === "running" ? _jsx(ShimmerLabel, { className: "font-normal", children: call.title }) : call.title, _jsxs("span", { className: "sr-only", children: [" \u2014 ", statusLabel(call.status)] })] }), call.approval?.originalInput !== undefined && (_jsx("span", { "data-slot": "tool-call-edited", className: "shrink-0 rounded-xs bg-warning-weak px-1 text-2xs text-warning-text", children: t("editedBeforeRun") })), call.approval?.by === "automatic" && (_jsx("span", { "data-slot": "tool-call-approval", className: "shrink-0 rounded-xs bg-muted px-1 text-2xs text-muted-foreground", children: t("approvedAutomatically") })), _jsx("code", { className: "hidden shrink-0 font-mono text-2xs text-faint-foreground sm:inline", children: call.tool }), call.duration && _jsx("span", { className: "shrink-0 text-xs tabular-nums text-faint-foreground", children: call.duration }), hasDetail && (_jsx(ChevronRight, { "aria-hidden": true, className: "rtl:-scale-x-100 size-3.5 shrink-0 text-faint-foreground transition-transform duration-200 ease-standard group-data-[state=open]/tool:[transform:rotate(90deg)]" }))] }), _jsx(CollapsibleContent, { className: "data-[state=open]:animate-in data-[state=open]:fade-in-0", children: _jsxs("div", { className: "mt-1 mb-2 ms-[1.375rem] flex flex-col gap-2 border-s ps-3.5", children: [call.error && (_jsx("p", { className: "rounded-md bg-destructive-weak px-2.5 py-1.5 text-xs text-destructive-text", children: call.error })), call.approval?.note && (_jsxs("p", { className: "text-xs text-muted-foreground", children: [call.approval.by === "automatic" ? t("approvedAutomatically") : t("approvedByPerson"), ": ", call.approval.note] })), call.approval?.originalInput !== undefined && _jsx("p", { className: "text-xs text-muted-foreground", children: t("editedNote") }), call.input && _jsx(Payload, { language: "json", title: t("input"), text: call.input }), call.approval?.originalInput !== undefined && _jsx(Payload, { language: "json", title: t("proposed"), text: call.approval.originalInput }), call.output && _jsx(Payload, { language: call.outputLanguage ?? "text", title: t("output"), text: call.output })] }) })] }));
37
+ return (_jsxs(Collapsible, { open: open, onOpenChange: setOpen, "data-slot": "tool-call", "data-status": call.status, className: cn("group/tool", className), children: [_jsxs(CollapsibleTrigger, { disabled: !hasDetail, className: "flex w-full items-center gap-2.5 rounded-md px-1.5 py-1 text-start outline-none transition-colors duration-150 enabled:hover:bg-accent focus-visible:ring-[3px] focus-visible:ring-ring/80", children: [_jsx(StepMark, { status: call.status }), _jsxs("span", { className: "min-w-0 flex-1 truncate text-sm", children: [call.status === "running" ? _jsx(ShimmerLabel, { className: "font-normal", children: call.title }) : call.title, _jsxs("span", { className: "sr-only", children: [" \u2014 ", statusLabel(call.status)] })] }), call.approval?.originalInput !== undefined && (_jsx("span", { "data-slot": "tool-call-edited", className: "shrink-0 rounded-xs bg-warning-weak px-1 text-2xs text-warning-text", children: t("editedBeforeRun") })), call.approval?.by === "automatic" && (_jsx("span", { "data-slot": "tool-call-approval", className: "shrink-0 rounded-xs bg-muted px-1 text-2xs text-muted-foreground", children: t("approvedAutomatically") })), _jsx("code", { className: "hidden shrink-0 font-mono text-2xs text-faint-foreground sm:inline", children: call.tool }), call.duration && _jsx("span", { className: "shrink-0 text-xs tabular-nums text-faint-foreground", children: call.duration }), hasDetail && (_jsx(ChevronRight, { "aria-hidden": true, className: "rtl:-scale-x-100 size-3.5 shrink-0 text-faint-foreground transition-transform duration-200 ease-standard group-data-[state=open]/tool:[transform:rotate(90deg)]" }))] }), _jsx(CollapsibleContent, { className: "data-[state=open]:animate-in data-[state=open]:fade-in-0", children: _jsxs("div", { className: "mt-1 mb-2 ms-[1.375rem] flex flex-col gap-2 border-s ps-3.5", children: [call.error && (_jsx("p", { className: "rounded-md bg-destructive-weak px-2.5 py-1.5 text-xs text-destructive-text", children: call.error })), call.approval?.note && (_jsxs("p", { className: "text-xs text-muted-foreground", children: [call.approval.by === "automatic" ? t("approvedAutomatically") : t("approvedByPerson"), ": ", call.approval.note] })), call.approval?.originalInput !== undefined && _jsx("p", { className: "text-xs text-muted-foreground", children: t("editedNote") }), call.input && _jsx(Payload, { language: "json", title: t("input"), text: call.input }), call.approval?.originalInput !== undefined && _jsx(Payload, { language: "json", title: t("proposed"), text: call.approval.originalInput }), call.output && _jsx(Payload, { language: call.outputLanguage ?? "text", title: t("output"), text: call.output })] }) })] }));
38
38
  }
39
39
  /** A run of tool calls, with a count when it's long enough to fold. */
40
40
  function ToolCallGroup({ calls, className, }) {
@@ -46,6 +46,6 @@ function ToolCallGroup({ calls, className, }) {
46
46
  if (calls.length <= 2) {
47
47
  return (_jsx("div", { "data-slot": "tool-call-group", className: cn("flex flex-col", className), children: calls.map((c) => (_jsx(ToolCall, { call: c }, c.id))) }));
48
48
  }
49
- return (_jsxs(Collapsible, { open: open, onOpenChange: setOpen, "data-slot": "tool-call-group", className: cn("group/tools rounded-lg border bg-card", className), children: [_jsxs(CollapsibleTrigger, { className: "flex w-full items-center gap-2 px-3 py-2 text-start text-xs text-muted-foreground outline-none hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/40 rounded-lg", children: [_jsx(Wrench, { "aria-hidden": true, className: "size-3.5" }), _jsxs("span", { className: "flex-1", children: [t("calls", { count: calls.length }), done > 0 && ` · ${t("done", { count: done })}`, failed > 0 && _jsxs("span", { className: "text-destructive-text", children: [" \u00B7 ", t("failed", { count: failed })] }), running && ` · ${t("working")}`] }), _jsx(ChevronRight, { "aria-hidden": true, className: "rtl:-scale-x-100 size-3.5 transition-transform duration-200 ease-standard group-data-[state=open]/tools:[transform:rotate(90deg)]" })] }), _jsx(CollapsibleContent, { children: _jsx("div", { className: "flex flex-col border-t px-1.5 py-1.5", children: calls.map((c) => (_jsx(ToolCall, { call: c }, c.id))) }) })] }));
49
+ return (_jsxs(Collapsible, { open: open, onOpenChange: setOpen, "data-slot": "tool-call-group", className: cn("group/tools rounded-lg border bg-card", className), children: [_jsxs(CollapsibleTrigger, { className: "flex w-full items-center gap-2 px-3 py-2 text-start text-xs text-muted-foreground outline-none hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/80 rounded-lg", children: [_jsx(Wrench, { "aria-hidden": true, className: "size-3.5" }), _jsxs("span", { className: "flex-1", children: [t("calls", { count: calls.length }), done > 0 && ` · ${t("done", { count: done })}`, failed > 0 && _jsxs("span", { className: "text-destructive-text", children: [" \u00B7 ", t("failed", { count: failed })] }), running && ` · ${t("working")}`] }), _jsx(ChevronRight, { "aria-hidden": true, className: "rtl:-scale-x-100 size-3.5 transition-transform duration-200 ease-standard group-data-[state=open]/tools:[transform:rotate(90deg)]" })] }), _jsx(CollapsibleContent, { children: _jsx("div", { className: "flex flex-col border-t px-1.5 py-1.5", children: calls.map((c) => (_jsx(ToolCall, { call: c }, c.id))) }) })] }));
50
50
  }
51
51
  export { ToolCall, ToolCallGroup };
package/dist/index.d.ts CHANGED
@@ -42,6 +42,7 @@ export * from "./lib/know.js";
42
42
  export * from "./lib/render-prop.js";
43
43
  export * from "./chat/ai-disclosure.js";
44
44
  export * from "./chat/permission-prompt.js";
45
+ export * from "./lib/mcp-input.js";
45
46
  export * from "./lib/utils.js";
46
47
  export { ToolCall } from "./chat/tool-call.js";
47
48
  export type { ToolCall as ToolCallData } from "./lib/chat.js";
package/dist/index.js CHANGED
@@ -42,6 +42,7 @@ export * from "./lib/know.js";
42
42
  export * from "./lib/render-prop.js";
43
43
  export * from "./chat/ai-disclosure.js";
44
44
  export * from "./chat/permission-prompt.js";
45
+ export * from "./lib/mcp-input.js";
45
46
  export * from "./lib/utils.js";
46
47
  export { ToolCall } from "./chat/tool-call.js";
47
48
  export { Message } from "./chat/message.js";
@@ -17,11 +17,11 @@ export type AgentIdentity = {
17
17
  /** The displayed name, e.g. "Architect agent". Never derived from model output. */
18
18
  name: string;
19
19
  role: AgentRole;
20
- specialty?: string;
20
+ specialty?: string | undefined;
21
21
  };
22
22
  export type MessageAuthor = {
23
23
  role: "user";
24
- name?: string;
24
+ name?: string | undefined;
25
25
  } | {
26
26
  role: "agent";
27
27
  agent: AgentIdentity;
@@ -30,7 +30,7 @@ export type MessageAuthor = {
30
30
  | {
31
31
  role: "person";
32
32
  name: string;
33
- team?: string;
33
+ team?: string | undefined;
34
34
  }
35
35
  /** System lines are the app's own strings, never model text. */
36
36
  | {
@@ -47,9 +47,9 @@ export type EntityRef = {
47
47
  /** The identifier, e.g. "orders.status" or "billing.invoice.due_date". */
48
48
  name: string;
49
49
  /** A friendlier label when there is one, e.g. "Region". */
50
- label?: string;
50
+ label?: string | undefined;
51
51
  /** Where it lives, when more than one place is in scope, e.g. "Shop staging". */
52
- location?: string;
52
+ location?: string | undefined;
53
53
  };
54
54
  export type InlineNode = {
55
55
  kind: "text";
@@ -68,19 +68,19 @@ export type InlineNode = {
68
68
  | {
69
69
  kind: "measured";
70
70
  text: string;
71
- source?: string;
71
+ source?: string | undefined;
72
72
  }
73
73
  /** @deprecated Renamed `measured`; still rendered the same way until 2.0. */
74
74
  | {
75
75
  kind: "orgData";
76
76
  text: string;
77
- source?: string;
77
+ source?: string | undefined;
78
78
  }
79
79
  /** The agent asserts this but has not verified it. */
80
80
  | {
81
81
  kind: "provisional";
82
82
  nodes: InlineNode[];
83
- reason?: string;
83
+ reason?: string | undefined;
84
84
  } | {
85
85
  kind: "citation";
86
86
  sourceId: string;
@@ -103,21 +103,21 @@ export type MessageSource = {
103
103
  kind: SourceKind;
104
104
  label: string;
105
105
  /** An inert excerpt shown in the popover. Text only. */
106
- snippet?: string;
106
+ snippet?: string | undefined;
107
107
  /** Where it came from in a human sense: "Docs · Sales process v3", "Query on orders". */
108
- origin?: string;
109
- provisional?: boolean;
108
+ origin?: string | undefined;
109
+ provisional?: boolean | undefined;
110
110
  };
111
111
  /** A source a tool's result supports, without the id and [n] an adapter assigns. */
112
112
  export type ToolSource = Omit<MessageSource, "id" | "marker"> & {
113
113
  /** Makes two results naming the same thing one source; defaults to `kind` and `label`. */
114
- key?: string;
114
+ key?: string | undefined;
115
115
  };
116
116
  type ReasoningBase = {
117
117
  id: string;
118
118
  title: string;
119
- body?: string;
120
- streaming?: boolean;
119
+ body?: string | undefined;
120
+ streaming?: boolean | undefined;
121
121
  };
122
122
  export type ReasoningEntry = (ReasoningBase & {
123
123
  basis: "verified";
@@ -134,23 +134,23 @@ export type ToolCall = {
134
134
  title: string;
135
135
  status: RunStatus;
136
136
  /** Shown when expanded. Text only; long values scroll. */
137
- input?: string;
138
- output?: string;
139
- outputLanguage?: PayloadLanguage;
137
+ input?: string | undefined;
138
+ output?: string | undefined;
139
+ outputLanguage?: PayloadLanguage | undefined;
140
140
  /** e.g. "1.2s". Absent while running; never estimated. */
141
- duration?: string;
141
+ duration?: string | undefined;
142
142
  /** Required when status is "failed": what went wrong, in a sentence. */
143
- error?: string;
143
+ error?: string | undefined;
144
144
  /**
145
145
  * Who let it run, when it needed a go-ahead: a person, or something automatic (a policy, a classifier, a rule).
146
146
  * `note` is why, in the source's words. Left out when it needed none or the source doesn't say.
147
147
  */
148
148
  approval?: {
149
149
  by: "person" | "automatic";
150
- note?: string;
150
+ note?: string | undefined;
151
151
  /** The input as the agent proposed it, when a person edited it before it ran; `input` is then what ran. */
152
- originalInput?: string;
153
- };
152
+ originalInput?: string | undefined;
153
+ } | undefined;
154
154
  };
155
155
  /** The block's language, e.g. "json", "sql", "python", "text". Shown as a label; never executed or highlighted as HTML. */
156
156
  export type PayloadLanguage = string;
@@ -173,13 +173,13 @@ export type MessageBlock = {
173
173
  nodes: InlineNode[];
174
174
  } | {
175
175
  kind: "list";
176
- ordered?: boolean;
176
+ ordered?: boolean | undefined;
177
177
  items: InlineNode[][];
178
178
  } | {
179
179
  kind: "payload";
180
180
  language: PayloadLanguage;
181
181
  text: string;
182
- title?: string;
182
+ title?: string | undefined;
183
183
  } | {
184
184
  kind: "tools";
185
185
  calls: ToolCall[];
@@ -188,7 +188,7 @@ export type MessageBlock = {
188
188
  | {
189
189
  kind: "degraded";
190
190
  reason: string;
191
- missing?: string;
191
+ missing?: string | undefined;
192
192
  }
193
193
  /**
194
194
  * A typed card (a result table, a plan, an approval) rendered by the app's
@@ -211,53 +211,53 @@ export type Message = {
211
211
  id: string;
212
212
  author: MessageAuthor;
213
213
  /** When it was sent. Absent when the source doesn't say; then no time is shown. */
214
- createdAt?: string;
214
+ createdAt?: string | undefined;
215
215
  status: MessageStatus;
216
216
  blocks: MessageBlock[];
217
- reasoning?: ReasoningEntry[];
217
+ reasoning?: ReasoningEntry[] | undefined;
218
218
  /** How long the agent spent before answering, e.g. "6s". Only once known. */
219
- thoughtFor?: string;
220
- sources?: MessageSource[];
219
+ thoughtFor?: string | undefined;
220
+ sources?: MessageSource[] | undefined;
221
221
  /** Shown under an agent's reply: model, cost. Only values the service measured. */
222
222
  attribution?: {
223
- model?: string;
224
- cost?: string;
225
- };
223
+ model?: string | undefined;
224
+ cost?: string | undefined;
225
+ } | undefined;
226
226
  };
227
227
  /** Where the next message's work lands. */
228
228
  export type ComposerScope = {
229
- account?: string;
229
+ account?: string | undefined;
230
230
  environment: string;
231
- detail?: string;
231
+ detail?: string | undefined;
232
232
  production: boolean;
233
233
  };
234
234
  export type MentionOption = {
235
235
  id: string;
236
236
  /** The heading it sits under in the menu, e.g. "Files", "People". Groups appear in the order first used. */
237
237
  group: string;
238
- entity?: EntityRef;
238
+ entity?: EntityRef | undefined;
239
239
  label: string;
240
- description?: string;
240
+ description?: string | undefined;
241
241
  };
242
242
  export type SlashCommand = {
243
243
  id: string;
244
244
  name: string;
245
245
  description: string;
246
246
  /** e.g. "<object>" — shown after the name. */
247
- args?: string;
247
+ args?: string | undefined;
248
248
  };
249
249
  export type Attachment = {
250
250
  id: string;
251
251
  name: string;
252
252
  /** In bytes. */
253
- size?: number;
253
+ size?: number | undefined;
254
254
  /** e.g. "image/png", "application/pdf": picks the icon, and allows an image preview. */
255
- mediaType?: string;
255
+ mediaType?: string | undefined;
256
256
  /** A thumbnail your app serves (or a blob: URL), shown for images. Never a URL from model output. */
257
- previewUrl?: string;
257
+ previewUrl?: string | undefined;
258
258
  /** 0–1 while uploading; absent when done. */
259
- progress?: number;
260
- error?: string;
259
+ progress?: number | undefined;
260
+ error?: string | undefined;
261
261
  };
262
262
  export type PromptSuggestion = {
263
263
  id: string;
@@ -14,17 +14,17 @@ export type KnowSource = {
14
14
  door: KnowDoor;
15
15
  label: string;
16
16
  /** Who added it, for files, links and answers. */
17
- addedBy?: string;
18
- addedAt?: string;
17
+ addedBy?: string | undefined;
18
+ addedAt?: string | undefined;
19
19
  /** When it was last read; null if never. */
20
20
  readAt: string | null;
21
21
  /** How often it should be re-read, in hours, when it has a schedule. */
22
- everyHours?: number;
22
+ everyHours?: number | undefined;
23
23
  status: RunStatus;
24
24
  /** "214 objects", "38 pages" — only when counted. */
25
- extent?: string;
25
+ extent?: string | undefined;
26
26
  /** Required when status is "failed". */
27
- error?: string;
27
+ error?: string | undefined;
28
28
  };
29
29
  export type Confidence = "high" | "medium" | "low" | "unknown";
30
30
  export type RaciRole = "R" | "A" | "C" | "I";
@@ -42,7 +42,7 @@ export type RetrievedPassage = {
42
42
  /** Whether the answer actually cites it. */
43
43
  cited: boolean;
44
44
  /** Why it ranked where it did, when the retriever says. */
45
- why?: string;
45
+ why?: string | undefined;
46
46
  readAt: string | null;
47
47
  };
48
48
  /** A sentence in an answer and the passages it rests on. */
@@ -57,7 +57,7 @@ export type ModelOption = {
57
57
  vendor: ModelVendor;
58
58
  name: string;
59
59
  /** One line on what it's good for. */
60
- note?: string;
60
+ note?: string | undefined;
61
61
  /** Relative cost, 1 (cheapest) to 4. */
62
62
  cost: 1 | 2 | 3 | 4;
63
63
  /** The vendor's zero-retention contract has been attested by an operator. */
@@ -70,7 +70,7 @@ export type ModelSettingsValue = {
70
70
  /** Most tool-calling steps per run before it stops and asks. */
71
71
  stepLimit: number;
72
72
  /** Used when the primary is unavailable; must also be attested. */
73
- fallback?: string;
73
+ fallback?: string | undefined;
74
74
  };
75
75
  export type ToolPermission = "always" | "ask" | "never";
76
76
  export type ToolSetting = {
@@ -81,7 +81,7 @@ export type ToolSetting = {
81
81
  effect: "read" | "write" | "deploy" | "outside";
82
82
  permission: ToolPermission;
83
83
  /** Permissions this tool can't take, with why. */
84
- locked?: Partial<Record<ToolPermission, string>>;
84
+ locked?: Partial<Record<ToolPermission, string | undefined>> | undefined;
85
85
  };
86
86
  export type AgentConfig = {
87
87
  agent: AgentIdentity;
@@ -93,7 +93,7 @@ export type AgentConfig = {
93
93
  ok: number;
94
94
  failed: number;
95
95
  }[];
96
- lastFailure?: string;
96
+ lastFailure?: string | undefined;
97
97
  };
98
98
  export type Memory = {
99
99
  id: string;
@@ -106,15 +106,17 @@ export type Memory = {
106
106
  };
107
107
  createdAt: string;
108
108
  scope: "account" | "environment";
109
- environmentId?: string;
109
+ environmentId?: string | undefined;
110
110
  };
111
+ /** Whether agents use memory: on, paused (kept and not used for now), or off. */
112
+ export type MemoryModeValue = "on" | "paused" | "off";
111
113
  export type Version = {
112
114
  id: string;
113
115
  label: string;
114
116
  state: "live" | "draft" | "past";
115
117
  at: string;
116
118
  by: string;
117
- note?: string;
119
+ note?: string | undefined;
118
120
  };
119
121
  export type ContextSegment = {
120
122
  label: string;