@alexkroman1/aai-ui 0.12.3 → 0.14.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 (203) hide show
  1. package/README.md +253 -0
  2. package/dist/_colors-CpZO-88A.js +78 -0
  3. package/dist/_module-url-C13kAJ87.js +15 -0
  4. package/dist/_page-shell.d.ts +11 -0
  5. package/dist/_recover-run.d.ts +79 -0
  6. package/dist/_repeat-until.d.ts +30 -0
  7. package/dist/_run-controls.d.ts +33 -0
  8. package/dist/_submission-state.d.ts +92 -0
  9. package/dist/_tap-to-talk-state.d.ts +88 -0
  10. package/dist/_utils-Mi-SCFA1.js +47 -0
  11. package/dist/_utils.d.ts +26 -0
  12. package/dist/_web-storage.d.ts +43 -0
  13. package/dist/_workflow-api-ref.d.ts +37 -0
  14. package/dist/_workflow-files.d.ts +34 -0
  15. package/dist/_workflow-test-defs.d.ts +34 -0
  16. package/dist/aai-logo-D8pMVn9_.js +85 -0
  17. package/dist/agent-state-labels.d.ts +65 -0
  18. package/dist/agent-state.d.ts +108 -0
  19. package/dist/audio/capture.d.ts +44 -0
  20. package/dist/audio/index.d.ts +18 -0
  21. package/dist/audio/index.js +431 -0
  22. package/dist/audio/preconnect.d.ts +87 -0
  23. package/dist/audio/voice-io.d.ts +112 -0
  24. package/dist/build-default-client.d.ts +1 -0
  25. package/dist/chat-view-BCbI7gD4.js +396 -0
  26. package/dist/client-config-C7fQrioM.js +139 -0
  27. package/dist/client-config.d.ts +119 -0
  28. package/dist/client-dir.d.ts +44 -0
  29. package/dist/client-dir.js +58 -0
  30. package/dist/client-identity.d.ts +131 -0
  31. package/dist/client-tool.d.ts +51 -0
  32. package/dist/components/_colors.d.ts +73 -0
  33. package/dist/components/_form-readiness.d.ts +56 -0
  34. package/dist/components/_form-values.d.ts +19 -0
  35. package/dist/components/aai-logo.d.ts +12 -0
  36. package/dist/components/audio-result.d.ts +94 -0
  37. package/dist/components/auto-scroll.d.ts +74 -0
  38. package/dist/components/bullet-list.d.ts +74 -0
  39. package/dist/components/button.d.ts +29 -21
  40. package/dist/components/button.js +62 -23
  41. package/dist/components/chat-view.d.ts +24 -18
  42. package/dist/components/chat-view.js +2 -60
  43. package/dist/components/console-shell.d.ts +85 -0
  44. package/dist/components/controls.d.ts +21 -6
  45. package/dist/components/controls.js +31 -19
  46. package/dist/components/conversation-view.d.ts +130 -0
  47. package/dist/components/eyebrow.d.ts +12 -0
  48. package/dist/components/facts.d.ts +81 -0
  49. package/dist/components/form-fields.d.ts +142 -0
  50. package/dist/components/form-types.d.ts +73 -0
  51. package/dist/components/form.d.ts +96 -0
  52. package/dist/components/markdown.d.ts +57 -0
  53. package/dist/components/message-list.d.ts +34 -7
  54. package/dist/components/message-list.js +2 -75
  55. package/dist/components/session-controls.d.ts +125 -0
  56. package/dist/components/session-error-banner.d.ts +69 -0
  57. package/dist/components/session-state-dot.d.ts +111 -0
  58. package/dist/components/sidebar-layout.d.ts +31 -10
  59. package/dist/components/sidebar-layout.js +32 -12
  60. package/dist/components/start-screen.d.ts +17 -6
  61. package/dist/components/start-screen.js +34 -14
  62. package/dist/components/tool-call-block.d.ts +15 -14
  63. package/dist/components/tool-call-block.js +2 -117
  64. package/dist/components/tool-call-row.d.ts +75 -0
  65. package/dist/components/tool-config-context.d.ts +22 -0
  66. package/dist/components/upload-progress.d.ts +76 -0
  67. package/dist/components/url-chips.d.ts +29 -0
  68. package/dist/components/workflow-fields.d.ts +46 -0
  69. package/dist/components/workflow-pending-note.d.ts +81 -0
  70. package/dist/components/workflow-progress.d.ts +81 -0
  71. package/dist/components/workflow-run-error.d.ts +48 -0
  72. package/dist/components/workflow-run-panel.d.ts +79 -0
  73. package/dist/context.d.ts +288 -0
  74. package/dist/context.js +382 -0
  75. package/dist/conversation-log.d.ts +88 -0
  76. package/dist/default-client/assets/_module-url-BX0RuRU2.js +1 -0
  77. package/dist/default-client/assets/audio-mpgipo2Q.js +1 -0
  78. package/dist/default-client/assets/capture-processor-D0q7Qrdd.js +90 -0
  79. package/dist/default-client/assets/index-8ihb1Urm.css +2 -0
  80. package/dist/default-client/assets/index-DPfmSFmc.js +126 -0
  81. package/dist/default-client/assets/playback-processor-CjDbc9iH.js +314 -0
  82. package/dist/default-client/favicon.ico +0 -0
  83. package/dist/default-client/index.html +15 -0
  84. package/dist/default-client-entry.d.ts +6 -0
  85. package/dist/default-client.d.ts +19 -0
  86. package/dist/define-client.d.ts +169 -26
  87. package/dist/define-client.js +209 -55
  88. package/dist/eyebrow-UfmSz9yy.js +27 -0
  89. package/dist/hooks-DN4gHYsj.js +182 -0
  90. package/dist/hooks.d.ts +172 -0
  91. package/dist/hooks.js +3 -0
  92. package/dist/hooks.test-d.d.ts +14 -0
  93. package/dist/inbox-protocol.d.ts +128 -0
  94. package/dist/inbox.d.ts +132 -0
  95. package/dist/index.d.ts +126 -29
  96. package/dist/index.js +5500 -15
  97. package/dist/internal.d.ts +43 -0
  98. package/dist/internal.js +8 -0
  99. package/dist/linked-client.d.ts +70 -0
  100. package/dist/message-list-DHZcLhRA.js +682 -0
  101. package/dist/notice-player.d.ts +70 -0
  102. package/dist/page.d.ts +114 -0
  103. package/dist/phone.d.ts +50 -0
  104. package/dist/route-fetch.d.ts +79 -0
  105. package/dist/session/audio-effects.d.ts +37 -0
  106. package/dist/session/audio-setup.d.ts +57 -0
  107. package/dist/session/audio-state.d.ts +157 -0
  108. package/dist/session/browser-session.d.ts +39 -0
  109. package/dist/session/close.d.ts +26 -0
  110. package/dist/session/dial.d.ts +65 -0
  111. package/dist/session/handshake.d.ts +60 -0
  112. package/dist/session/index.d.ts +17 -0
  113. package/dist/session/index.js +2 -0
  114. package/dist/session/messages.d.ts +63 -0
  115. package/dist/session/mic.d.ts +64 -0
  116. package/dist/session/preconnect.d.ts +48 -0
  117. package/dist/session/reconnect.d.ts +25 -0
  118. package/dist/session/resume-store.d.ts +57 -0
  119. package/dist/session/state.d.ts +146 -0
  120. package/dist/session/ticket.d.ts +44 -0
  121. package/dist/session/types.d.ts +476 -0
  122. package/dist/session/url.d.ts +17 -0
  123. package/dist/session/user-turn.d.ts +55 -0
  124. package/dist/session-D9jM5-TF.js +2140 -0
  125. package/dist/stored-value.d.ts +103 -0
  126. package/dist/tool-call-block-uXtMOUWN.js +204 -0
  127. package/dist/tool-config-context-Es4YUzV2.js +19 -0
  128. package/dist/types.d.ts +272 -34
  129. package/dist/types.js +35 -4
  130. package/dist/upload/files.d.ts +83 -0
  131. package/dist/upload/index.d.ts +15 -0
  132. package/dist/upload/pause.d.ts +31 -0
  133. package/dist/upload/recall.d.ts +67 -0
  134. package/dist/upload/report.d.ts +26 -0
  135. package/dist/upload/session.d.ts +99 -0
  136. package/dist/url-chips-oyy_Eiqh.js +239 -0
  137. package/dist/use-client-runs.d.ts +87 -0
  138. package/dist/use-conversation-log.d.ts +86 -0
  139. package/dist/use-conversation.d.ts +131 -0
  140. package/dist/use-copy.d.ts +74 -0
  141. package/dist/use-download-url.d.ts +84 -0
  142. package/dist/use-flash.d.ts +61 -0
  143. package/dist/use-inbox.d.ts +101 -0
  144. package/dist/use-push-to-talk.d.ts +105 -0
  145. package/dist/use-route-mutation.d.ts +113 -0
  146. package/dist/use-route.d.ts +82 -0
  147. package/dist/use-run-key.d.ts +133 -0
  148. package/dist/use-session-controls.d.ts +69 -0
  149. package/dist/use-session-id.d.ts +57 -0
  150. package/dist/use-tap-to-talk.d.ts +138 -0
  151. package/dist/use-user-transcript-uyHhzy4d.js +73 -0
  152. package/dist/use-user-transcript.d.ts +75 -0
  153. package/dist/use-workflow-form.d.ts +285 -0
  154. package/dist/use-workflow-progress.d.ts +137 -0
  155. package/dist/use-workflow-run-DdhWmbx-.js +697 -0
  156. package/dist/use-workflow-run.d.ts +100 -0
  157. package/dist/use-workflow-runs.d.ts +71 -0
  158. package/dist/use-workflow-stream.d.ts +154 -0
  159. package/dist/use-workflows.d.ts +77 -0
  160. package/dist/workflow-client.d.ts +136 -0
  161. package/dist/workflow-def-types.d.ts +31 -0
  162. package/dist/workflow-events.d.ts +39 -0
  163. package/dist/workflow-field-kind.d.ts +95 -0
  164. package/dist/workflow-status-labels.d.ts +35 -0
  165. package/dist/worklets/_module-url.d.ts +10 -0
  166. package/dist/worklets/_playback-bench-harness.d.ts +181 -0
  167. package/dist/worklets/_playback-bench-host.d.ts +63 -0
  168. package/dist/worklets/_playback-bench-page.d.ts +65 -0
  169. package/dist/worklets/_tts-trace-harness.d.ts +142 -0
  170. package/dist/worklets/_worklet-test-utils.d.ts +27 -0
  171. package/dist/worklets/capture-processor.d.ts +4 -2
  172. package/dist/worklets/capture-processor.js +73 -33
  173. package/dist/worklets/playback-processor.d.ts +4 -2
  174. package/dist/worklets/playback-processor.js +258 -42
  175. package/package.json +59 -32
  176. package/styles.css +152 -30
  177. package/dist/_jsdom-setup.d.ts +0 -0
  178. package/dist/audio.d.ts +0 -45
  179. package/dist/audio.js +0 -121
  180. package/dist/client-context.d.ts +0 -33
  181. package/dist/client-context.js +0 -15
  182. package/dist/client-handler.d.ts +0 -43
  183. package/dist/components/app.d.ts +0 -23
  184. package/dist/components/app.js +0 -41
  185. package/dist/components/error-banner.d.ts +0 -21
  186. package/dist/components/error-banner.js +0 -27
  187. package/dist/components/message-bubble.d.ts +0 -23
  188. package/dist/components/message-bubble.js +0 -35
  189. package/dist/components/state-indicator.d.ts +0 -29
  190. package/dist/components/state-indicator.js +0 -37
  191. package/dist/components/thinking-indicator.d.ts +0 -16
  192. package/dist/components/thinking-indicator.js +0 -34
  193. package/dist/components/tool-icons.d.ts +0 -17
  194. package/dist/components/tool-icons.js +0 -128
  195. package/dist/components/transcript.d.ts +0 -25
  196. package/dist/components/transcript.js +0 -35
  197. package/dist/session-CWB7vmqz.js +0 -429
  198. package/dist/session.d.ts +0 -69
  199. package/dist/session.js +0 -2
  200. package/dist/signals.d.ts +0 -120
  201. package/dist/signals.js +0 -161
  202. package/dist/tsdown.config.d.ts +0 -2
  203. package/dist/types.test-d.d.ts +0 -7
