@alexkroman1/aai-ui 0.12.3 → 0.13.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 (201) hide show
  1. package/README.md +257 -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 +38 -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-cGFGID-2.js +1 -0
  78. package/dist/default-client/assets/capture-processor-BUABoo3_.js +90 -0
  79. package/dist/default-client/assets/index-8ihb1Urm.css +2 -0
  80. package/dist/default-client/assets/index-BQURVARl.js +126 -0
  81. package/dist/default-client/assets/playback-processor-ABOa11j0.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.d.ts +23 -0
  85. package/dist/define-client.d.ts +162 -26
  86. package/dist/define-client.js +201 -55
  87. package/dist/eyebrow-UfmSz9yy.js +27 -0
  88. package/dist/hooks-DN4gHYsj.js +182 -0
  89. package/dist/hooks.d.ts +172 -0
  90. package/dist/hooks.js +3 -0
  91. package/dist/hooks.test-d.d.ts +14 -0
  92. package/dist/inbox-protocol.d.ts +128 -0
  93. package/dist/inbox.d.ts +132 -0
  94. package/dist/index.d.ts +126 -29
  95. package/dist/index.js +5500 -15
  96. package/dist/internal.d.ts +43 -0
  97. package/dist/internal.js +8 -0
  98. package/dist/linked-client.d.ts +70 -0
  99. package/dist/message-list-DHZcLhRA.js +682 -0
  100. package/dist/notice-player.d.ts +70 -0
  101. package/dist/page.d.ts +114 -0
  102. package/dist/phone.d.ts +50 -0
  103. package/dist/route-fetch.d.ts +79 -0
  104. package/dist/session/audio-effects.d.ts +31 -0
  105. package/dist/session/audio-setup.d.ts +57 -0
  106. package/dist/session/audio-state.d.ts +157 -0
  107. package/dist/session/browser-session.d.ts +30 -0
  108. package/dist/session/close.d.ts +26 -0
  109. package/dist/session/dial.d.ts +65 -0
  110. package/dist/session/handshake.d.ts +60 -0
  111. package/dist/session/index.d.ts +17 -0
  112. package/dist/session/index.js +2 -0
  113. package/dist/session/messages.d.ts +63 -0
  114. package/dist/session/mic.d.ts +64 -0
  115. package/dist/session/preconnect.d.ts +48 -0
  116. package/dist/session/reconnect.d.ts +25 -0
  117. package/dist/session/resume-store.d.ts +57 -0
  118. package/dist/session/state.d.ts +146 -0
  119. package/dist/session/ticket.d.ts +44 -0
  120. package/dist/session/types.d.ts +476 -0
  121. package/dist/session/url.d.ts +17 -0
  122. package/dist/session/user-turn.d.ts +55 -0
  123. package/dist/session-CE0Ipsb3.js +2129 -0
  124. package/dist/stored-value.d.ts +103 -0
  125. package/dist/tool-call-block-uXtMOUWN.js +204 -0
  126. package/dist/tool-config-context-Es4YUzV2.js +19 -0
  127. package/dist/types.d.ts +272 -34
  128. package/dist/types.js +35 -4
  129. package/dist/upload/files.d.ts +83 -0
  130. package/dist/upload/index.d.ts +15 -0
  131. package/dist/upload/pause.d.ts +31 -0
  132. package/dist/upload/recall.d.ts +67 -0
  133. package/dist/upload/report.d.ts +26 -0
  134. package/dist/upload/session.d.ts +99 -0
  135. package/dist/url-chips-oyy_Eiqh.js +239 -0
  136. package/dist/use-client-runs.d.ts +87 -0
  137. package/dist/use-conversation-log.d.ts +86 -0
  138. package/dist/use-conversation.d.ts +131 -0
  139. package/dist/use-copy.d.ts +74 -0
  140. package/dist/use-download-url.d.ts +84 -0
  141. package/dist/use-flash.d.ts +61 -0
  142. package/dist/use-inbox.d.ts +101 -0
  143. package/dist/use-push-to-talk.d.ts +105 -0
  144. package/dist/use-route-mutation.d.ts +113 -0
  145. package/dist/use-route.d.ts +82 -0
  146. package/dist/use-run-key.d.ts +133 -0
  147. package/dist/use-session-controls.d.ts +69 -0
  148. package/dist/use-session-id.d.ts +57 -0
  149. package/dist/use-tap-to-talk.d.ts +138 -0
  150. package/dist/use-user-transcript-uyHhzy4d.js +73 -0
  151. package/dist/use-user-transcript.d.ts +75 -0
  152. package/dist/use-workflow-form.d.ts +285 -0
  153. package/dist/use-workflow-progress.d.ts +137 -0
  154. package/dist/use-workflow-run-DdhWmbx-.js +697 -0
  155. package/dist/use-workflow-run.d.ts +100 -0
  156. package/dist/use-workflow-runs.d.ts +71 -0
  157. package/dist/use-workflow-stream.d.ts +154 -0
  158. package/dist/use-workflows.d.ts +77 -0
  159. package/dist/workflow-client.d.ts +136 -0
  160. package/dist/workflow-def-types.d.ts +31 -0
  161. package/dist/workflow-events.d.ts +39 -0
  162. package/dist/workflow-field-kind.d.ts +95 -0
  163. package/dist/workflow-status-labels.d.ts +35 -0
  164. package/dist/worklets/_module-url.d.ts +10 -0
  165. package/dist/worklets/_playback-bench-harness.d.ts +181 -0
  166. package/dist/worklets/_playback-bench-host.d.ts +63 -0
  167. package/dist/worklets/_playback-bench-page.d.ts +65 -0
  168. package/dist/worklets/_tts-trace-harness.d.ts +142 -0
  169. package/dist/worklets/_worklet-test-utils.d.ts +27 -0
  170. package/dist/worklets/capture-processor.d.ts +4 -2
  171. package/dist/worklets/capture-processor.js +73 -33
  172. package/dist/worklets/playback-processor.d.ts +4 -2
  173. package/dist/worklets/playback-processor.js +258 -42
  174. package/package.json +57 -32
  175. package/styles.css +152 -30
  176. package/dist/audio.d.ts +0 -45
  177. package/dist/audio.js +0 -121
  178. package/dist/client-context.d.ts +0 -33
  179. package/dist/client-context.js +0 -15
  180. package/dist/client-handler.d.ts +0 -43
  181. package/dist/components/app.d.ts +0 -23
  182. package/dist/components/app.js +0 -41
  183. package/dist/components/error-banner.d.ts +0 -21
  184. package/dist/components/error-banner.js +0 -27
  185. package/dist/components/message-bubble.d.ts +0 -23
  186. package/dist/components/message-bubble.js +0 -35
  187. package/dist/components/state-indicator.d.ts +0 -29
  188. package/dist/components/state-indicator.js +0 -37
  189. package/dist/components/thinking-indicator.d.ts +0 -16
  190. package/dist/components/thinking-indicator.js +0 -34
  191. package/dist/components/tool-icons.d.ts +0 -17
  192. package/dist/components/tool-icons.js +0 -128
  193. package/dist/components/transcript.d.ts +0 -25
  194. package/dist/components/transcript.js +0 -35
  195. package/dist/session-CWB7vmqz.js +0 -429
  196. package/dist/session.d.ts +0 -69
  197. package/dist/session.js +0 -2
  198. package/dist/signals.d.ts +0 -120
  199. package/dist/signals.js +0 -161
  200. package/dist/tsdown.config.d.ts +0 -2
  201. package/dist/types.test-d.d.ts +0 -7
