@alexkroman1/aai-ui 0.9.3 → 0.10.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 (39) hide show
  1. package/dist/_components/app.d.ts +19 -1
  2. package/dist/_components/app.js +19 -1
  3. package/dist/_components/button.d.ts +43 -4
  4. package/dist/_components/button.js +33 -9
  5. package/dist/_components/chat-view.d.ts +25 -1
  6. package/dist/_components/chat-view.js +25 -1
  7. package/dist/_components/controls.d.ts +15 -1
  8. package/dist/_components/controls.js +15 -1
  9. package/dist/_components/error-banner.d.ts +15 -1
  10. package/dist/_components/error-banner.js +15 -1
  11. package/dist/_components/message-bubble.d.ts +19 -3
  12. package/dist/_components/message-bubble.js +17 -1
  13. package/dist/_components/message-list.d.ts +18 -1
  14. package/dist/_components/message-list.js +20 -3
  15. package/dist/_components/state-indicator.d.ts +23 -1
  16. package/dist/_components/state-indicator.js +23 -1
  17. package/dist/_components/thinking-indicator.d.ts +12 -1
  18. package/dist/_components/thinking-indicator.js +23 -11
  19. package/dist/_components/tool-call-block.d.ts +21 -1
  20. package/dist/_components/tool-call-block.js +33 -11
  21. package/dist/_components/transcript.d.ts +19 -1
  22. package/dist/_components/transcript.js +19 -1
  23. package/dist/_sqlite-stub.d.ts +3 -0
  24. package/dist/audio.js +13 -3
  25. package/dist/client-handler.d.ts +43 -0
  26. package/dist/index.d.ts +23 -6
  27. package/dist/index.js +3 -4
  28. package/dist/mount.d.ts +4 -0
  29. package/dist/mount.js +7 -4
  30. package/dist/session-D0RdWlWH.js +451 -0
  31. package/dist/session.d.ts +5 -36
  32. package/dist/session.js +1 -358
  33. package/dist/signals.d.ts +48 -0
  34. package/dist/signals.js +84 -25
  35. package/dist/types.d.ts +19 -5
  36. package/package.json +12 -10
  37. package/styles.css +2 -3
  38. package/dist/components.d.ts +0 -31
  39. package/dist/components.js +0 -17