@@ -0,0 +1,103 @@
1
+ /**
2
+ * A string a page remembers in this browser — a setting, a phone number, a
3
+ * linked device — readable outside React (a `mountClient({ phone })` getter
4
+ * asked on every connect) and reactive inside it.
5
+ *
6
+ * Built on `_web-storage.ts`, so it inherits that module's rule: every access
7
+ * is guarded, and a browser that refuses storage (private mode, a blocked
8
+ * iframe) degrades to a value held in memory for the tab — the page still
9
+ * works, it just forgets on reload. On top of that:
10
+ *
11
+ * - **Every holder of a key sees a write** — two components, a component and a
12
+ * getter, and other TABS (the `storage` event), so a setting changed in one
13
+ * place is not stale in another.
14
+ * - **An empty value is no value**: writing `""` REMOVES the entry, so the next
15
+ * read is the `initial` again rather than a stored blank.
16
+ *
17
+ * @module
18
+ */
19
+ /**
20
+ * Options for {@link createStoredValue}.
21
+ *
22
+ * @public
23
+ */
24
+ export type StoredValueOptions = {
25
+ /** What a read answers while nothing is stored. Default `""`. */
26
+ initial?: string | undefined;
27
+ /** `"local"` (the default) outlives the tab; `"session"` does not. */
28
+ storage?: "local" | "session" | undefined;
29
+ };
30
+ /**
31
+ * One remembered string — see {@link createStoredValue}.
32
+ *
33
+ * @public
34
+ */
35
+ export type StoredValue = {
36
+ /** The storage key, as given. */
37
+ readonly key: string;
38
+ /** The value now: what is stored, else the `initial`. */
39
+ get(): string;
40
+ /** Remember `value`; `""` or `undefined` forgets it. Every holder of the key is told. */
41
+ set(value: string | undefined): void;
42
+ /** Be told when the value changes — here, in another holder, or in another tab. */
43
+ subscribe(listener: () => void): () => void;
44
+ };
45
+ /**
46
+ * A string remembered in this browser under `key` — see this module's doc.
47
+ *
48
+ * Create it once at module scope and share it: `get()` in a getter outside
49
+ * React, {@link useStoredValue} in a component.
50
+ *
51
+ * The key is used as given, so choose one that is yours (`"my-app:phone"`);
52
+ * two agents on one origin share a key they both name.
53
+ *
54
+ * @example A phone number for the session, edited in a settings field
55
+ * ```tsx
56
+ * import { createStoredValue, mountClient, phoneE164, useStoredValue } from "@alexkroman1/aai-ui";
57
+ *
58
+ * const phone = createStoredValue("my-speaker:phone");
59
+ *
60
+ * function PhoneField() {
61
+ * const [value, setValue] = useStoredValue(phone);
62
+ * return <input defaultValue={value} onBlur={(e) => setValue(e.currentTarget.value.trim())} />;
63
+ * }
64
+ *
65
+ * mountClient({ phone: () => phoneE164(phone.get()), component: PhoneField });
66
+ * ```
67
+ *
68
+ * @param key - The storage key.
69
+ * @param options - `initial` and which storage; see {@link StoredValueOptions}.
70
+ * @returns The value's handle; see {@link StoredValue}.
71
+ *
72
+ * @public
73
+ */
74
+ export declare function createStoredValue(key: string, options?: StoredValueOptions): StoredValue;
75
+ /**
76
+ * A remembered string as React state: the value, and a setter that writes it
77
+ * through to storage — see this module's doc.
78
+ *
79
+ * Pass the key (and an `initial`) for a value one component owns, or a
80
+ * {@link StoredValue} shared with code outside React.
81
+ *
82
+ * @example
83
+ * ```tsx
84
+ * import { useStoredValue } from "@alexkroman1/aai-ui";
85
+ *
86
+ * function Units() {
87
+ * const [units, setUnits] = useStoredValue("my-app:units", "metric");
88
+ * return (
89
+ * <button type="button" onClick={() => setUnits(units === "metric" ? "imperial" : "metric")}>
90
+ * {units}
91
+ * </button>
92
+ * );
93
+ * }
94
+ * ```
95
+ *
96
+ * @param source - A storage key, or a {@link StoredValue}.
97
+ * @param initial - With a key: what to answer while nothing is stored. Default `""`.
98
+ * Ignored with a `StoredValue`, which carries its own.
99
+ * @returns `[value, setValue]`; `setValue("")` forgets it.
100
+ *
101
+ * @public
102
+ */
103
+ export declare function useStoredValue(source: StoredValue | string, initial?: string): [string, (value: string | undefined) => void];
@@ -0,0 +1,204 @@
1
+ import { a as tryParseJSON, i as truncate } from "./_utils-Mi-SCFA1.js";
2
+ import { useTheme } from "./context.js";
3
+ import { a as inkTint } from "./_colors-CpZO-88A.js";
4
+ import { t as Eyebrow } from "./eyebrow-UfmSz9yy.js";
5
+ import { n as useToolConfig } from "./tool-config-context-Es4YUzV2.js";
6
+ import { memo, useMemo, useState } from "react";
7
+ import clsx from "clsx";
8
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
9
+ //#region src/components/tool-call-row.tsx
10
+ /** @jsxImportSource react */
11
+ const VARIANT_CLASSES = {
12
+ default: {
13
+ button: "gap-2.5 px-3.5 py-2.5",
14
+ title: "text-[13px]",
15
+ chevron: "text-[10px]"
16
+ },
17
+ compact: {
18
+ button: "gap-2 px-3 py-2",
19
+ title: "text-[11px]",
20
+ chevron: "text-[9px]"
21
+ }
22
+ };
23
+ /**
24
+ * The design system's console row for one tool invocation: a small outlined
25
+ * "TOOL" chip (or a custom `icon`), the tool title in mono, a truncated
26
+ * detail preview, and a rotating chevron that expands to the panel content.
27
+ *
28
+ * Purely presentational — callers own the mapping from their tool-call data
29
+ * to `title`/`detail`/`pending` and the expanded panel. The deployed agent
30
+ * UI's message list renders it via its tool-call block, and the studio's
31
+ * chat transcript renders it with `variant="compact"`, so the two surfaces
32
+ * read as one component.
33
+ *
34
+ * Colors come from the nearest theme context (see {@link useTheme}); without
35
+ * a provider the default AssemblyAI theme applies.
36
+ *
37
+ * @example
38
+ * ```tsx
39
+ * import { ToolCallRow, useSession } from "@alexkroman1/aai-ui";
40
+ *
41
+ * // A custom chrome's tool log, from the snapshot's own `toolCalls`.
42
+ * function ToolLog() {
43
+ * const { toolCalls } = useSession();
44
+ * return (
45
+ * <div>
46
+ * {toolCalls.map((call) => (
47
+ * <ToolCallRow
48
+ * key={call.callId}
49
+ * title={call.name}
50
+ * detail={call.result}
51
+ * pending={call.status === "pending"}
52
+ * />
53
+ * ))}
54
+ * </div>
55
+ * );
56
+ * }
57
+ * ```
58
+ *
59
+ * @param props - See {@link ToolCallRowProps}.
60
+ *
61
+ * @public
62
+ */
63
+ function ToolCallRow({ title, detail, pending = false, icon, variant = "default", className, children }) {
64
+ const [isOpen, setIsOpen] = useState(false);
65
+ const theme = useTheme();
66
+ const sizes = VARIANT_CLASSES[variant];
67
+ const canExpand = children != null;
68
+ const faint = inkTint(theme.text, theme.bg, 65);
69
+ return /* @__PURE__ */ jsxs("div", {
70
+ className: clsx("flex flex-col rounded-md border overflow-hidden", className),
71
+ style: {
72
+ borderColor: theme.border,
73
+ background: theme.bg
74
+ },
75
+ children: [/* @__PURE__ */ jsxs("button", {
76
+ type: "button",
77
+ "aria-expanded": canExpand ? isOpen : void 0,
78
+ disabled: !canExpand,
79
+ className: clsx("flex items-center select-none text-left w-full appearance-none border-none bg-transparent", sizes.button, canExpand && "cursor-pointer"),
80
+ onClick: () => {
81
+ if (canExpand) setIsOpen(!isOpen);
82
+ },
83
+ children: [
84
+ icon ? /* @__PURE__ */ jsx("span", {
85
+ className: "w-4 h-4 shrink-0 text-center leading-4",
86
+ children: icon
87
+ }) : /* @__PURE__ */ jsx(Eyebrow, {
88
+ className: "shrink-0",
89
+ style: { color: faint },
90
+ children: "Tool"
91
+ }),
92
+ /* @__PURE__ */ jsx("span", {
93
+ className: clsx("min-w-0 truncate font-aai-mono font-medium", sizes.title, pending && "tool-shimmer"),
94
+ style: { color: theme.text },
95
+ children: title
96
+ }),
97
+ /* @__PURE__ */ jsx("span", {
98
+ className: clsx("font-aai-mono truncate flex-1 min-w-0", sizes.title),
99
+ style: { color: faint },
100
+ children: detail
101
+ }),
102
+ canExpand && /* @__PURE__ */ jsx("span", {
103
+ className: clsx("shrink-0 transition-transform duration-150", sizes.chevron, isOpen && "rotate-90"),
104
+ style: { color: faint },
105
+ children: "▶"
106
+ })
107
+ ]
108
+ }), isOpen && canExpand && /* @__PURE__ */ jsx("div", {
109
+ className: "border-t max-h-64 overflow-auto",
110
+ style: {
111
+ borderColor: theme.border,
112
+ background: theme.surface
113
+ },
114
+ children
115
+ })]
116
+ });
117
+ }
118
+ //#endregion
119
+ //#region src/components/tool-call-block.tsx
120
+ /** @jsxImportSource react */
121
+ function formatResult(result) {
122
+ const parsed = tryParseJSON(result);
123
+ return parsed === result ? result : JSON.stringify(parsed, null, 2);
124
+ }
125
+ /**
126
+ * The expanded result pane.
127
+ *
128
+ * Its own component so the parse-and-pretty-print happens only when the row is
129
+ * actually OPEN. `ToolCallRow` renders `children` under `isOpen && canExpand`,
130
+ * and a row starts closed — so computing this in the parent meant every row
131
+ * paid `JSON.parse` + `JSON.stringify(_, null, 2)` over its whole result, and
132
+ * then RETAINED the pretty string, for a panel nobody clicked. The burst case
133
+ * is `history.restored`, which mounts up to `MAX_CLIENT_MESSAGES` rows in one
134
+ * commit.
135
+ *
136
+ * Creating this element is free; React only runs the body once it is mounted.
137
+ */
138
+ function ToolCallResult({ result }) {
139
+ const theme = useTheme();
140
+ const formatted = useMemo(() => formatResult(result), [result]);
141
+ if (!formatted) return null;
142
+ return /* @__PURE__ */ jsx("pre", {
143
+ className: "font-aai-mono text-xs px-3.5 py-3 m-0 whitespace-pre-wrap wrap-break-word",
144
+ style: { color: inkTint(theme.text, theme.surface, 75) },
145
+ children: formatted
146
+ });
147
+ }
148
+ /**
149
+ * Renders a tool invocation as the design system's console row (see
150
+ * `ToolCallRow`): a small outlined "TOOL" chip (or the tool's configured
151
+ * icon), the tool name in mono, a truncated args preview, and a rotating
152
+ * chevron that expands the formatted result.
153
+ *
154
+ * Tool display is configured via `ToolConfigContext`. If no config is found
155
+ * for a tool name, the raw tool name is shown as the title.
156
+ *
157
+ * While the tool call is pending a shimmer animation is shown. Once
158
+ * complete, clicking the row expands the formatted JSON result.
159
+ *
160
+ * Memoized: tool-call objects are referentially stable across session
161
+ * snapshots and rows are keyed on the stable `callId`, so a list update only
162
+ * re-renders the rows whose tool call actually changed.
163
+ *
164
+ * @param toolCall - The tool call to render (see {@link ToolCallInfo}).
165
+ * @param className - Additional CSS class names.
166
+ *
167
+ * @internal Not exported from the package — rendered by `MessageList`.
168
+ */
169
+ const ToolCallBlock = memo(function ToolCallBlock({ toolCall, className }) {
170
+ const theme = useTheme();
171
+ const toolConfig = useToolConfig();
172
+ const config = Object.hasOwn(toolConfig, toolCall.name) ? toolConfig[toolCall.name] : void 0;
173
+ const isPending = toolCall.status === "pending";
174
+ const title = config?.label || toolCall.name;
175
+ const icon = config?.icon;
176
+ const canExpand = !isPending && Boolean(toolCall.result);
177
+ const subtitle = useMemo(() => {
178
+ const args = toolCall.args;
179
+ if (toolCall.name === "run_code" && args.code) return truncate(String(args.code).split("\n")[0] ?? "");
180
+ for (const key of [
181
+ "query",
182
+ "url",
183
+ "question"
184
+ ]) if (args[key]) return String(args[key]);
185
+ return truncate(JSON.stringify(args));
186
+ }, [toolCall.name, toolCall.args]);
187
+ return /* @__PURE__ */ jsx(ToolCallRow, {
188
+ title,
189
+ detail: subtitle,
190
+ pending: isPending,
191
+ icon,
192
+ className,
193
+ children: canExpand ? /* @__PURE__ */ jsxs(Fragment$1, { children: [toolCall.name === "run_code" && Boolean(toolCall.args.code) && /* @__PURE__ */ jsx("pre", {
194
+ className: "font-aai-mono text-xs px-3.5 py-3 m-0 whitespace-pre-wrap wrap-break-word border-b",
195
+ style: {
196
+ color: theme.text,
197
+ borderColor: theme.border
198
+ },
199
+ children: String(toolCall.args.code)
200
+ }), toolCall.result && /* @__PURE__ */ jsx(ToolCallResult, { result: toolCall.result })] }) : void 0
201
+ });
202
+ });
203
+ //#endregion
204
+ export { ToolCallRow as n, ToolCallBlock as t };
@@ -0,0 +1,19 @@
1
+ import { createContext, useContext } from "react";
2
+ //#region src/components/tool-config-context.ts
3
+ /**
4
+ * Context for tool display configuration. Installed by `mountClient()` from
5
+ * `ClientConfig.tools`; the built-in components read it via `useToolConfig`.
6
+ *
7
+ * @internal
8
+ */
9
+ const ToolConfigContext = createContext({});
10
+ /**
11
+ * Read tool display configuration from the nearest `ToolConfigContext.Provider`.
12
+ *
13
+ * @internal
14
+ */
15
+ function useToolConfig() {
16
+ return useContext(ToolConfigContext);
17
+ }
18
+ //#endregion
19
+ export { useToolConfig as n, ToolConfigContext as t };
package/dist/types.d.ts CHANGED
@@ -1,8 +1,60 @@
1
+ import type { DefaultToolResult } from "@alexkroman1/aai";
1
2
  import type { SessionErrorCode } from "@alexkroman1/aai/protocol";
