@alexkroman1/aai-ui 0.12.2 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_react-test-utils.d.ts +86 -0
- package/dist/audio.js +4 -5
- package/dist/build-default-client.d.ts +1 -0
- package/dist/{_components → components}/button.d.ts +4 -4
- package/dist/{_components → components}/button.js +28 -5
- package/dist/components/chat-view.d.ts +38 -0
- package/dist/components/chat-view.js +102 -0
- package/dist/{_components → components}/controls.d.ts +1 -1
- package/dist/{_components → components}/controls.js +8 -5
- package/dist/{_components → components}/message-list.d.ts +2 -2
- package/dist/components/message-list.js +149 -0
- package/dist/components/sidebar-layout.d.ts +21 -0
- package/dist/components/sidebar-layout.js +43 -0
- package/dist/{_components → components}/start-screen.d.ts +4 -4
- package/dist/{_components → components}/start-screen.js +17 -8
- package/dist/{_components → components}/tool-call-block.d.ts +5 -6
- package/dist/components/tool-call-block.js +3 -0
- package/dist/components/tool-config-context.d.ts +22 -0
- package/dist/context.d.ts +23 -0
- package/dist/context.js +39 -0
- package/dist/default-client/assets/audio-DH9LpexG.js +1 -0
- package/dist/default-client/assets/capture-processor-C26lSiVr.js +53 -0
- package/dist/default-client/assets/default-client-AeJhjHfj.js +49 -0
- package/dist/default-client/assets/default-client-BK1ItCvw.css +2 -0
- package/dist/default-client/assets/playback-processor-dSA8Im99.js +101 -0
- package/dist/default-client/default-client.html +15 -0
- package/dist/default-client.d.ts +2 -0
- package/dist/define-client.d.ts +72 -24
- package/dist/define-client.js +71 -45
- package/dist/hooks.d.ts +5 -0
- package/dist/hooks.js +55 -0
- package/dist/index.d.ts +15 -37
- package/dist/index.js +11 -18
- package/dist/session-core.d.ts +70 -0
- package/dist/session-core.js +437 -0
- package/dist/tool-call-block-CCuChsFm.js +131 -0
- package/dist/types.d.ts +19 -22
- package/package.json +24 -18
- package/styles.css +3 -33
- package/dist/_components/app.d.ts +0 -23
- package/dist/_components/app.js +0 -41
- package/dist/_components/chat-view.d.ts +0 -29
- package/dist/_components/chat-view.js +0 -61
- package/dist/_components/error-banner.d.ts +0 -21
- package/dist/_components/error-banner.js +0 -27
- package/dist/_components/message-bubble.d.ts +0 -23
- package/dist/_components/message-bubble.js +0 -35
- package/dist/_components/message-list.js +0 -76
- package/dist/_components/sidebar-layout.d.ts +0 -21
- package/dist/_components/sidebar-layout.js +0 -36
- package/dist/_components/state-indicator.d.ts +0 -29
- package/dist/_components/state-indicator.js +0 -37
- package/dist/_components/thinking-indicator.d.ts +0 -16
- package/dist/_components/thinking-indicator.js +0 -34
- package/dist/_components/tool-call-block.js +0 -118
- package/dist/_components/tool-icons.d.ts +0 -17
- package/dist/_components/tool-icons.js +0 -128
- package/dist/_components/transcript.d.ts +0 -25
- package/dist/_components/transcript.js +0 -35
- package/dist/client-context.d.ts +0 -33
- package/dist/client-context.js +0 -15
- package/dist/client-handler.d.ts +0 -43
- package/dist/session-CWB7vmqz.js +0 -429
- package/dist/session.d.ts +0 -69
- package/dist/session.js +0 -2
- package/dist/signals.d.ts +0 -120
- package/dist/signals.js +0 -161
- package/dist/types.test-d.d.ts +0 -7
package/dist/session-CWB7vmqz.js
DELETED
|
@@ -1,429 +0,0 @@
|
|
|
1
|
-
import { errorMessage } from "@alexkroman1/aai/utils";
|
|
2
|
-
import { ReadyConfigSchema, ServerMessageSchema } from "@alexkroman1/aai/protocol";
|
|
3
|
-
//#region client-handler.ts
|
|
4
|
-
/**
|
|
5
|
-
* Handles server→client messages and updates reactive Preact signals
|
|
6
|
-
* accordingly (state transitions, transcripts, messages, audio playback).
|
|
7
|
-
*
|
|
8
|
-
* @internal Exported for testing only.
|
|
9
|
-
*/
|
|
10
|
-
var ClientHandler = class {
|
|
11
|
-
#state;
|
|
12
|
-
#messages;
|
|
13
|
-
#toolCalls;
|
|
14
|
-
#userUtterance;
|
|
15
|
-
#agentUtterance;
|
|
16
|
-
#error;
|
|
17
|
-
#voiceIO;
|
|
18
|
-
#batch;
|
|
19
|
-
/** Incremented on each turn boundary — stale async callbacks compare against this. */
|
|
20
|
-
#generation = 0;
|
|
21
|
-
/** Accumulated chat_delta text for real-time display. */
|
|
22
|
-
#deltaAccum = "";
|
|
23
|
-
constructor(opts) {
|
|
24
|
-
this.#state = opts.state;
|
|
25
|
-
this.#messages = opts.messages;
|
|
26
|
-
this.#toolCalls = opts.toolCalls;
|
|
27
|
-
this.#userUtterance = opts.userUtterance;
|
|
28
|
-
this.#agentUtterance = opts.agentUtterance;
|
|
29
|
-
this.#error = opts.error;
|
|
30
|
-
this.#voiceIO = opts.voiceIO;
|
|
31
|
-
this.#batch = opts.batch;
|
|
32
|
-
}
|
|
33
|
-
/** Single entry point for all server→client session events. */
|
|
34
|
-
event(e) {
|
|
35
|
-
switch (e.type) {
|
|
36
|
-
case "speech_started":
|
|
37
|
-
this.#userUtterance.value = "";
|
|
38
|
-
break;
|
|
39
|
-
case "speech_stopped": break;
|
|
40
|
-
case "transcript":
|
|
41
|
-
this.#userUtterance.value = e.text;
|
|
42
|
-
break;
|
|
43
|
-
case "turn":
|
|
44
|
-
this.#generation++;
|
|
45
|
-
this.#deltaAccum = "";
|
|
46
|
-
this.#batch(() => {
|
|
47
|
-
this.#userUtterance.value = null;
|
|
48
|
-
this.#messages.value = [...this.#messages.value, {
|
|
49
|
-
role: "user",
|
|
50
|
-
content: e.text
|
|
51
|
-
}];
|
|
52
|
-
this.#state.value = "thinking";
|
|
53
|
-
});
|
|
54
|
-
break;
|
|
55
|
-
case "chat_delta":
|
|
56
|
-
this.#deltaAccum += (this.#deltaAccum ? " " : "") + e.text;
|
|
57
|
-
this.#agentUtterance.value = this.#deltaAccum;
|
|
58
|
-
break;
|
|
59
|
-
case "chat":
|
|
60
|
-
this.#deltaAccum = "";
|
|
61
|
-
this.#batch(() => {
|
|
62
|
-
this.#agentUtterance.value = null;
|
|
63
|
-
this.#messages.value = [...this.#messages.value, {
|
|
64
|
-
role: "assistant",
|
|
65
|
-
content: e.text
|
|
66
|
-
}];
|
|
67
|
-
});
|
|
68
|
-
break;
|
|
69
|
-
case "tool_call_start":
|
|
70
|
-
this.#toolCalls.value = [...this.#toolCalls.value, {
|
|
71
|
-
toolCallId: e.toolCallId,
|
|
72
|
-
toolName: e.toolName,
|
|
73
|
-
args: e.args,
|
|
74
|
-
status: "pending",
|
|
75
|
-
afterMessageIndex: this.#messages.value.length - 1
|
|
76
|
-
}];
|
|
77
|
-
break;
|
|
78
|
-
case "tool_call_done": {
|
|
79
|
-
const tcs = this.#toolCalls.value;
|
|
80
|
-
const idx = tcs.findIndex((tc) => tc.toolCallId === e.toolCallId);
|
|
81
|
-
if (idx !== -1) {
|
|
82
|
-
const updated = [...tcs];
|
|
83
|
-
const existing = updated[idx];
|
|
84
|
-
if (existing) updated[idx] = {
|
|
85
|
-
...existing,
|
|
86
|
-
status: "done",
|
|
87
|
-
result: e.result
|
|
88
|
-
};
|
|
89
|
-
this.#toolCalls.value = updated;
|
|
90
|
-
}
|
|
91
|
-
break;
|
|
92
|
-
}
|
|
93
|
-
case "tts_done":
|
|
94
|
-
this.#state.value = "listening";
|
|
95
|
-
break;
|
|
96
|
-
case "cancelled":
|
|
97
|
-
this.#generation++;
|
|
98
|
-
this.#voiceIO()?.flush();
|
|
99
|
-
this.#batch(() => {
|
|
100
|
-
this.#userUtterance.value = null;
|
|
101
|
-
this.#agentUtterance.value = null;
|
|
102
|
-
this.#state.value = "listening";
|
|
103
|
-
});
|
|
104
|
-
break;
|
|
105
|
-
case "reset":
|
|
106
|
-
this.#generation++;
|
|
107
|
-
this.#voiceIO()?.flush();
|
|
108
|
-
this.#batch(() => {
|
|
109
|
-
this.#messages.value = [];
|
|
110
|
-
this.#toolCalls.value = [];
|
|
111
|
-
this.#userUtterance.value = null;
|
|
112
|
-
this.#agentUtterance.value = null;
|
|
113
|
-
this.#error.value = null;
|
|
114
|
-
this.#state.value = "listening";
|
|
115
|
-
});
|
|
116
|
-
break;
|
|
117
|
-
case "error":
|
|
118
|
-
console.error("Agent error:", e.message);
|
|
119
|
-
this.#batch(() => {
|
|
120
|
-
this.#error.value = {
|
|
121
|
-
code: e.code,
|
|
122
|
-
message: e.message
|
|
123
|
-
};
|
|
124
|
-
this.#state.value = "error";
|
|
125
|
-
});
|
|
126
|
-
break;
|
|
127
|
-
default: break;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
/** Enqueue a PCM16 audio chunk for playback. Transitions state to `"speaking"` on the first chunk. */
|
|
131
|
-
playAudioChunk(chunk) {
|
|
132
|
-
if (this.#state.value === "error") return;
|
|
133
|
-
if (this.#state.value !== "speaking") this.#state.value = "speaking";
|
|
134
|
-
if (chunk.buffer instanceof ArrayBuffer) this.#voiceIO()?.enqueue(chunk.buffer);
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* Signal that the server has finished sending audio for this turn.
|
|
138
|
-
* Waits for the audio queue to drain, then transitions state to `"listening"`.
|
|
139
|
-
* Uses the `#generation` counter to discard stale completions from interrupted turns.
|
|
140
|
-
*/
|
|
141
|
-
playAudioDone() {
|
|
142
|
-
const gen = this.#generation;
|
|
143
|
-
const io = this.#voiceIO();
|
|
144
|
-
if (io) io.done().then(() => {
|
|
145
|
-
if (this.#generation !== gen) return;
|
|
146
|
-
this.#state.value = "listening";
|
|
147
|
-
}).catch((err) => {
|
|
148
|
-
console.warn("Audio playback done failed:", err);
|
|
149
|
-
});
|
|
150
|
-
else this.#state.value = "listening";
|
|
151
|
-
}
|
|
152
|
-
/**
|
|
153
|
-
* Dispatch an incoming WebSocket message (text or binary).
|
|
154
|
-
*
|
|
155
|
-
* Returns the parsed config if the message is a `config` message,
|
|
156
|
-
* otherwise `null`.
|
|
157
|
-
*/
|
|
158
|
-
handleMessage(data) {
|
|
159
|
-
if (data instanceof ArrayBuffer) {
|
|
160
|
-
this.playAudioChunk(new Uint8Array(data));
|
|
161
|
-
return null;
|
|
162
|
-
}
|
|
163
|
-
let msg;
|
|
164
|
-
try {
|
|
165
|
-
const parsed = ServerMessageSchema.safeParse(JSON.parse(data));
|
|
166
|
-
if (!parsed.success) {
|
|
167
|
-
console.warn("Ignoring invalid server message:", parsed.error.message);
|
|
168
|
-
return null;
|
|
169
|
-
}
|
|
170
|
-
msg = parsed.data;
|
|
171
|
-
} catch {
|
|
172
|
-
return null;
|
|
173
|
-
}
|
|
174
|
-
if (msg.type === "config") {
|
|
175
|
-
const { type: _, sessionId, ...config } = msg;
|
|
176
|
-
const parsed = ReadyConfigSchema.safeParse(config);
|
|
177
|
-
if (!parsed.success) {
|
|
178
|
-
console.warn("Unsupported server config:", parsed.error.message);
|
|
179
|
-
return null;
|
|
180
|
-
}
|
|
181
|
-
return sessionId ? {
|
|
182
|
-
...parsed.data,
|
|
183
|
-
sessionId
|
|
184
|
-
} : parsed.data;
|
|
185
|
-
}
|
|
186
|
-
if (msg.type === "audio_done") {
|
|
187
|
-
this.playAudioDone();
|
|
188
|
-
return null;
|
|
189
|
-
}
|
|
190
|
-
this.event(msg);
|
|
191
|
-
return null;
|
|
192
|
-
}
|
|
193
|
-
};
|
|
194
|
-
//#endregion
|
|
195
|
-
//#region session.ts
|
|
196
|
-
const WS_OPEN = 1;
|
|
197
|
-
/**
|
|
198
|
-
* Initialize audio capture and playback after the server sends a ready config.
|
|
199
|
-
*
|
|
200
|
-
* Lifecycle: dynamically import audio modules → request microphone access →
|
|
201
|
-
* register AudioWorklet processors → create a `VoiceIO` instance → send
|
|
202
|
-
* `audio_ready` to the server → transition state to `"listening"`.
|
|
203
|
-
*
|
|
204
|
-
* Uses the connection `generation` counter to detect if `connect()` was called
|
|
205
|
-
* while awaiting async operations; if so, the stale VoiceIO is closed immediately
|
|
206
|
-
* to prevent it from being assigned to a newer connection.
|
|
207
|
-
*
|
|
208
|
-
* On failure (e.g. microphone permission denied, WebSocket closed mid-setup),
|
|
209
|
-
* sets the error state and transitions to `"error"`.
|
|
210
|
-
*
|
|
211
|
-
* @param conn - The shared mutable connection state (WebSocket, VoiceIO, generation).
|
|
212
|
-
* @param msg - The `ReadyConfig` from the server containing audio sample rates.
|
|
213
|
-
* @param deps.send - Send a typed client message over the WebSocket.
|
|
214
|
-
* @param deps.sendBinary - Send raw binary audio data over the WebSocket.
|
|
215
|
-
* @param deps.state - Reactive state signal for the agent's current state.
|
|
216
|
-
* @param deps.error - Reactive signal for session errors.
|
|
217
|
-
* @param deps.batch - Batching function for grouping reactive updates.
|
|
218
|
-
*/
|
|
219
|
-
async function initAudioCapture(conn, msg, deps) {
|
|
220
|
-
if (conn.audioSetupInFlight) return;
|
|
221
|
-
conn.audioSetupInFlight = true;
|
|
222
|
-
const gen = conn.generation;
|
|
223
|
-
try {
|
|
224
|
-
const [{ createVoiceIO }, captureWorklet, playbackWorklet] = await Promise.all([
|
|
225
|
-
import("./audio.js"),
|
|
226
|
-
import("./worklets/capture-processor.js").then((m) => m.default),
|
|
227
|
-
import("./worklets/playback-processor.js").then((m) => m.default)
|
|
228
|
-
]);
|
|
229
|
-
const io = await createVoiceIO({
|
|
230
|
-
sttSampleRate: msg.sampleRate,
|
|
231
|
-
ttsSampleRate: msg.ttsSampleRate,
|
|
232
|
-
captureWorkletSrc: captureWorklet,
|
|
233
|
-
playbackWorkletSrc: playbackWorklet,
|
|
234
|
-
onMicData: (pcm16) => {
|
|
235
|
-
try {
|
|
236
|
-
deps.sendBinary(pcm16);
|
|
237
|
-
} catch {
|
|
238
|
-
console.debug("[aai-ui] sendBinary dropped: connection closed");
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
});
|
|
242
|
-
if (conn.generation !== gen || !conn.ws || conn.ws.readyState !== WS_OPEN) {
|
|
243
|
-
io.close();
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
conn.voiceIO = io;
|
|
247
|
-
deps.send({ type: "audio_ready" });
|
|
248
|
-
deps.state.value = "listening";
|
|
249
|
-
} catch (err) {
|
|
250
|
-
if (conn.generation !== gen || !conn.ws || conn.ws.readyState !== WS_OPEN) return;
|
|
251
|
-
deps.batch(() => {
|
|
252
|
-
deps.error.value = {
|
|
253
|
-
code: "audio",
|
|
254
|
-
message: `Microphone access failed: ${errorMessage(err)}`
|
|
255
|
-
};
|
|
256
|
-
deps.state.value = "error";
|
|
257
|
-
});
|
|
258
|
-
} finally {
|
|
259
|
-
conn.audioSetupInFlight = false;
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
function buildWsUrl(platformUrl, resume, sessionId) {
|
|
263
|
-
const wsUrl = new URL("websocket", platformUrl.endsWith("/") ? platformUrl : `${platformUrl}/`);
|
|
264
|
-
wsUrl.protocol = wsUrl.protocol === "https:" ? "wss:" : "ws:";
|
|
265
|
-
if (sessionId) wsUrl.searchParams.set("sessionId", sessionId);
|
|
266
|
-
else if (resume) wsUrl.searchParams.set("resume", "1");
|
|
267
|
-
return wsUrl;
|
|
268
|
-
}
|
|
269
|
-
/**
|
|
270
|
-
* Create a voice session that connects to an AAI server via WebSocket.
|
|
271
|
-
*
|
|
272
|
-
* Uses plain JSON text frames and binary audio frames for communication.
|
|
273
|
-
*
|
|
274
|
-
* @param options - Session configuration including the platform server URL.
|
|
275
|
-
* @returns A {@link VoiceSession} handle for controlling the session.
|
|
276
|
-
*
|
|
277
|
-
* @public
|
|
278
|
-
*/
|
|
279
|
-
function createVoiceSession(options) {
|
|
280
|
-
const WS = options.WebSocket ?? WebSocket;
|
|
281
|
-
const reactive = options.reactiveFactory ?? ((initial) => ({ value: initial }));
|
|
282
|
-
const batchFn = options.batch ?? ((fn) => fn());
|
|
283
|
-
const state = reactive("disconnected");
|
|
284
|
-
const messages = reactive([]);
|
|
285
|
-
const toolCalls = reactive([]);
|
|
286
|
-
const userUtterance = reactive(null);
|
|
287
|
-
const agentUtterance = reactive(null);
|
|
288
|
-
const error = reactive(null);
|
|
289
|
-
const disconnected = reactive(null);
|
|
290
|
-
const conn = {
|
|
291
|
-
ws: null,
|
|
292
|
-
voiceIO: null,
|
|
293
|
-
audioSetupInFlight: false,
|
|
294
|
-
generation: 0
|
|
295
|
-
};
|
|
296
|
-
let connectionController = null;
|
|
297
|
-
let hasConnected = false;
|
|
298
|
-
function cleanupAudio() {
|
|
299
|
-
conn.audioSetupInFlight = false;
|
|
300
|
-
conn.voiceIO?.close();
|
|
301
|
-
conn.voiceIO = null;
|
|
302
|
-
}
|
|
303
|
-
function resetState() {
|
|
304
|
-
batchFn(() => {
|
|
305
|
-
messages.value = [];
|
|
306
|
-
toolCalls.value = [];
|
|
307
|
-
userUtterance.value = null;
|
|
308
|
-
agentUtterance.value = null;
|
|
309
|
-
error.value = null;
|
|
310
|
-
});
|
|
311
|
-
}
|
|
312
|
-
function send(msg) {
|
|
313
|
-
if (conn.ws && conn.ws.readyState === WS_OPEN) conn.ws.send(JSON.stringify(msg));
|
|
314
|
-
}
|
|
315
|
-
function sendBinary(data) {
|
|
316
|
-
if (conn.ws && conn.ws.readyState === WS_OPEN) conn.ws.send(data);
|
|
317
|
-
}
|
|
318
|
-
const audioDeps = {
|
|
319
|
-
send,
|
|
320
|
-
sendBinary,
|
|
321
|
-
state,
|
|
322
|
-
error,
|
|
323
|
-
batch: batchFn
|
|
324
|
-
};
|
|
325
|
-
function connect(opts) {
|
|
326
|
-
disconnected.value = null;
|
|
327
|
-
state.value = "connecting";
|
|
328
|
-
connectionController?.abort();
|
|
329
|
-
cleanupAudio();
|
|
330
|
-
conn.ws?.close();
|
|
331
|
-
conn.ws = null;
|
|
332
|
-
conn.generation++;
|
|
333
|
-
const controller = new AbortController();
|
|
334
|
-
connectionController = controller;
|
|
335
|
-
const { signal: sig } = controller;
|
|
336
|
-
if (opts?.signal) opts.signal.addEventListener("abort", () => disconnect(), { signal: sig });
|
|
337
|
-
const resumeId = !hasConnected ? options.resumeSessionId : void 0;
|
|
338
|
-
const socket = new WS(buildWsUrl(options.platformUrl, hasConnected, resumeId).toString());
|
|
339
|
-
socket.binaryType = "arraybuffer";
|
|
340
|
-
conn.ws = socket;
|
|
341
|
-
const handler = new ClientHandler({
|
|
342
|
-
state,
|
|
343
|
-
messages,
|
|
344
|
-
toolCalls,
|
|
345
|
-
userUtterance,
|
|
346
|
-
agentUtterance,
|
|
347
|
-
error,
|
|
348
|
-
voiceIO: () => conn.voiceIO,
|
|
349
|
-
batch: batchFn
|
|
350
|
-
});
|
|
351
|
-
socket.addEventListener("open", () => {
|
|
352
|
-
state.value = "ready";
|
|
353
|
-
}, { signal: sig });
|
|
354
|
-
socket.addEventListener("message", (event) => {
|
|
355
|
-
const config = handler.handleMessage(event.data);
|
|
356
|
-
if (config) {
|
|
357
|
-
if (config.sessionId) options.onSessionId?.(config.sessionId);
|
|
358
|
-
const isReconnect = hasConnected;
|
|
359
|
-
hasConnected = true;
|
|
360
|
-
initAudioCapture(conn, config, audioDeps).catch((err) => {
|
|
361
|
-
audioDeps.batch(() => {
|
|
362
|
-
audioDeps.error.value = {
|
|
363
|
-
code: "audio",
|
|
364
|
-
message: `Audio capture failed: ${errorMessage(err)}`
|
|
365
|
-
};
|
|
366
|
-
audioDeps.state.value = "error";
|
|
367
|
-
});
|
|
368
|
-
});
|
|
369
|
-
if (isReconnect && messages.value.length > 0) send({
|
|
370
|
-
type: "history",
|
|
371
|
-
messages: messages.value.map((m) => ({
|
|
372
|
-
role: m.role,
|
|
373
|
-
content: m.content
|
|
374
|
-
}))
|
|
375
|
-
});
|
|
376
|
-
}
|
|
377
|
-
}, { signal: sig });
|
|
378
|
-
socket.addEventListener("close", () => {
|
|
379
|
-
if (sig.aborted) return;
|
|
380
|
-
controller.abort();
|
|
381
|
-
disconnected.value = { intentional: false };
|
|
382
|
-
cleanupAudio();
|
|
383
|
-
state.value = "disconnected";
|
|
384
|
-
}, { signal: sig });
|
|
385
|
-
}
|
|
386
|
-
function cancel() {
|
|
387
|
-
conn.voiceIO?.flush();
|
|
388
|
-
state.value = "listening";
|
|
389
|
-
send({ type: "cancel" });
|
|
390
|
-
}
|
|
391
|
-
function reset() {
|
|
392
|
-
conn.voiceIO?.flush();
|
|
393
|
-
if (conn.ws && conn.ws.readyState === WS_OPEN) {
|
|
394
|
-
send({ type: "reset" });
|
|
395
|
-
return;
|
|
396
|
-
}
|
|
397
|
-
resetState();
|
|
398
|
-
disconnect();
|
|
399
|
-
connect();
|
|
400
|
-
}
|
|
401
|
-
function disconnect() {
|
|
402
|
-
connectionController?.abort();
|
|
403
|
-
connectionController = null;
|
|
404
|
-
cleanupAudio();
|
|
405
|
-
conn.ws?.close();
|
|
406
|
-
conn.ws = null;
|
|
407
|
-
state.value = "disconnected";
|
|
408
|
-
disconnected.value = { intentional: true };
|
|
409
|
-
}
|
|
410
|
-
return {
|
|
411
|
-
state,
|
|
412
|
-
messages,
|
|
413
|
-
toolCalls,
|
|
414
|
-
userUtterance,
|
|
415
|
-
agentUtterance,
|
|
416
|
-
error,
|
|
417
|
-
disconnected,
|
|
418
|
-
connect,
|
|
419
|
-
cancel,
|
|
420
|
-
resetState,
|
|
421
|
-
reset,
|
|
422
|
-
disconnect,
|
|
423
|
-
[Symbol.dispose]() {
|
|
424
|
-
disconnect();
|
|
425
|
-
}
|
|
426
|
-
};
|
|
427
|
-
}
|
|
428
|
-
//#endregion
|
|
429
|
-
export { ClientHandler as n, createVoiceSession as t };
|
package/dist/session.d.ts
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import type { AgentState, ChatMessage, Reactive, SessionError, ToolCallInfo, VoiceSessionOptions } from "./types.ts";
|
|
2
|
-
export { ClientHandler } from "./client-handler.ts";
|
|
3
|
-
export type { AgentState, ChatMessage, Reactive, SessionError, SessionErrorCode, ToolCallInfo, VoiceSessionOptions, WebSocketConstructor, } from "./types.ts";
|
|
4
|
-
/**
|
|
5
|
-
* A reactive voice session that manages WebSocket communication,
|
|
6
|
-
* audio capture/playback, and agent state transitions.
|
|
7
|
-
*
|
|
8
|
-
* Uses plain JSON text frames and binary audio frames for communication
|
|
9
|
-
* and native WebSocket for the connection.
|
|
10
|
-
*
|
|
11
|
-
* Implements `Disposable` for resource cleanup via `using`.
|
|
12
|
-
*
|
|
13
|
-
* @public
|
|
14
|
-
*/
|
|
15
|
-
export type VoiceSession = {
|
|
16
|
-
/** Current agent state (connecting, listening, thinking, etc.). */
|
|
17
|
-
readonly state: Reactive<AgentState>;
|
|
18
|
-
/** Chat message history for the session. */
|
|
19
|
-
readonly messages: Reactive<ChatMessage[]>;
|
|
20
|
-
/** Active tool calls for the current turn. */
|
|
21
|
-
readonly toolCalls: Reactive<ToolCallInfo[]>;
|
|
22
|
-
/**
|
|
23
|
-
* Live user utterance from STT/VAD.
|
|
24
|
-
* `null` = not speaking, `""` = speech detected but no text yet,
|
|
25
|
-
* non-empty string = partial/final transcript text.
|
|
26
|
-
*/
|
|
27
|
-
readonly userUtterance: Reactive<string | null>;
|
|
28
|
-
/**
|
|
29
|
-
* Streaming agent response text.
|
|
30
|
-
* `null` = not speaking, non-empty string = accumulated delta text.
|
|
31
|
-
* Cleared when the final `chat` message arrives.
|
|
32
|
-
*/
|
|
33
|
-
readonly agentUtterance: Reactive<string | null>;
|
|
34
|
-
/** Current session error, or `null` if no error. */
|
|
35
|
-
readonly error: Reactive<SessionError | null>;
|
|
36
|
-
/** Disconnection info, or `null` if connected. */
|
|
37
|
-
readonly disconnected: Reactive<{
|
|
38
|
-
intentional: boolean;
|
|
39
|
-
} | null>;
|
|
40
|
-
/**
|
|
41
|
-
* Open a WebSocket connection to the server and begin audio capture.
|
|
42
|
-
*
|
|
43
|
-
* @param options - Optional connection options. `signal` is an AbortSignal that, when aborted, disconnects the session.
|
|
44
|
-
*/
|
|
45
|
-
connect(options?: {
|
|
46
|
-
signal?: AbortSignal;
|
|
47
|
-
}): void;
|
|
48
|
-
/** Cancel the current agent turn and discard in-flight TTS audio. */
|
|
49
|
-
cancel(): void;
|
|
50
|
-
/** Clear messages, transcript, and error state without disconnecting. */
|
|
51
|
-
resetState(): void;
|
|
52
|
-
/** Reset the session: clear state and reconnect. */
|
|
53
|
-
reset(): void;
|
|
54
|
-
/** Close the WebSocket and release all audio resources. */
|
|
55
|
-
disconnect(): void;
|
|
56
|
-
/** Alias for `disconnect` for use with `using`. */
|
|
57
|
-
[Symbol.dispose](): void;
|
|
58
|
-
};
|
|
59
|
-
/**
|
|
60
|
-
* Create a voice session that connects to an AAI server via WebSocket.
|
|
61
|
-
*
|
|
62
|
-
* Uses plain JSON text frames and binary audio frames for communication.
|
|
63
|
-
*
|
|
64
|
-
* @param options - Session configuration including the platform server URL.
|
|
65
|
-
* @returns A {@link VoiceSession} handle for controlling the session.
|
|
66
|
-
*
|
|
67
|
-
* @public
|
|
68
|
-
*/
|
|
69
|
-
export declare function createVoiceSession(options: VoiceSessionOptions): VoiceSession;
|
package/dist/session.js
DELETED
package/dist/signals.d.ts
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import { type Signal } from "@preact/signals";
|
|
2
|
-
import type { ComponentChildren, JSX, RefObject } from "preact";
|
|
3
|
-
import type { VoiceSession } from "./session.ts";
|
|
4
|
-
import type { ToolCallInfo } from "./types.ts";
|
|
5
|
-
/**
|
|
6
|
-
* Reactive session controls wrapping a {@link VoiceSession} with Preact signals.
|
|
7
|
-
*
|
|
8
|
-
* Components access reactive data via `session` (e.g. `session.state`,
|
|
9
|
-
* `session.messages`). UI-only state (`started`, `running`) and actions
|
|
10
|
-
* (`start`, `toggle`, `reset`) live directly on this object.
|
|
11
|
-
*
|
|
12
|
-
* @public
|
|
13
|
-
*/
|
|
14
|
-
export type SessionSignals = {
|
|
15
|
-
/** The underlying voice session — all reactive data lives here. */
|
|
16
|
-
session: VoiceSession;
|
|
17
|
-
/** Whether the session has been started by the user. */
|
|
18
|
-
started: Signal<boolean>;
|
|
19
|
-
/** Whether the session is currently running (connected or connecting). */
|
|
20
|
-
running: Signal<boolean>;
|
|
21
|
-
/** Dispose the reactive effect that tracks error state. */
|
|
22
|
-
dispose(): void;
|
|
23
|
-
/** Start the session for the first time (sets `started` and `running`). */
|
|
24
|
-
start(): void;
|
|
25
|
-
/** Toggle between connected and disconnected states. */
|
|
26
|
-
toggle(): void;
|
|
27
|
-
/** Reset the session: clear state and reconnect. */
|
|
28
|
-
reset(): void;
|
|
29
|
-
/** Alias for `dispose` for use with `using`. */
|
|
30
|
-
[Symbol.dispose](): void;
|
|
31
|
-
};
|
|
32
|
-
/**
|
|
33
|
-
* Wrap a {@link VoiceSession} in Preact signals for reactive UI binding.
|
|
34
|
-
*
|
|
35
|
-
* Creates higher-level controls (start, toggle, reset) on top of the raw
|
|
36
|
-
* session, and automatically sets `running` to `false` when the session
|
|
37
|
-
* enters an error state.
|
|
38
|
-
*
|
|
39
|
-
* @param session - The voice session to wrap.
|
|
40
|
-
* @returns A {@link SessionSignals} object for use in Preact components.
|
|
41
|
-
*
|
|
42
|
-
* @public
|
|
43
|
-
*/
|
|
44
|
-
export declare function createSessionControls(session: VoiceSession): SessionSignals;
|
|
45
|
-
/**
|
|
46
|
-
* Preact context provider that makes session signals available to descendant
|
|
47
|
-
* components via {@link useSession}.
|
|
48
|
-
*
|
|
49
|
-
* @param props - Provider props. `value` is the session signals to provide. `children` are child components that may consume the context.
|
|
50
|
-
* @returns A Preact VNode wrapping children in the session context.
|
|
51
|
-
*
|
|
52
|
-
* @public
|
|
53
|
-
*/
|
|
54
|
-
export declare function SessionProvider({ value, children, }: {
|
|
55
|
-
value: SessionSignals;
|
|
56
|
-
children?: ComponentChildren;
|
|
57
|
-
}): JSX.Element;
|
|
58
|
-
/**
|
|
59
|
-
* Hook to access session signals from within a {@link SessionProvider}.
|
|
60
|
-
*
|
|
61
|
-
* @returns The {@link SessionSignals} from the nearest provider.
|
|
62
|
-
* @throws If called outside of a `SessionProvider`.
|
|
63
|
-
*
|
|
64
|
-
* @public
|
|
65
|
-
*/
|
|
66
|
-
export declare function useSession(): SessionSignals;
|
|
67
|
-
/**
|
|
68
|
-
* Hook that fires a callback exactly once for each newly completed tool call.
|
|
69
|
-
*
|
|
70
|
-
* Handles deduplication internally — safe to use with `useState` setters
|
|
71
|
-
* without worrying about duplicates. The `result` argument is the parsed
|
|
72
|
-
* JSON result from the tool (or the raw string if parsing fails).
|
|
73
|
-
*
|
|
74
|
-
* Automatically resets tracking when the session is reset (toolCalls cleared).
|
|
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
|
-
*
|
|
95
|
-
* @param callback - Called once per completed tool call with the tool name,
|
|
96
|
-
* parsed result, and full {@link ToolCallInfo}.
|
|
97
|
-
*
|
|
98
|
-
* @public
|
|
99
|
-
*/
|
|
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
|
-
* Auto-scroll a container to the bottom when messages, tool calls,
|
|
115
|
-
* or utterances change. Returns a ref to attach to a sentinel `<div>`
|
|
116
|
-
* at the bottom of the scrollable area.
|
|
117
|
-
*
|
|
118
|
-
* @public
|
|
119
|
-
*/
|
|
120
|
-
export declare function useAutoScroll(): RefObject<HTMLDivElement>;
|