package/dist/session.js CHANGED
@@ -1,359 +1,2 @@
1
- import { ServerMessageSchema } from "@alexkroman1/aai/protocol";
2
- import { errorMessage } from "@alexkroman1/aai/utils";
3
- //#region session.ts
4
- /** Built-in non-reactive container (plain mutable wrapper). */
5
- function plainReactive(initial) {
6
- return { value: initial };
7
- }
8
- /** No-op batch — just calls the function. */
9
- function plainBatch(fn) {
10
- fn();
11
- }
12
- /**
13
- * Handles server→client messages and updates reactive Preact signals
14
- * accordingly (state transitions, transcripts, messages, audio playback).
15
- */
16
- /** @internal Exported for testing only. */
17
- var ClientHandler = class {
18
- #state;
19
- #messages;
20
- #toolCalls;
21
- #userUtterance;
22
- #agentUtterance;
23
- #error;
24
- #voiceIO;
25
- #batch;
26
- /** Incremented on each turn boundary — stale async callbacks compare against this. */
27
- #generation = 0;
28
- constructor(opts) {
29
- this.#state = opts.state;
30
- this.#messages = opts.messages;
31
- this.#toolCalls = opts.toolCalls;
32
- this.#userUtterance = opts.userUtterance;
33
- this.#agentUtterance = opts.agentUtterance;
34
- this.#error = opts.error;
35
- this.#voiceIO = opts.voiceIO;
36
- this.#batch = opts.batch;
37
- }
38
- /** Single entry point for all server→client session events. */
39
- event(e) {
40
- switch (e.type) {
41
- case "speech_started":
42
- this.#userUtterance.value = "";
43
- break;
44
- case "speech_stopped": break;
45
- case "transcript":
46
- this.#userUtterance.value = e.text;
47
- break;
48
- case "turn":
49
- this.#generation++;
50
- this.#batch(() => {
51
- this.#userUtterance.value = null;
52
- this.#messages.value = [...this.#messages.value, {
53
- role: "user",
54
- content: e.text
55
- }];
56
- this.#state.value = "thinking";
57
- });
58
- break;
59
- case "chat_delta":
60
- this.#agentUtterance.value = this.#agentUtterance.value ? `${this.#agentUtterance.value} ${e.text}` : e.text;
61
- break;
62
- case "chat":
63
- this.#batch(() => {
64
- this.#agentUtterance.value = null;
65
- this.#messages.value = [...this.#messages.value, {
66
- role: "assistant",
67
- content: e.text
68
- }];
69
- });
70
- break;
71
- case "tool_call_start":
72
- this.#toolCalls.value = [...this.#toolCalls.value, {
73
- toolCallId: e.toolCallId,
74
- toolName: e.toolName,
75
- args: e.args,
76
- status: "pending",
77
- afterMessageIndex: this.#messages.value.length - 1
78
- }];
79
- break;
80
- case "tool_call_done": {
81
- const tcs = this.#toolCalls.value;
82
- const idx = tcs.findIndex((tc) => tc.toolCallId === e.toolCallId);
83
- if (idx !== -1) {
84
- const updated = [...tcs];
85
- const existing = updated[idx];
86
- if (existing) updated[idx] = {
87
- ...existing,
88
- status: "done",
89
- result: e.result
90
- };
91
- this.#toolCalls.value = updated;
92
- }
93
- break;
94
- }
95
- case "tts_done":
96
- this.#state.value = "listening";
97
- break;
98
- case "cancelled":
99
- this.#generation++;
100
- this.#voiceIO()?.flush();
101
- this.#batch(() => {
102
- this.#userUtterance.value = null;
103
- this.#agentUtterance.value = null;
104
- this.#state.value = "listening";
105
- });
106
- break;
107
- case "reset":
108
- this.#generation++;
109
- this.#voiceIO()?.flush();
110
- this.#batch(() => {
111
- this.#messages.value = [];
112
- this.#toolCalls.value = [];
113
- this.#userUtterance.value = null;
114
- this.#agentUtterance.value = null;
115
- this.#error.value = null;
116
- this.#state.value = "listening";
117
- });
118
- break;
119
- case "error":
120
- console.error("Agent error:", e.message);
121
- this.#batch(() => {
122
- this.#error.value = {
123
- code: e.code,
124
- message: e.message
125
- };
126
- this.#state.value = "error";
127
- });
128
- break;
129
- }
130
- }
131
- playAudioChunk(chunk) {
132
- if (this.#state.value === "error") return;
133
- if (this.#state.value !== "speaking") this.#state.value = "speaking";
134
- this.#voiceIO()?.enqueue(chunk.buffer);
135
- }
136
- playAudioDone() {
137
- const gen = this.#generation;
138
- const io = this.#voiceIO();
139
- if (io) io.done().then(() => {
140
- if (this.#generation !== gen) return;
141
- this.#state.value = "listening";
142
- }).catch(() => {});
143
- else this.#state.value = "listening";
144
- }
145
- /**
146
- * Dispatch an incoming WebSocket message (text or binary).
147
- *
148
- * Returns the parsed config if the message is a `config` message,
149
- * otherwise `null`.
150
- */
151
- handleMessage(data) {
152
- if (data instanceof ArrayBuffer) {
153
- this.playAudioChunk(new Uint8Array(data));
154
- return null;
155
- }
156
- let msg;
157
- try {
158
- const parsed = ServerMessageSchema.safeParse(JSON.parse(data));
159
- if (!parsed.success) return null;
160
- msg = parsed.data;
161
- } catch {
162
- return null;
163
- }
164
- if (msg.type === "config") {
165
- const { type: _, ...config } = msg;
166
- return config;
167
- }
168
- if (msg.type === "audio_done") {
169
- this.playAudioDone();
170
- return null;
171
- }
172
- this.event(msg);
173
- return null;
174
- }
175
- };
176
- /**
177
- * Create a voice session that connects to an AAI server via WebSocket.
178
- *
179
- * Uses plain JSON text frames and binary audio frames for communication.
180
- *
181
- * @param options - Session configuration including the platform server URL.
182
- * @returns A {@link VoiceSession} handle for controlling the session.
183
- *
184
- * @public
185
- */
186
- function createVoiceSession(options) {
187
- const reactive = options.signal ?? plainReactive;
188
- const batchFn = options.batch ?? plainBatch;
189
- const state = reactive("disconnected");
190
- const messages = reactive([]);
191
- const toolCalls = reactive([]);
192
- const userUtterance = reactive(null);
193
- const agentUtterance = reactive(null);
194
- const error = reactive(null);
195
- const disconnected = reactive(null);
196
- let ws = null;
197
- let voiceIO = null;
198
- let connectionController = null;
199
- let hasConnected = false;
200
- let audioSetupInFlight = false;
201
- function cleanupAudio() {
202
- audioSetupInFlight = false;
203
- voiceIO?.close();
204
- voiceIO = null;
205
- }
206
- function resetState() {
207
- batchFn(() => {
208
- messages.value = [];
209
- toolCalls.value = [];
210
- userUtterance.value = null;
211
- agentUtterance.value = null;
212
- error.value = null;
213
- });
214
- }
215
- function send(msg) {
216
- if (ws && ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify(msg));
217
- }
218
- function sendBinary(data) {
219
- if (ws && ws.readyState === WebSocket.OPEN) ws.send(data);
220
- }
221
- async function handleReady(msg) {
222
- if (audioSetupInFlight) return;
223
- audioSetupInFlight = true;
224
- try {
225
- const [{ createVoiceIO }, captureWorklet, playbackWorklet] = await Promise.all([
226
- import("./audio.js"),
227
- import("./worklets/capture-processor.js").then((m) => m.default),
228
- import("./worklets/playback-processor.js").then((m) => m.default)
229
- ]);
230
- const io = await createVoiceIO({
231
- sttSampleRate: msg.sampleRate,
232
- ttsSampleRate: msg.ttsSampleRate,
233
- captureWorkletSrc: captureWorklet,
234
- playbackWorkletSrc: playbackWorklet,
235
- onMicData: (pcm16) => {
236
- try {
237
- sendBinary(pcm16);
238
- } catch {}
239
- }
240
- });
241
- if (!ws || ws.readyState !== WebSocket.OPEN) {
242
- io.close();
243
- return;
244
- }
245
- voiceIO = io;
246
- send({ type: "audio_ready" });
247
- state.value = "listening";
248
- } catch (err) {
249
- if (!ws || ws.readyState !== WebSocket.OPEN) return;
250
- batchFn(() => {
251
- error.value = {
252
- code: "audio",
253
- message: `Microphone access failed: ${errorMessage(err)}`
254
- };
255
- state.value = "error";
256
- });
257
- } finally {
258
- audioSetupInFlight = false;
259
- }
260
- }
261
- function connect(opts) {
262
- disconnected.value = null;
263
- state.value = "connecting";
264
- connectionController?.abort();
265
- cleanupAudio();
266
- ws?.close();
267
- ws = null;
268
- const controller = new AbortController();
269
- connectionController = controller;
270
- const { signal: sig } = controller;
271
- if (opts?.signal) opts.signal.addEventListener("abort", () => disconnect(), { signal: sig });
272
- const base = options.platformUrl;
273
- const wsUrl = new URL("websocket", base.endsWith("/") ? base : `${base}/`);
274
- wsUrl.protocol = wsUrl.protocol === "https:" ? "wss:" : "ws:";
275
- if (hasConnected) wsUrl.searchParams.set("resume", "1");
276
- const socket = new WebSocket(wsUrl.toString());
277
- socket.binaryType = "arraybuffer";
278
- ws = socket;
279
- const handler = new ClientHandler({
280
- state,
281
- messages,
282
- toolCalls,
283
- userUtterance,
284
- agentUtterance,
285
- error,
286
- voiceIO: () => voiceIO,
287
- batch: batchFn
288
- });
289
- socket.addEventListener("open", () => {
290
- state.value = "ready";
291
- }, { signal: sig });
292
- socket.addEventListener("message", (event) => {
293
- const msgEvent = event;
294
- const config = handler.handleMessage(msgEvent.data);
295
- if (config) {
296
- const isReconnect = hasConnected;
297
- hasConnected = true;
298
- handleReady(config);
299
- if (isReconnect && messages.value.length > 0) send({
300
- type: "history",
301
- messages: messages.value.map((m) => ({
302
- role: m.role,
303
- text: m.content
304
- }))
305
- });
306
- }
307
- }, { signal: sig });
308
- socket.addEventListener("close", () => {
309
- if (sig.aborted) return;
310
- controller.abort();
311
- disconnected.value = { intentional: false };
312
- cleanupAudio();
313
- state.value = "disconnected";
314
- }, { signal: sig });
315
- }
316
- function cancel() {
317
- voiceIO?.flush();
318
- state.value = "listening";
319
- send({ type: "cancel" });
320
- }
321
- function reset() {
322
- voiceIO?.flush();
323
- if (ws && ws.readyState === WebSocket.OPEN) {
324
- send({ type: "reset" });
325
- return;
326
- }
327
- resetState();
328
- disconnect();
329
- connect();
330
- }
331
- function disconnect() {
332
- connectionController?.abort();
333
- connectionController = null;
334
- cleanupAudio();
335
- ws?.close();
336
- ws = null;
337
- state.value = "disconnected";
338
- disconnected.value = { intentional: true };
339
- }
340
- return {
341
- state,
342
- messages,
343
- toolCalls,
344
- userUtterance,
345
- agentUtterance,
346
- error,
347
- disconnected,
348
- connect,
349
- cancel,
350
- resetState,
351
- reset,
352
- disconnect,
353
- [Symbol.dispose]() {
354
- disconnect();
355
- }
356
- };
357
- }
358
- //#endregion
1
+ import { n as ClientHandler, t as createVoiceSession } from "./session-D0RdWlWH.js";
359
2
  export { ClientHandler, createVoiceSession };
package/dist/signals.d.ts CHANGED
@@ -73,12 +73,60 @@ export declare function useSession(): SessionSignals;
73
73
  *
74
74
  * Automatically resets tracking when the session is reset (toolCalls cleared).
75
75
  *
76
+ * @example
77
+ * ```tsx
78
+ * // Filter by tool name with a typed result:
79
+ * useToolResult<Recipe>("get_recipe", (result, toolCall) => {
80
+ * setRecipe(result);
81
+ * });
82
+ *
83
+ * // Receive all tool results (untyped):
84
+ * useToolResult((toolName, result, toolCall) => {
85
+ * console.log(toolName, result);
86
+ * });
87
+ * ```
88
+ *
89
+ * @public
90
+ */
91
+ export declare function useToolResult<R = unknown>(toolName: string, callback: (result: R, toolCall: ToolCallInfo) => void): void;
92
+ /**
93
+ * Hook that fires a callback exactly once for each newly completed tool call.
94
+ *
76
95
  * @param callback - Called once per completed tool call with the tool name,
77
96
  * parsed result, and full {@link ToolCallInfo}.
78
97
  *
79
98
  * @public
80
99
  */
81
100
  export declare function useToolResult(callback: (toolName: string, result: unknown, toolCall: ToolCallInfo) => void): void;
101
+ /**
102
+ * Hook that fires a callback when a new tool call starts (status: "pending").
103
+ *
104
+ * Use this to show a loading/skeleton UI immediately when the agent invokes
105
+ * a tool, rather than waiting for the full result. The callback receives the
106
+ * tool name, parsed arguments, and the full {@link ToolCallInfo}.
107
+ *
108
+ * @param callback - Called once per new tool call at start time.
109
+ *
110
+ * @public
111
+ */
112
+ export declare function useToolCallStart(callback: (toolName: string, args: Record<string, unknown>, toolCall: ToolCallInfo) => void): void;
113
+ /**
114
+ * Hook that fires a callback for each intermediate update pushed by a tool
115
+ * via `ctx.sendUpdate()`.
116
+ *
117
+ * Use this to render progressive/streaming data from a tool before it
118
+ * finishes — e.g. showing a recipe preview card while nutrition data is
119
+ * still loading.
120
+ *
121
+ * The callback fires once per update. The `data` argument is the parsed
122
+ * JSON update (or raw string if parsing fails).
123
+ *
124
+ * @param callback - Called once per intermediate update with the tool name,
125
+ * parsed data, and full {@link ToolCallInfo}.
126
+ *
127
+ * @public
128
+ */
129
+ export declare function useToolCallUpdate(callback: (toolName: string, data: unknown, toolCall: ToolCallInfo) => void): void;
82
130
  /**
83
131
  * Auto-scroll a container to the bottom when messages, tool calls,
84
132
  * or utterances change. Returns a ref to attach to a sentinel `<div>`
package/dist/signals.js CHANGED
@@ -80,40 +80,99 @@ function tryParseJSON(str) {
80
80
  return str;
81
81
  }
82
82
  }
83
+ function isNewCompletedCall(tc, seen, filterName) {
84
+ if (tc.status !== "done" || !tc.result) return false;
85
+ if (seen.has(tc.toolCallId)) return false;
86
+ if (filterName && tc.toolName !== filterName) return false;
87
+ return true;
88
+ }
89
+ function useToolResult(toolNameOrCallback, maybeCallback) {
90
+ const filterName = typeof toolNameOrCallback === "string" ? toolNameOrCallback : void 0;
91
+ const callback = typeof toolNameOrCallback === "function" ? toolNameOrCallback : (_name, result, tc) => maybeCallback?.(result, tc);
92
+ const { session } = useSession();
93
+ const seenRef = useRef(/* @__PURE__ */ new Set());
94
+ const cbRef = useRef(callback);
95
+ cbRef.current = callback;
96
+ useEffect(() => effect(() => {
97
+ const toolCalls = session.toolCalls.value;
98
+ if (toolCalls.length === 0) {
99
+ seenRef.current.clear();
100
+ return;
101
+ }
102
+ for (const tc of toolCalls) {
103
+ if (!isNewCompletedCall(tc, seenRef.current, filterName)) continue;
104
+ seenRef.current.add(tc.toolCallId);
105
+ cbRef.current(tc.toolName, tryParseJSON(tc.result), tc);
106
+ }
107
+ }), [session]);
108
+ }
83
109
  /**
84
- * Hook that fires a callback exactly once for each newly completed tool call.
85
- *
86
- * Handles deduplication internally — safe to use with `useState` setters
87
- * without worrying about duplicates. The `result` argument is the parsed
88
- * JSON result from the tool (or the raw string if parsing fails).
110
+ * Hook that fires a callback when a new tool call starts (status: "pending").
89
111
  *
90
- * Automatically resets tracking when the session is reset (toolCalls cleared).
112
+ * Use this to show a loading/skeleton UI immediately when the agent invokes
113
+ * a tool, rather than waiting for the full result. The callback receives the
114
+ * tool name, parsed arguments, and the full {@link ToolCallInfo}.
91
115
  *
92
- * @param callback - Called once per completed tool call with the tool name,
93
- * parsed result, and full {@link ToolCallInfo}.
116
+ * @param callback - Called once per new tool call at start time.
94
117
  *
95
118
  * @public
96
119
  */