2
- /** Microphone buffer duration in seconds before sending to the server. */
3
- export declare const MIC_BUFFER_SECONDS = 0.1;
3
+ export { CAPTURE_STOP_ACK_TIMEOUT_MS, CLIENT_AUDIO_LEAD_MS, HEARD_AUDIO_LAG_MS, MIC_BUFFER_SECONDS, MIC_SEND_MAX_BUFFERED_BYTES, MIC_SILENCE_PROBE_MS, PACER_BURST_MS, PIPELINE_PLAYBACK_GRACE_MS, PLAYBACK_BUFFER_SECONDS, PLAYBACK_CONCEAL_FADE_MS, PLAYBACK_CONCEAL_FLOOR, PLAYBACK_DONE_MAX_WAIT_MS, PLAYBACK_DONE_POLL_MS, PLAYBACK_FILL_MS, PLAYBACK_PROGRESS_INTERVAL_MS, } from "@alexkroman1/aai/internal";
4
4
  /**
5
- * Current state of the voice agent session.
5
+ * `getUserMedia` audio constraints for every capture path in this package.
6
+ *
7
+ * Defined once because four copies of this object drifted apart trivially, and
8
+ * the flags are not cosmetic — each one rewrites the signal before STT (and
9
+ * before the sync path's energy VAD) ever sees it:
10
+ *
11
+ * - **`autoGainControl: false`** — AGC continuously retargets level, which
12
+ * means riding the noise floor up through silence. An energy VAD calibrated
13
+ * against a moving floor is calibrated against nothing.
14
+ * - **`noiseSuppression: false`** / **`voiceIsolation: false`** — both discard
15
+ * signal to make speech sound cleaner to a human, and both can gate a quiet
16
+ * room to *exact* zeros, which is also what a dead microphone looks like
17
+ * (see `MIC_SILENCE_PROBE_MS`).
18
+ * - **`echoCancellation: true`** — this one stays on. The mic is open while
19
+ * the agent speaks (barge-in needs it), so without AEC the agent hears
20
+ * itself and interrupts its own reply.
21
+ *
22
+ * Cast because `voiceIsolation` is newer than TypeScript's DOM lib.
23
+ *
24
+ * On `@alexkroman1/aai-ui/internal` rather than the root barrel, for the same
25
+ * reason as the audio budgets above: it is a framework decision with no
26
+ * `mountClient()` field to set, and the root is the authoring surface. A custom
27
+ * chrome that bypasses `mountClient()` and opens its own microphone reaches it there
28
+ * alongside the providers it also needs.
29
+ */
30
+ export declare const VOICE_CAPTURE_CONSTRAINTS: MediaTrackConstraints;
31
+ /**
32
+ * Current state of the voice agent session — the `state` field of
33
+ * {@link SessionSnapshot}, and what a chrome paints its status indicator from.
34
+ *
35
+ * @remarks
36
+ * The seven members, in the order a call passes through them:
37
+ *
38
+ * - `"disconnected"` — no socket. The state before the first `start()` and
39
+ * after `disconnect()` / `end()`.
40
+ * - `"connecting"` — dialling. Covers the broker lookup and every automatic
41
+ * reconnect attempt, so a session flickers back through it mid-call.
42
+ * - `"ready"` — the socket is open and the handshake is done, but no turn has
43
+ * happened yet. **The default chrome paints this with the same live
44
+ * indicator as `"listening"`**, which is deliberate — to a caller they are
45
+ * the same "the agent is there" — but they are not the same thing, and a
46
+ * session can wedge here (see `session/handshake.ts`).
47
+ * - `"listening"` — the microphone is open and the agent is waiting for the
48
+ * caller. Check {@link SessionSnapshot.recording} for whether the mic is
49
+ * actually live.
50
+ * - `"thinking"` — the caller's turn is committed and the agent is working:
51
+ * the LLM step, and any tool calls under it.
52
+ * - `"speaking"` — the agent's reply is playing. A caller may still barge in;
53
+ * the mic stays open throughout.
54
+ * - `"error"` — the session reported a failure. See
55
+ * {@link SessionSnapshot.error} for what it was. A FATAL error latches here
56
+ * until the next completed handshake, so a later frame cannot quietly paint
57
+ * over the banner explaining a dead call.
6
58
  *
7
59
  * @public
8
60
  */