@@ -0,0 +1,396 @@
1
+ import { useSessionError, useSessionStatus, useTheme } from "./context.js";
2
+ import { t as MessageList } from "./message-list-DHZcLhRA.js";
3
+ import { a as inkTint, r as THINKING_COLOR, t as ERROR_COLOR } from "./_colors-CpZO-88A.js";
4
+ import { t as AaiLogo } from "./aai-logo-D8pMVn9_.js";
5
+ import { t as Eyebrow } from "./eyebrow-UfmSz9yy.js";
6
+ import { Controls } from "./components/controls.js";
7
+ import clsx from "clsx";
8
+ import { jsx, jsxs } from "react/jsx-runtime";
9
+ //#region src/agent-state-labels.ts
10
+ /**
11
+ * The default label per {@link AgentState}.
12
+ *
13
+ * Override the ones your page has a better word for and keep the rest:
14
+ *
15
+ * ```ts
16
+ * import type { AgentState } from "@alexkroman1/aai-ui";
17
+ * import { AGENT_STATE_LABELS } from "@alexkroman1/aai-ui";
18
+ *
19
+ * // A dispatch board that shouts, and renames one state.
20
+ * const STATE_LABEL = { ...AGENT_STATE_LABELS, thinking: "Processing" };
21
+ * const shout = (s: AgentState) => STATE_LABEL[s].toUpperCase();
22
+ * ```
23
+ *
24
+ * **Sentence case, deliberately.** A template that wants caps applies its own
25
+ * `.toUpperCase()`, and a template that wants Title Case is already there;
26
+ * shipping the shouted form instead would leave the two chromes that do not
27
+ * shout with a string they have to un-shout, which no case transform does
28
+ * correctly.
29
+ *
30
+ * Two wordings are decisions rather than transliterations of the member name:
31
+ *
32
+ * - `disconnected` is **"Idle"**. It is the state a session is in BEFORE it has
33
+ * ever started as well as after it ends, so it is the first word most callers
34
+ * see; "Disconnected" reads as a fault on a page where nothing has gone
35
+ * wrong yet. Both chromes that mapped this state by hand chose "Idle" too.
36
+ * - `connecting` and `thinking` carry an ellipsis, `listening` and `speaking`
37
+ * do not. The first two are waits with nothing for the caller to do; the
38
+ * other two describe someone actually talking. Same distinction
39
+ * `WORKFLOW_STATUS_LABELS` draws with its one "Working…".
40
+ *
41
+ * @public
42
+ */
43
+ const AGENT_STATE_LABELS = {
44
+ disconnected: "Idle",
45
+ connecting: "Connecting…",
46
+ ready: "Ready",
47
+ listening: "Listening",
48
+ thinking: "Thinking…",
49
+ speaking: "Speaking",
50
+ error: "Error"
51
+ };
52
+ //#endregion
53
+ //#region src/components/session-error-banner.tsx
54
+ /** @jsxImportSource react */
55
+ /**
56
+ * The announced banner for a failed session: the error's message and code, or
57
+ * nothing at all when the session is fine.
58
+ *
59
+ * **This used to be four lines inside `ConsoleShell`, and that is why it is its
60
+ * own component.** The banner was the reason `ConsoleShell` was published —
61
+ * `role="alert"` is the one part of that component a reviewer cannot see is
62
+ * missing, since per the `fatalError` latch in `session/browser-session.ts` the banner is
63
+ * the ONLY remaining signal a session died (the state eyebrow beside it goes
64
+ * back to reading like a live session), and a screen reader is never told an
65
+ * unannounced one appeared. But `ConsoleShell` is a whole FRAME: a centred
66
+ * `max-w-190` column with its own header and footer. Every full-bleed chrome —
67
+ * a two-pane board, a CRT — therefore could not adopt it, rebuilt the banner
68
+ * instead, and the three that did had ALREADY drifted: one rendered
69
+ * `ERROR: {message}` and dropped the code entirely, one `ERROR: {message}
70
+ * ({code})`, one `{message} ({code})`. Splitting the banner out is what lets a
71
+ * chrome take the announced-error decision without taking the layout, and
72
+ * `ConsoleShell` composes this rather than keeping a second copy, so the two
73
+ * cannot drift again.
74
+ *
75
+ * **It reads the session itself.** There is no `error` prop: a banner that
76
+ * takes its text from the caller is a banner a caller can forget to wire, which
77
+ * is exactly the failure above with an extra step. It subscribes narrowly via
78
+ * {@link useSessionError}, so a page that renders it does not re-render with
79
+ * the transcript.
80
+ *
81
+ * **The code is shown, always.** `SessionError.code` is the eight-member wire
82
+ * union — it is what a user pastes into a bug report and the only part of the
83
+ * error that is stable across wordings — and the chrome that dropped it left
84
+ * its readers with a sentence and no way to say which failure it was.
85
+ *
86
+ * Must be rendered inside the providers `client()` installs.
87
+ *
88
+ * @example A full-bleed chrome that wants the banner and not the frame
89
+ * ```tsx
90
+ * import { SessionErrorBanner } from "@alexkroman1/aai-ui";
91
+ *
92
+ * function Board() {
93
+ * return (
94
+ * <div className="grid grid-cols-[1fr_320px] h-screen">
95
+ * <main>…</main>
96
+ * <aside>…</aside>
97
+ * <SessionErrorBanner className="col-span-2" />
98
+ * </div>
99
+ * );
100
+ * }
101
+ * ```
102
+ *
103
+ * @param props - See {@link SessionErrorBannerProps}.
104
+ *
105
+ * @public
106
+ */
107
+ function SessionErrorBanner({ className }) {
108
+ const error = useSessionError();
109
+ if (!error) return null;
110
+ return /* @__PURE__ */ jsxs("div", {
111
+ role: "alert",
112
+ className: clsx("px-3.5 py-2.5 rounded-aai border text-[13px] leading-[130%] shrink-0", className),
113
+ style: {
114
+ borderColor: "rgba(179,38,30,0.35)",
115
+ background: "rgba(179,38,30,0.06)",
116
+ color: ERROR_COLOR
117
+ },
118
+ children: [
119
+ error.message,
120
+ " (",
121
+ error.code,
122
+ ")"
123
+ ]
124
+ });
125
+ }
126
+ //#endregion
127
+ //#region src/components/session-state-dot.tsx
128
+ /** @jsxImportSource react */
129
+ /**
130
+ * One `aai-pulse` cycle per pulsing state, in milliseconds. The two states
131
+ * that pulse are the two in which the agent is DOING something the reader
132
+ * cannot otherwise see — the microphone is open, or a reply is being worked
133
+ * out — and `thinking` beats faster because it is the shorter wait. `speaking`
134
+ * has the audio itself for a signal and stays still.
135
+ */
136
+ const PULSE_MS = {
137
+ listening: 1500,
138
+ thinking: 800
139
+ };
140
+ /**
141
+ * The dot itself: a coloured circle, pulsing or still.
142
+ *
143
+ * Shared by {@link SessionStateDot} and `ConsoleShell`, which computes its
144
+ * colour from the theme rather than from a palette prop and decides for itself
145
+ * when to pulse. Not on the package barrel — the public component is the one
146
+ * that reads the session.
147
+ *
148
+ * The colour is written to `background` AND `color`, so a caller can extend
149
+ * the dot with a `currentColor` utility (`shadow-[0_0_6px_currentColor]` for a
150
+ * glow) without restating the palette.
151
+ *
152
+ * @internal
153
+ */
154
+ function StateDot({ color, pulseMs, className, title }) {
155
+ return /* @__PURE__ */ jsx("span", {
156
+ className: clsx("rounded-full inline-block shrink-0", className),
157
+ style: {
158
+ background: color,
159
+ color,
160
+ animation: pulseMs === null ? "none" : `aai-pulse ${pulseMs}ms ease-in-out infinite`
161
+ },
162
+ title
163
+ });
164
+ }
165
+ /**
166
+ * The live session state as a coloured dot and a word, on its own narrow
167
+ * subscription.
168
+ *
169
+ * Three custom chromes had each written this: a `satisfies Record<AgentState,
170
+ * string>` palette (kept — it is the prop), a `STATE_LABELS` spread over
171
+ * {@link AGENT_STATE_LABELS} (kept — it is `labels`), and then the same
172
+ * fourteen lines of markup around them, including the same two-arm ternary
173
+ * deciding which states pulse and how fast. `agent-state-labels.ts` argued
174
+ * against a dot component on the grounds that it would take the shared part
175
+ * (the words) hostage to the part that is not (the palette); this takes the
176
+ * palette as a prop precisely so it does not. What is shared is the structure
177
+ * — the exhaustive colour lookup, the label fallback, the pulse rule, the
178
+ * `useSessionStatus()` subscription that keeps the rest of the header from
179
+ * re-rendering at STT-partial rate.
180
+ *
181
+ * Must be rendered inside the providers `mountClient()` installs.
182
+ *
183
+ * @example A dispatch board's readout: its own colours, three of its own words
184
+ * ```tsx
185
+ * import type { AgentState } from "@alexkroman1/aai-ui";
186
+ * import { SessionStateDot } from "@alexkroman1/aai-ui";
187
+ *
188
+ * const STATE_COLORS = {
189
+ * disconnected: "#6b7280",
190
+ * connecting: "#6b7280",
191
+ * ready: "#22c55e",
192
+ * listening: "#22c55e",
193
+ * thinking: "#eab308",
194
+ * speaking: "#3b82f6",
195
+ * error: "#6b7280",
196
+ * } satisfies Record<AgentState, string>;
197
+ *
198
+ * function StatusReadout() {
199
+ * return (
200
+ * <SessionStateDot
201
+ * colors={STATE_COLORS}
202
+ * labels={{ listening: "LISTENING", thinking: "PROCESSING", speaking: "TRANSMITTING" }}
203
+ * labelClassName="text-[11px] uppercase"
204
+ * />
205
+ * );
206
+ * }
207
+ * ```
208
+ *
209
+ * @param props - See {@link SessionStateDotProps}.
210
+ *
211
+ * @public
212
+ */
213
+ function SessionStateDot({ colors, labels, pulse = true, className, dotClassName, labelClassName }) {
214
+ const state = useSessionStatus();
215
+ const pulseMs = pulse ? PULSE_MS[state] ?? null : null;
216
+ return /* @__PURE__ */ jsxs("span", {
217
+ className: clsx("inline-flex items-center gap-2", className),
218
+ "data-state": state,
219
+ children: [/* @__PURE__ */ jsx(StateDot, {
220
+ color: colors[state],
221
+ pulseMs,
222
+ className: dotClassName ?? "w-2 h-2",
223
+ title: state
224
+ }), /* @__PURE__ */ jsx("span", {
225
+ className: labelClassName,
226
+ children: labels?.[state] ?? AGENT_STATE_LABELS[state]
227
+ })]
228
+ });
229
+ }
230
+ //#endregion
231
+ //#region src/components/console-shell.tsx
232
+ /** @jsxImportSource react */
233
+ /**
234
+ * Indicator dot color per state.
235
+ *
236
+ * `idle` is the theme's own faint ink step rather than a fixed warm grey, so
237
+ * the dot stays visible on a dark ground; the thinking amber and error red
238
+ * are semantic and stay fixed.
239
+ *
240
+ * @internal
241
+ */
242
+ function stateColor(state, primary, idle) {
243
+ switch (state) {
244
+ case "listening":
245
+ case "speaking":
246
+ case "ready": return primary;
247
+ case "thinking": return THINKING_COLOR;
248
+ case "error": return ERROR_COLOR;
249
+ default: return idle;
250
+ }
251
+ }
252
+ /**
253
+ * The design-system "console" chrome: a 760px column on the themed page with a
254
+ * header (icon + live-status eyebrow), an announced error banner, the main
255
+ * content on a raised card, and a footer row beneath it.
256
+ *
257
+ * {@link ChatView} is this shell with `<MessageList>` inside it and
258
+ * `<Controls>` under it, and until now that was the only way to get it — the
259
+ * shell itself was internal, so a client wanting its own conversation markup
260
+ * had to rebuild the chrome as well. Each one that did re-derived the error
261
+ * banner WITHOUT `role="alert"`.
262
+ *
263
+ * **The banner is {@link SessionErrorBanner} now, composed here rather than
264
+ * spelled out.** It was four lines of this file, and this file is a whole
265
+ * FRAME — a centred `max-w-190` column — so the full-bleed chromes that needed
266
+ * the announced banner could not take it without taking a layout that would
267
+ * replace the design they exist to demonstrate. Composing means there is one
268
+ * banner, and it means this component no longer takes an `error` prop: the
269
+ * banner reads the session itself, which is one fewer thing a caller can wire
270
+ * up wrong.
271
+ *
272
+ * Reach for it when the conversation is yours and the frame is not. Reach for
273
+ * `<ChatView>` when both are ours. Reach for `<SessionErrorBanner>` alone when
274
+ * neither is.
275
+ *
276
+ * Must be rendered inside the providers `mountClient()` installs.
277
+ *
278
+ * @example A custom conversation in the stock chrome
279
+ * ```tsx
280
+ * import {
281
+ * ConsoleShell,
282
+ * Controls,
283
+ * useConversation,
284
+ * useSessionStatus,
285
+ * } from "@alexkroman1/aai-ui";
286
+ *
287
+ * function Console() {
288
+ * const state = useSessionStatus();
289
+ * const { items } = useConversation();
290
+ * return (
291
+ * <ConsoleShell
292
+ * title="Dispatch"
293
+ * state={state}
294
+ * pulsing={state === "listening"}
295
+ * footer={<Controls />}
296
+ * >
297
+ * <ul>
298
+ * {items.map((item) => (
299
+ * <li key={item.kind === "message" ? item.message.id : item.toolCall.callId}>
300
+ * {item.kind === "message" ? item.message.content : item.toolCall.name}
301
+ * </li>
302
+ * ))}
303
+ * </ul>
304
+ * </ConsoleShell>
305
+ * );
306
+ * }
307
+ * ```
308
+ *
309
+ * @param props - See {@link ConsoleShellProps}.
310
+ *
311
+ * @public
312
+ */
313
+ function ConsoleShell({ icon, title, state, pulsing, children, footer, className }) {
314
+ const theme = useTheme();
315
+ return /* @__PURE__ */ jsxs("div", {
316
+ className: clsx("flex flex-col h-screen w-full max-w-190 mx-auto box-border px-6 py-8 gap-5 font-aai text-sm", className),
317
+ style: {
318
+ background: theme.bg,
319
+ color: theme.text
320
+ },
321
+ children: [
322
+ /* @__PURE__ */ jsxs("div", {
323
+ className: "flex items-center justify-between shrink-0",
324
+ children: [/* @__PURE__ */ jsxs("div", {
325
+ className: "flex items-center gap-3 min-w-0",
326
+ children: [icon ?? /* @__PURE__ */ jsx(AaiLogo, { size: 22 }), title && /* @__PURE__ */ jsx("span", {
327
+ className: "font-aai-serif text-[22px] leading-[1.2] font-normal truncate",
328
+ style: { color: theme.text },
329
+ children: title
330
+ })]
331
+ }), /* @__PURE__ */ jsxs(Eyebrow, {
332
+ className: "shrink-0",
333
+ "data-state": state,
334
+ children: [/* @__PURE__ */ jsx(StateDot, {
335
+ color: stateColor(state, theme.primary, inkTint(theme.text, theme.bg, 65)),
336
+ pulseMs: pulsing ? 1600 : null,
337
+ className: "w-[7px] h-[7px]"
338
+ }), state]
339
+ })]
340
+ }),
341
+ /* @__PURE__ */ jsx(SessionErrorBanner, {}),
342
+ /* @__PURE__ */ jsx("div", {
343
+ className: "flex flex-col flex-1 min-h-0 border rounded-lg overflow-hidden",
344
+ style: {
345
+ background: theme.surface,
346
+ borderColor: theme.border,
347
+ boxShadow: "0 1px 3px 0 rgb(20 18 12 / 0.06)"
348
+ },
349
+ children
350
+ }),
351
+ footer
352
+ ]
353
+ });
354
+ }
355
+ //#endregion
356
+ //#region src/components/chat-view.tsx
357
+ const PULSING_STATES = /* @__PURE__ */ new Set(["listening", "speaking"]);
358
+ /**
359
+ * The main chat interface for a voice agent session — the design-system
360
+ * "voice agent console": a 760px column on the cream page with a header
361
+ * (logo + live-status eyebrow), the conversation on a raised white card,
362
+ * and the session controls beneath it.
363
+ *
364
+ * Must be rendered inside a `SessionProvider`.
365
+ *
366
+ * @example
367
+ * ```tsx
368
+ * import { ChatView, StartScreen } from "@alexkroman1/aai-ui";
369
+ *
370
+ * function App() {
371
+ * return (
372
+ * <StartScreen icon="🍕" title="Pizza Palace">
373
+ * <ChatView />
374
+ * </StartScreen>
375
+ * );
376
+ * }
377
+ * ```
378
+ *
379
+ * @param props - Chat surface props.
380
+ *
381
+ * @public
382
+ */
383
+ function ChatView({ icon, title, className }) {
384
+ const state = useSessionStatus();
385
+ return /* @__PURE__ */ jsx(ConsoleShell, {
386
+ icon,
387
+ title,
388
+ state,
389
+ pulsing: PULSING_STATES.has(state),
390
+ className,
391
+ footer: /* @__PURE__ */ jsx(Controls, {}),
392
+ children: /* @__PURE__ */ jsx(MessageList, {})
393
+ });
394
+ }
395
+ //#endregion
396
+ export { AGENT_STATE_LABELS as a, SessionErrorBanner as i, ConsoleShell as n, SessionStateDot as r, ChatView as t };
@@ -0,0 +1,139 @@
1
+ import { n as pageBaseUrl } from "./_utils-Mi-SCFA1.js";
2
+ import { CLIENT_CONFIG_PATH, ClientConfigResponseSchema, SESSION_TICKET_HEADER } from "@alexkroman1/aai/protocol";
3
+ //#region src/client-config.ts
4
+ /**
5
+ * Pre-connection client-config lookup.
6
+ *
7
+ * `GET client-config` (relative to the agent's base URL — see
8
+ * `sdk/client-config.ts` in `@alexkroman1/aai`) gives the default client the
9
+ * agent's display name and greeting before any connection exists. For that
10
+ * use every failure path — network error, 404 from an older server,
11
+ * malformed body — degrades to the empty default (`fetchClientConfig`), so
12
+ * the lookup can never break an existing agent.
13
+ *
14
+ * The session's broker decision needs the opposite: `loadClientConfig`
15
+ * keeps "the lookup failed" (`null`) distinct from "the server answered and
16
+ * named no sessionUrl" (`{}`). See its doc comment.
17
+ */
18
+ /**
19
+ * Resolve a relative endpoint path against the agent's base URL.
20
+ *
21
+ * @internal
22
+ */
23
+ function buildAgentUrl(platformUrl, endpointPath) {
24
+ return new URL(endpointPath, platformUrl.endsWith("/") ? platformUrl : `${platformUrl}/`);
25
+ }
26
+ const AGENT_DEFAULT = { page: "voice" };
27
+ /**
28
+ * Per-attempt deadline for the `client-config` lookup.
29
+ *
30
+ * A request issued while the platform is restarting or saturated can HANG
31
+ * rather than fail — the proxy holds the socket open — and a browser fetch
32
+ * has no timeout of its own. Every other failure here is already handled
33
+ * (`null`, then the same-origin fallback), but a hang is not a failure: the
34
+ * promise simply never settles.
35
+ *
36
+ * That is unrecoverable rather than merely slow, because this lookup runs
37
+ * inside the session's WebSocket URL *provider*. partysocket awaits the
38
+ * provider under `_connectLock` and arms its own `connectionTimeout` only
39
+ * AFTER the URL resolves, so a hung lookup means no socket is ever
40
+ * constructed, no `error`/`close` ever fires, and none of the 10 reconnect
41
+ * attempts ever happen — the session sits on "connecting" forever, and stays
42
+ * there long after the server is back. Reproduced: zero sockets opened.
43
+ *
44
+ * A timed-out attempt therefore degrades exactly like any other failed one —
45
+ * `null`, so `configPerAttempt` stays unlatched and the attempt falls through
46
+ * to the same-origin `websocket` path, whose failure re-enters the normal
47
+ * backoff and re-fetches this on the next attempt.
48
+ *
49
+ * Sized well above the real work (one same-origin JSON GET that reads the
50
+ * agent's row) and well under a user's patience — the same 10s the studio's
51
+ * gating reads use for the identical hazard.
52
+ *
53
+ * @internal
54
+ */
55
+ const CLIENT_CONFIG_ATTEMPT_TIMEOUT_MS = 1e4;
56
+ /**
57
+ * Fetch the agent's client config, reporting `null` when the lookup did not
58
+ * produce an answer (network error, non-2xx, unparsable body).
59
+ *
60
+ * The distinction from `fetchClientConfig` matters for exactly one caller:
61
+ * the session's per-attempt broker decision. A config that ARRIVED and named
62
+ * no `sessionUrl` means "this server is not a broker" (`aai dev`, an older
63
+ * server) — a durable fact worth latching. A lookup that FAILED means
64
+ * nothing about the server, and treating the two alike is how a single 503
65
+ * (a sandbox mid-boot, or one that failed to start) pinned a session to the
66
+ * platform's `/:slug/websocket` — a WebSocket redirect browsers don't
67
+ * follow, so every retry failed with no re-brokering even after the agent
68
+ * recovered.
69
+ *
70
+ * @internal
71
+ */
72
+ async function loadClientConfig(platformUrl, fetchFn, presentedTicket) {
73
+ const doFetch = fetchFn ?? ((input, init) => globalThis.fetch(input, init));
74
+ try {
75
+ const resp = await doFetch(buildAgentUrl(platformUrl, CLIENT_CONFIG_PATH).href, {
76
+ signal: AbortSignal.timeout(CLIENT_CONFIG_ATTEMPT_TIMEOUT_MS),
77
+ ...presentedTicket === void 0 ? {} : { headers: { [SESSION_TICKET_HEADER]: presentedTicket } }
78
+ });
79
+ if (!resp.ok) return null;
80
+ const parsed = ClientConfigResponseSchema.safeParse(await resp.json());
81
+ return parsed.success ? parsed.data : null;
82
+ } catch {
83
+ return null;
84
+ }
85
+ }
86
+ /**
87
+ * Fetch the agent's declared `name`, `greeting` and front door; any failure
88
+ * yields the agent default (`{}`).
89
+ *
90
+ * **This is what a workflow app's own component calls instead of receiving the
91
+ * config.** Both mounts fetch `GET client-config` for the shell they render
92
+ * themselves — `mountClient()` for the chat shell, `mountPage()` for the
93
+ * generated workflow shell — so neither DEFAULT has to be told the agent's
94
+ * name. A `component:` replaces that shell, and with it the lookup, so a page
95
+ * that wants the agent's own `name` or `greeting` asks for them here.
96
+ *
97
+ * Every failure path degrades to the empty default rather than throwing: a
98
+ * network error, a 404 from a server older than the endpoint, a malformed
99
+ * body, and a lookup that hangs past
100
+ * `CLIENT_CONFIG_ATTEMPT_TIMEOUT_MS` all read as "the agent declared nothing".
101
+ * So a page may render straight from the result and never needs a `catch` —
102
+ * treat every field as optional, because an agent that declared none is a
103
+ * normal agent.
104
+ *
105
+ * @param platformUrl - The agent's base URL. **Defaults to the page's own
106
+ * origin and path** (`pageBaseUrl()`), which is the agent that served the page
107
+ * and the only case a browser has — the argument was required while the default
108
+ * sat one module away in this same package, so every caller wrote
109
+ * `location.origin + location.pathname` for itself. Pass one for a page reading
110
+ * a DIFFERENT agent. The endpoint is resolved relative to it.
111
+ * @param fetchFn - Fetch implementation, for tests and for a caller that
112
+ * supplies its own credentials. Defaults to the global `fetch`.
113
+ * @returns The agent's config, or `{}` when the lookup produced no answer.
114
+ *
115
+ * @example
116
+ * ```tsx
117
+ * import { fetchClientConfig, mountPage } from "@alexkroman1/aai-ui";
118
+ *
119
+ * const { name, greeting } = await fetchClientConfig();
120
+ *
121
+ * function App() {
122
+ * return (
123
+ * <main>
124
+ * <h1>{name ?? "Workflows"}</h1>
125
+ * {greeting ? <p>{greeting}</p> : null}
126
+ * </main>
127
+ * );
128
+ * }
129
+ *
130
+ * mountPage({ name: name ?? "Workflows", component: App });
131
+ * ```
132
+ *
133
+ * @public
134
+ */
135
+ async function fetchClientConfig(platformUrl = pageBaseUrl(), fetchFn) {
136
+ return await loadClientConfig(platformUrl, fetchFn) ?? AGENT_DEFAULT;
137
+ }
138
+ //#endregion
139
+ export { fetchClientConfig as n, loadClientConfig as r, buildAgentUrl as t };
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Pre-connection client-config lookup.
3
+ *
4
+ * `GET client-config` (relative to the agent's base URL — see
5
+ * `sdk/client-config.ts` in `@alexkroman1/aai`) gives the default client the
6
+ * agent's display name and greeting before any connection exists. For that
7
+ * use every failure path — network error, 404 from an older server,
8
+ * malformed body — degrades to the empty default (`fetchClientConfig`), so
9
+ * the lookup can never break an existing agent.
10
+ *
11
+ * The session's broker decision needs the opposite: `loadClientConfig`
12
+ * keeps "the lookup failed" (`null`) distinct from "the server answered and
13
+ * named no sessionUrl" (`{}`). See its doc comment.
14
+ */
15
+ import { type ClientConfigResponse } from "@alexkroman1/aai/protocol";
16
+ /** @internal Re-exported for the sibling modules; the SDK's `/protocol` subpath is the canonical home. */
17
+ export type { ClientConfigResponse } from "@alexkroman1/aai/protocol";
18
+ /**
19
+ * Resolve a relative endpoint path against the agent's base URL.
20
+ *
21
+ * @internal
22
+ */
23
+ export declare function buildAgentUrl(platformUrl: string, endpointPath: string): URL;
24
+ /**
25
+ * Per-attempt deadline for the `client-config` lookup.
26
+ *
27
+ * A request issued while the platform is restarting or saturated can HANG
28
+ * rather than fail — the proxy holds the socket open — and a browser fetch
29
+ * has no timeout of its own. Every other failure here is already handled
30
+ * (`null`, then the same-origin fallback), but a hang is not a failure: the
31
+ * promise simply never settles.
32
+ *
33
+ * That is unrecoverable rather than merely slow, because this lookup runs
34
+ * inside the session's WebSocket URL *provider*. partysocket awaits the
35
+ * provider under `_connectLock` and arms its own `connectionTimeout` only
36
+ * AFTER the URL resolves, so a hung lookup means no socket is ever
37
+ * constructed, no `error`/`close` ever fires, and none of the 10 reconnect
38
+ * attempts ever happen — the session sits on "connecting" forever, and stays
39
+ * there long after the server is back. Reproduced: zero sockets opened.
40
+ *
41
+ * A timed-out attempt therefore degrades exactly like any other failed one —
42
+ * `null`, so `configPerAttempt` stays unlatched and the attempt falls through
43
+ * to the same-origin `websocket` path, whose failure re-enters the normal
44
+ * backoff and re-fetches this on the next attempt.
45
+ *
46
+ * Sized well above the real work (one same-origin JSON GET that reads the
47
+ * agent's row) and well under a user's patience — the same 10s the studio's
48
+ * gating reads use for the identical hazard.
49
+ *
50
+ * @internal
51
+ */
52
+ export declare const CLIENT_CONFIG_ATTEMPT_TIMEOUT_MS = 10000;
53
+ /**
54
+ * Fetch the agent's client config, reporting `null` when the lookup did not
55
+ * produce an answer (network error, non-2xx, unparsable body).
56
+ *
57
+ * The distinction from `fetchClientConfig` matters for exactly one caller:
58
+ * the session's per-attempt broker decision. A config that ARRIVED and named
59
+ * no `sessionUrl` means "this server is not a broker" (`aai dev`, an older
60
+ * server) — a durable fact worth latching. A lookup that FAILED means
61
+ * nothing about the server, and treating the two alike is how a single 503
62
+ * (a sandbox mid-boot, or one that failed to start) pinned a session to the
63
+ * platform's `/:slug/websocket` — a WebSocket redirect browsers don't
64
+ * follow, so every retry failed with no re-brokering even after the agent
65
+ * recovered.
66
+ *
67
+ * @internal
68
+ */
69
+ export declare function loadClientConfig(platformUrl: string, fetchFn?: typeof globalThis.fetch, presentedTicket?: string): Promise<ClientConfigResponse | null>;
70
+ /**
71
+ * Fetch the agent's declared `name`, `greeting` and front door; any failure
72
+ * yields the agent default (`{}`).
73
+ *
74
+ * **This is what a workflow app's own component calls instead of receiving the
75
+ * config.** Both mounts fetch `GET client-config` for the shell they render
76
+ * themselves — `mountClient()` for the chat shell, `mountPage()` for the
77
+ * generated workflow shell — so neither DEFAULT has to be told the agent's
78
+ * name. A `component:` replaces that shell, and with it the lookup, so a page
79
+ * that wants the agent's own `name` or `greeting` asks for them here.
80
+ *
81
+ * Every failure path degrades to the empty default rather than throwing: a
82
+ * network error, a 404 from a server older than the endpoint, a malformed
83
+ * body, and a lookup that hangs past
84
+ * `CLIENT_CONFIG_ATTEMPT_TIMEOUT_MS` all read as "the agent declared nothing".
85
+ * So a page may render straight from the result and never needs a `catch` —
86
+ * treat every field as optional, because an agent that declared none is a
87
+ * normal agent.
88
+ *
89
+ * @param platformUrl - The agent's base URL. **Defaults to the page's own
90
+ * origin and path** (`pageBaseUrl()`), which is the agent that served the page
91
+ * and the only case a browser has — the argument was required while the default
92
+ * sat one module away in this same package, so every caller wrote
93
+ * `location.origin + location.pathname` for itself. Pass one for a page reading
94
+ * a DIFFERENT agent. The endpoint is resolved relative to it.
95
+ * @param fetchFn - Fetch implementation, for tests and for a caller that
96
+ * supplies its own credentials. Defaults to the global `fetch`.
97
+ * @returns The agent's config, or `{}` when the lookup produced no answer.
98
+ *
99
+ * @example
100
+ * ```tsx
101
+ * import { fetchClientConfig, mountPage } from "@alexkroman1/aai-ui";
102
+ *
103
+ * const { name, greeting } = await fetchClientConfig();
104
+ *
105
+ * function App() {
106
+ * return (
107
+ * <main>
108
+ * <h1>{name ?? "Workflows"}</h1>
109
+ * {greeting ? <p>{greeting}</p> : null}
110
+ * </main>
111
+ * );
112
+ * }
113
+ *
114
+ * mountPage({ name: name ?? "Workflows", component: App });
115
+ * ```
116
+ *
117
+ * @public
118
+ */
119
+ export declare function fetchClientConfig(platformUrl?: string, fetchFn?: typeof globalThis.fetch): Promise<ClientConfigResponse>;