97
- function useToolResult(callback) {
120
+ function useToolCallStart(callback) {
98
121
  const { session } = useSession();
99
122
  const seenRef = useRef(/* @__PURE__ */ new Set());
100
123
  const cbRef = useRef(callback);
101
124
  cbRef.current = callback;
102
- useEffect(() => {
103
- return effect(() => {
104
- const toolCalls = session.toolCalls.value;
105
- if (toolCalls.length === 0) {
106
- seenRef.current.clear();
107
- return;
108
- }
109
- for (const tc of toolCalls) {
110
- if (tc.status !== "done" || !tc.result) continue;
111
- if (seenRef.current.has(tc.toolCallId)) continue;
112
- seenRef.current.add(tc.toolCallId);
113
- cbRef.current(tc.toolName, tryParseJSON(tc.result), tc);
114
- }
115
- });
116
- }, [session]);
125
+ useEffect(() => effect(() => {
126
+ const toolCalls = session.toolCalls.value;
127
+ if (toolCalls.length === 0) {
128
+ seenRef.current.clear();
129
+ return;
130
+ }
131
+ for (const tc of toolCalls) {
132
+ if (seenRef.current.has(tc.toolCallId)) continue;
133
+ seenRef.current.add(tc.toolCallId);
134
+ cbRef.current(tc.toolName, tc.args, tc);
135
+ }
136
+ }), [session]);
137
+ }
138
+ /**
139
+ * Hook that fires a callback for each intermediate update pushed by a tool
140
+ * via `ctx.sendUpdate()`.
141
+ *
142
+ * Use this to render progressive/streaming data from a tool before it
143
+ * finishes — e.g. showing a recipe preview card while nutrition data is
144
+ * still loading.
145
+ *
146
+ * The callback fires once per update. The `data` argument is the parsed
147
+ * JSON update (or raw string if parsing fails).
148
+ *
149
+ * @param callback - Called once per intermediate update with the tool name,
150
+ * parsed data, and full {@link ToolCallInfo}.
151
+ *
152
+ * @public
153
+ */
154
+ function useToolCallUpdate(callback) {
155
+ const { session } = useSession();
156
+ const countRef = useRef(/* @__PURE__ */ new Map());
157
+ const cbRef = useRef(callback);
158
+ cbRef.current = callback;
159
+ useEffect(() => effect(() => {
160
+ const toolCalls = session.toolCalls.value;
161
+ if (toolCalls.length === 0) {
162
+ countRef.current.clear();
163
+ return;
164
+ }
165
+ for (const tc of toolCalls) processNewUpdates(tc, countRef.current, cbRef.current);
166
+ }), [session]);
167
+ }
168
+ function processNewUpdates(tc, seenCounts, cb) {
169
+ const seen = seenCounts.get(tc.toolCallId) ?? 0;
170
+ if (tc.updates.length <= seen) return;
171
+ for (let i = seen; i < tc.updates.length; i++) {
172
+ const raw = tc.updates[i];
173
+ if (raw !== void 0) cb(tc.toolName, tryParseJSON(raw), tc);
174
+ }
175
+ seenCounts.set(tc.toolCallId, tc.updates.length);
117
176
  }
118
177
  /**
119
178
  * Auto-scroll a container to the bottom when messages, tool calls,
@@ -135,4 +194,4 @@ function useAutoScroll() {
135
194
  return ref;
136
195
  }
137
196
  //#endregion
138
- export { SessionProvider, createSessionControls, useAutoScroll, useSession, useToolResult };
197
+ export { SessionProvider, createSessionControls, useAutoScroll, useSession, useToolCallStart, useToolCallUpdate, useToolResult };
package/dist/types.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { SessionErrorCode } from "@alexkroman1/aai/protocol";
1
2
  /** Microphone buffer duration in seconds before sending to the server. */
2
3
  export declare const MIC_BUFFER_SECONDS = 0.1;
3
4
  /**
@@ -11,7 +12,7 @@ export type AgentState = "disconnected" | "connecting" | "ready" | "listening" |
11
12
  *
12
13
  * @public
13
14
  */
14
- export type Message = {
15
+ export type ChatMessage = {
15
16
  /** The sender of the message. */
16
17
  role: "user" | "assistant";
17
18
  /** The text content of the message. */
@@ -28,11 +29,12 @@ export type ToolCallInfo = {
28
29
  args: Record<string, unknown>;
29
30
  status: "pending" | "done";
30
31
  result?: string | undefined;
32
+ /** Intermediate updates pushed by the tool via `ctx.sendUpdate()`. */
33
+ updates: string[];
31
34
  /** Index in the messages array where this tool call should appear. */
32
35
  afterMessageIndex: number;
33
36
  };
34
- import type { SessionErrorCode } from "@alexkroman1/aai/protocol";
35
- export type { SessionErrorCode };
37
+ export type { SessionErrorCode } from "@alexkroman1/aai/protocol";
36
38
  /**
37
39
  * Error reported by the voice session.
38
40
  *
@@ -57,7 +59,7 @@ export type Reactive<T> = {
57
59
  *
58
60
  * @public
59
61
  */
60
- export type SessionOptions = {
62
+ export type VoiceSessionOptions = {
61
63
  /** Base URL of the AAI platform server. */
62
64
  platformUrl: string;
63
65
  /**
@@ -65,11 +67,23 @@ export type SessionOptions = {
65
67
  * Defaults to a plain mutable wrapper. Pass `signal` from
66
68
  * `@preact/signals` for automatic Preact component re-rendering.
67
69
  */
68
- signal?: <T>(initial: T) => Reactive<T>;
70
+ reactiveFactory?: <T>(initial: T) => Reactive<T>;
69
71
  /**
70
72
  * Function to batch multiple reactive updates.
71
73
  * Defaults to calling the function directly. Pass `batch` from
72
74
  * `@preact/signals` for optimized batched updates.
73
75
  */
74
76
  batch?: (fn: () => void) => void;
77
+ /**
78
+ * Called when the server sends a session ID in the config message.
79
+ * Use this to store the ID (e.g. in localStorage) for reconnection
80
+ * via `resumeSessionId`.
81
+ */
82
+ onSessionId?: ((sessionId: string) => void) | undefined;
83
+ /**
84
+ * Session ID from a previous connection. When set, the server will
85
+ * attempt to restore persisted session state (if the agent has
86
+ * `persistence` enabled).
87
+ */
88
+ resumeSessionId?: string | undefined;
75
89
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alexkroman1/aai-ui",
3
- "version": "0.9.3",
3
+ "version": "0.10.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",
@@ -17,16 +17,11 @@
17
17
  "source": "./session.ts",
18
18
  "types": "./dist/session.d.ts",
19
19
  "import": "./dist/session.js"
20
- },
21
- "./components": {
22
- "source": "./components.ts",
23
- "types": "./dist/components.d.ts",
24
- "import": "./dist/components.js"
25
20
  }
26
21
  },
27
22
  "dependencies": {
28
23
  "clsx": "^2.1.1",
29
- "@alexkroman1/aai": "0.9.3"
24
+ "@alexkroman1/aai": "0.10.0"
30
25
  },
31
26
  "peerDependencies": {
32
27
  "@preact/signals": "^2.8.2",
@@ -43,14 +38,21 @@
43
38
  "@testing-library/preact": "^3.2.4",
44
39
  "jsdom": "^29.0.1",
45
40
  "preact": "^10.29.0",
46
- "tsdown": "^0.21.4"
41
+ "tsdown": "^0.21.5"
47
42
  },
48
43
  "engines": {
49
- "node": ">=22"
44
+ "node": ">=22.6"
45
+ },
46
+ "repository": {
47
+ "type": "git",
48
+ "url": "https://github.com/alexkroman/agent.git",
49
+ "directory": "packages/aai-ui"
50
50
  },
51
51
  "scripts": {
52
52
  "build": "tsdown && tsc -p tsconfig.build.json",
53
53
  "typecheck": "tsc --noEmit",
54
- "lint": "biome check ."
54
+ "lint": "biome check .",
55
+ "check:api": "api-extractor run -c api-extractor.json",
56
+ "check:attw": "attw --pack --profile esm-only --entrypoints . ./session"
55
57
  }
56
58
  }
package/styles.css CHANGED
@@ -1,4 +1,3 @@
1
- @import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=IBM+Plex+Mono:wght@400;500&display=swap");
2
1
  @import "tailwindcss";
3
2
  @source "./";
4
3
  @source "../dist/ui/";
@@ -24,8 +23,8 @@
24
23
  --color-aai-state-speaking: #e06c75;
25
24
  --color-aai-state-error: #e06c75;
26
25
  --radius-aai: 6px;
27
- --font-aai: "Inter", system-ui, -apple-system, sans-serif;
28
- --font-aai-mono: "IBM Plex Mono", monospace;
26
+ --font-aai: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
27
+ --font-aai-mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
29
28
  }
30
29
 
31
30
  @layer base {