@@ -10,9 +62,20 @@ export type AgentState = "disconnected" | "connecting" | "ready" | "listening" |
10
62
  /**
11
63
  * A chat message exchanged between user and assistant.
12
64
  *
65
+ * `role` is `"user" | "assistant"` only — unlike the SDK's `Message`, there
66
+ * is no `"tool"` role here. Tool activity never arrives as messages: it is
67
+ * surfaced via `SessionSnapshot.toolCalls` (or `useEvent` for `ctx.send`
68
+ * events).
69
+ *
13
70
  * @public
14
71
  */
15
72
  export type ChatMessage = {
73
+ /**
74
+ * Monotonically increasing, session-unique message id assigned at append
75
+ * time. Stable across snapshot updates and window slides — use it as a
76
+ * render key.
77
+ */
78
+ id: number;
16
79
  /** The sender of the message. */
17
80
  role: "user" | "assistant";
18
81
  /** The text content of the message. */
@@ -24,14 +87,43 @@ export type ChatMessage = {
24
87
  * @public
25
88
  */
26
89
  export type ToolCallInfo = {
27
- toolCallId: string;
28
- toolName: string;
29
- args: Record<string, unknown>;
90
+ callId: string;
91
+ name: string;
92
+ /**
93
+ * The tool's arguments, as the model sent them.
94
+ *
95
+ * Values are {@link DefaultToolResult} — `any` — for the same reason a tool
96
+ * *result* is: the shape is the author's own Zod schema, which the framework
97
+ * cannot see from here. As `Record<string, unknown>` the ordinary
98
+ * `toolCall.args.url` was a compile error in a client that runs correctly,
99
+ * and the escape hatch agents reached for next (`args as FetchJsonArgs`) is
100
+ * itself an error — TypeScript rejects the cast as insufficiently
101
+ * overlapping. That pair cost two build rounds in one run.
102
+ *
103
+ * Annotate at the read site for real checking:
104
+ * `const { url } = toolCall.args as { url: string }` is still available, and
105
+ * now actually compiles.
106
+ */
107
+ args: Record<string, DefaultToolResult>;
30
108
  status: "pending" | "done";
31
109
  result?: string | undefined;
32
- /** Index in the messages array where this tool call should appear. */
33
- afterMessageIndex: number;
110
+ /**
111
+ * Monotonically increasing, session-unique insertion sequence number.
112
+ * Tool calls in a snapshot are always sorted ascending by `seq`.
113
+ */
114
+ seq: number;
115
+ /**
116
+ * `id` of the last {@link ChatMessage} present when this tool call was
117
+ * inserted (`-1` when there were none). The tool call renders immediately
118
+ * after that message; if the anchor message has slid out of the retained
119
+ * window, the tool call renders before all messages.
120
+ */
121
+ afterMessageId: number;
34
122
  };
123
+ /**
124
+ * Re-exported from `@alexkroman1/aai/protocol` (the canonical definition)
125
+ * so client code needs only this package.
126
+ */
35
127
  export type { SessionErrorCode } from "@alexkroman1/aai/protocol";
36
128
  /**
37
129
  * Error reported by the voice session.
@@ -43,50 +135,179 @@ export type SessionError = {
43
135
  readonly code: SessionErrorCode;
44
136
  /** A human-readable description of the error. */
45
137
  readonly message: string;
138
+ /**
139
+ * Whether the session is OVER.
140
+ *
141
+ * `false` means surface the message and keep the session interactive — a
142
+ * turn-level failure over a server that kept running. `true` means the call
143
+ * is dead and the microphone has been released.
144
+ *
145
+ * Required rather than optional, because the wire always carries it
146
+ * (`error.reported` declares `fatal: z.boolean()`) and a client that cannot
147
+ * tell the two apart has to guess which banner to render. It was dropped one
148
+ * line before reaching here for long enough that this type's own doc, and
149
+ * the reference page generated from it, described a field that did not
150
+ * exist.
151
+ */
152
+ readonly fatal: boolean;
46
153
  };
47
154
  /**
48
- * Minimal reactive container. Compatible with `@preact/signals` `Signal<T>`.
49
- *
50
- * @public
51
- */
52
- export type Reactive<T> = {
53
- value: T;
54
- };
55
- /**
56
- * Options for creating a voice session.
155
+ * Options for creating a voice session — the shared field set accepted by
156
+ * both `mountClient()` and `createBrowserSession`. The one difference: `mountClient()`
157
+ * defaults `platformUrl` from `location.href`, while `createBrowserSession`
158
+ * requires it.
57
159
  *
58
160
  * @public
59
161
  */
60
162
  export type VoiceSessionOptions = {
61
163
  /** Base URL of the AAI platform server. */
62
164
  platformUrl: string;
63
- /**
64
- * Factory for creating reactive state containers.
65
- * Defaults to a plain mutable wrapper. Pass `signal` from
66
- * `@preact/signals` for automatic Preact component re-rendering.
67
- */
68
- reactiveFactory?: <T>(initial: T) => Reactive<T>;
69
- /**
70
- * Function to batch multiple reactive updates.
71
- * Defaults to calling the function directly. Pass `batch` from
72
- * `@preact/signals` for optimized batched updates.
73
- */
74
- batch?: (fn: () => void) => void;
75
165
  /**
76
166
  * Called when the server sends a session ID in the config message.
77
167
  * Use this to store the ID (e.g. in localStorage) for reconnection
78
168
  * via `resumeSessionId`.
169
+ *
170
+ * Treat session IDs as sensitive: whoever holds one can resume the
171
+ * session and read its replayed history. They travel as a WebSocket
172
+ * query parameter (browsers cannot set WS headers), so they may appear
173
+ * in proxy and server access logs — don't put them in shared URLs.
79
174
  */
80
175
  onSessionId?: ((sessionId: string) => void) | undefined;
81
176
  /**
82
- * Session ID from a previous connection. When set, the server will
83
- * attempt to restore persisted session state (if the agent has
84
- * `persistence` enabled).
177
+ * Session ID from a previous connection. When set, the server resumes
178
+ * that session if its per-session state is still within the resume grace
179
+ * window (`SESSION_RESUME_GRACE_MS`), replaying history into the new
180
+ * connection. Sensitive — see {@link onSessionId}.
85
181
  */
86
182
  resumeSessionId?: string | undefined;
87
183
  /**
88
- * WebSocket constructor override. Defaults to the native `WebSocket`.
89
- * Primarily useful for testing with a mock WebSocket.
184
+ * Where the client is — e.g. a street address or `"Portland, Oregon"` — sent
185
+ * as `?location=` on every connection attempt (the first, a resume and each
186
+ * reconnect, brokered or not). Location-aware builtins read it: `open_meteo`
187
+ * falls back to it when the model names no place, and `google_places` biases
188
+ * its searches toward it.
189
+ *
190
+ * A string, or a getter asked on EVERY attempt — so a UI whose location can
191
+ * change (a settings field) passes `() => current` and the next reconnect
192
+ * carries the new value without a remount. An empty or `undefined` answer
193
+ * sends none. The server strips control characters and ignores a value
194
+ * longer than 200 characters.
195
+ *
196
+ * Treat it as personal data: like the session id it travels as a query
197
+ * parameter (browsers cannot set WebSocket headers), so it may appear in
198
+ * proxy and access logs.
199
+ */
200
+ location?: string | (() => string | undefined) | undefined;
201
+ /**
202
+ * The phone number of whoever owns this client — sent as `?phone=` on every
203
+ * connection attempt, with the same rules as {@link location}: a string or a
204
+ * getter asked per attempt, trimmed, an empty answer sends none. A tool reads
205
+ * it with `sessionClientPhone(ctx)`, e.g. to text the caller a link.
206
+ *
207
+ * Give it in E.164 form, with the `+` and country code (`"+1 503 555 0123"`):
208
+ * the server strips spaces, dashes, dots and parentheses and then needs a `+`
209
+ * and 8–15 digits. A bare `"5035550123"` is NOT assumed to be North American
210
+ * — it is dropped (the server logs that it was, never the value).
211
+ *
212
+ * The server takes it on the client's word. Personal data: it travels as a
213
+ * query parameter, so it may appear in proxy and access logs.
214
+ */
215
+ phone?: string | (() => string | undefined) | undefined;
216
+ /**
217
+ * The name of THIS client — a device id — sent as `?client=` on every
218
+ * connection attempt, with the same rules as {@link location}: a string or a
219
+ * getter asked per attempt, trimmed, an empty answer sends none.
220
+ *
221
+ * **`"auto"`** has the SDK mint and keep one: this browser's
222
+ * `browserClientId()` — `browser-<32 hex>`, stored in `localStorage` per
223
+ * agent URL (for this tab only where storage is unavailable). It is the id
224
+ * `useInbox()` holds the inbox under too, with a holder id per TAB so two tabs
225
+ * of one browser do not replace each other's inbox socket. Read it back with
226
+ * `useClientId()` or `session.identity.clientId()`. (So `"auto"` itself can
227
+ * never be a client id.)
228
+ *
229
+ * It is the id a tool reads with `sessionClientId(ctx)` and the one a client
230
+ * holds its `WS /inbox?client=` socket open under, so a tool can hand it to a
231
+ * workflow run and the run's `stepNotifyClient` reaches this client AFTER the
232
+ * voice session has ended (a reminder, a finished research job). Send the
233
+ * same id the inbox socket uses, or the run delivers to a client that is not
234
+ * listening.
235
+ *
236
+ * Letters, digits, `-` and `_`, at most 64 characters: the server checks it
237
+ * against the same pattern the inbox uses and ignores one that does not
238
+ * match — the session still opens, it just has no client id.
239
+ *
240
+ * It is also the key of this client's DURABLE conversation: every connection
241
+ * that sends it is seeded with that client's earlier sessions. The server
242
+ * takes it on the client's word and authenticates nothing, so on a server
243
+ * reachable from a network (a LAN-listening dev server, a self-hosted one)
244
+ * the id is the ONLY credential for that history — anyone who can reach the
245
+ * server and knows the id reads what was said. Use an unguessable id.
246
+ */
247
+ client?: string | (() => string | undefined) | undefined;
248
+ /**
249
+ * Open the microphone when the session connects, rather than once the
250
+ * server has configured it, and send what the caller said in between ahead
251
+ * of the live audio — so an opener spoken while the agent is still joining
252
+ * (a handshake, or a sandbox boot on the platform) reaches it instead of
253
+ * being lost. The latest 10 seconds are kept.
254
+ *
255
+ * Default `true`. `false` asks for the microphone only after the server's
256
+ * `config` frame, as before; a UI that wants the permission prompt to
257
+ * follow its own greeting might.
258
+ */
259
+ preConnectAudio?: boolean | undefined;
260
+ /**
261
+ * The session ticket this client presents — for a server that requires one
262
+ * (`AAI_SESSION_SECRET`, or `createSessionAuth()` on
263
+ * `@alexkroman1/aai-runtime/auth`).
264
+ *
265
+ * A string, or a getter asked on EVERY connection attempt — the first, each
266
+ * reconnect and each resume — so a short-lived ticket (60 s by default) is
267
+ * fresh each time. Fetch it from your own backend, which checks its own login
268
+ * and mints with `createSessionToken()`; the secret must never reach the
269
+ * browser. The getter is told the session the attempt RESUMES (`undefined`
270
+ * for a new one), so a backend can bind a resume ticket to it
271
+ * (`createSessionToken({ sessionId })`) — what a resume needs after the
272
+ * server restarted.
273
+ *
274
+ * It travels in `Sec-WebSocket-Protocol` as `aai.auth.<ticket>` beside the
275
+ * plain `aai.session` protocol, never in the URL, so it stays out of access
276
+ * logs (a value that is not a valid protocol token falls back to `?token=`).
277
+ * An empty or `undefined` answer sends none, and so does a getter that throws
278
+ * or rejects: the attempt still dials, and a server that requires a ticket
279
+ * refuses it with a reason. Give a fetch inside the getter a deadline — the
280
+ * attempt waits for it. With an injected {@link WebSocket} the getter must
281
+ * answer synchronously.
282
+ *
283
+ * When omitted, a ticket the server's `client-config` issued is used — which
284
+ * `aai dev` does for its own client when `AAI_SESSION_SECRET` is set.
285
+ *
286
+ * @example
287
+ * ```ts
288
+ * import { mountClient } from "@alexkroman1/aai-ui";
289
+ *
290
+ * mountClient({
291
+ * token: async ({ sessionId }) => {
292
+ * const res = await fetch("/my-backend/session-ticket", {
293
+ * method: "POST",
294
+ * body: JSON.stringify({ sessionId }),
295
+ * signal: AbortSignal.timeout(5000),
296
+ * });
297
+ * return (await res.json()).token;
298
+ * },
299
+ * });
300
+ * ```
301
+ */
302
+ token?: string | ((attempt: {
303
+ readonly sessionId: string | undefined;
304
+ }) => string | undefined | Promise<string | undefined>) | undefined;
305
+ /**
306
+ * WebSocket constructor override. Primarily useful for testing with a mock
307
+ * WebSocket. When omitted, the session uses a reconnecting WebSocket
308
+ * (partysocket) that retries with exponential backoff after an unexpected
309
+ * close and resumes the session; an injected constructor is used as-is and
310
+ * never reconnects on its own.
90
311
  */
91
312
  WebSocket?: WebSocketConstructor | undefined;
92
313
  };
@@ -99,3 +320,20 @@ export type WebSocketConstructor = {
99
320
  new (url: string | URL, protocols?: string | string[]): WebSocket;
100
321
  readonly OPEN: number;
101
322
  };
323
+ /**
324
+ * Theme color overrides for the AAI UI components.
325
+ *
326
+ * @public
327
+ */
328
+ export type ClientTheme = {
329
+ /** Background color, also painted on `html`/`body`. Default: `#FBF8F2`. */
330
+ bg?: string;
331
+ /** Primary accent color. Default: `#3F2BC1`. */
332
+ primary?: string;
333
+ /** Main text color. Default: `#1B1A18`. */
334
+ text?: string;
335
+ /** Surface/card color. Default: `#FFFFFF`. */
336
+ surface?: string;
337
+ /** Border color. Default: `#DCD7CC`. */
338
+ border?: string;
339
+ };