@alexkroman1/aai 1.4.5 → 1.5.1

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 (86) hide show
  1. package/.turbo/turbo-build.log +10 -10
  2. package/CHANGELOG.md +19 -0
  3. package/dist/{_internal-types-3p3OJZPb.js → _internal-types-DFL07G3f.js} +2 -0
  4. package/dist/assemblyai-C969QGi4.js +35 -0
  5. package/dist/cartesia-BfQPOQ7Y.js +37 -0
  6. package/dist/host/_pipeline-test-fakes.d.ts +3 -1
  7. package/dist/host/providers/stt/deepgram.d.ts +28 -0
  8. package/dist/host/providers/tts/cartesia.d.ts +1 -1
  9. package/dist/host/providers/tts/rime.d.ts +44 -0
  10. package/dist/host/runtime-barrel.d.ts +4 -2
  11. package/dist/host/runtime-barrel.js +1434 -1209
  12. package/dist/host/runtime.d.ts +2 -2
  13. package/dist/host/s2s.d.ts +16 -16
  14. package/dist/host/session-core.d.ts +37 -0
  15. package/dist/host/transports/pipeline-transport.d.ts +48 -0
  16. package/dist/host/transports/s2s-transport.d.ts +19 -0
  17. package/dist/host/transports/types.d.ts +45 -0
  18. package/dist/host/ws-handler.d.ts +14 -10
  19. package/dist/sdk/_internal-types.d.ts +2 -0
  20. package/dist/sdk/manifest-barrel.js +1 -1
  21. package/dist/sdk/protocol.d.ts +6 -5
  22. package/dist/sdk/providers/llm-barrel.js +1 -1
  23. package/dist/sdk/providers/stt/deepgram.d.ts +35 -0
  24. package/dist/sdk/providers/stt-barrel.d.ts +1 -0
  25. package/dist/sdk/providers/stt-barrel.js +2 -2
  26. package/dist/sdk/providers/tts/cartesia.d.ts +12 -4
  27. package/dist/sdk/providers/tts/rime.d.ts +42 -0
  28. package/dist/sdk/providers/tts-barrel.d.ts +1 -0
  29. package/dist/sdk/providers/tts-barrel.js +2 -2
  30. package/host/_pipeline-test-fakes.ts +6 -3
  31. package/host/_test-utils.ts +209 -128
  32. package/host/builtin-tools.ts +1 -0
  33. package/host/cleanup.test.ts +25 -298
  34. package/host/integration/pipeline-reference.integration.test.ts +30 -35
  35. package/host/providers/resolve.ts +10 -2
  36. package/host/providers/stt/deepgram.test.ts +229 -0
  37. package/host/providers/stt/deepgram.ts +172 -0
  38. package/host/providers/tts/cartesia.ts +7 -3
  39. package/host/providers/tts/rime.test.ts +251 -0
  40. package/host/providers/tts/rime.ts +322 -0
  41. package/host/runtime-barrel.ts +4 -2
  42. package/host/runtime.test.ts +16 -47
  43. package/host/runtime.ts +131 -23
  44. package/host/s2s.test.ts +122 -131
  45. package/host/s2s.ts +44 -52
  46. package/host/session-core.test.ts +257 -0
  47. package/host/session-core.ts +262 -0
  48. package/host/to-vercel-tools.test.ts +9 -1
  49. package/host/transports/pipeline-transport.test.ts +653 -0
  50. package/host/transports/pipeline-transport.ts +532 -0
  51. package/host/{fixture-replay.test.ts → transports/s2s-transport-fixtures.test.ts} +76 -106
  52. package/host/transports/s2s-transport.test.ts +56 -0
  53. package/host/transports/s2s-transport.ts +116 -0
  54. package/host/transports/types.test.ts +22 -0
  55. package/host/transports/types.ts +51 -0
  56. package/host/ws-handler.test.ts +324 -242
  57. package/host/ws-handler.ts +56 -59
  58. package/package.json +2 -1
  59. package/sdk/__snapshots__/exports.test.ts.snap +3 -3
  60. package/sdk/__snapshots__/schema-shapes.test.ts.snap +1 -0
  61. package/sdk/_internal-types.ts +3 -0
  62. package/sdk/protocol-compat.test.ts +8 -0
  63. package/sdk/protocol.ts +6 -5
  64. package/sdk/providers/stt/deepgram.ts +43 -0
  65. package/sdk/providers/stt-barrel.ts +2 -0
  66. package/sdk/providers/tts/cartesia.ts +15 -5
  67. package/sdk/providers/tts/rime.ts +52 -0
  68. package/sdk/providers/tts-barrel.ts +2 -0
  69. package/sdk/schema-alignment.test.ts +18 -6
  70. package/dist/assemblyai-Cxg9eobY.js +0 -18
  71. package/dist/cartesia-DwDk2tEu.js +0 -10
  72. package/dist/host/pipeline-session-ctx.d.ts +0 -24
  73. package/dist/host/pipeline-session.d.ts +0 -52
  74. package/dist/host/session-ctx.d.ts +0 -73
  75. package/dist/host/session.d.ts +0 -62
  76. package/host/pipeline-session-ctx.test.ts +0 -31
  77. package/host/pipeline-session-ctx.ts +0 -36
  78. package/host/pipeline-session.test.ts +0 -672
  79. package/host/pipeline-session.ts +0 -533
  80. package/host/s2s-fixtures.test.ts +0 -237
  81. package/host/session-ctx.test.ts +0 -387
  82. package/host/session-ctx.ts +0 -134
  83. package/host/session-fixture-replay.test.ts +0 -128
  84. package/host/session.test.ts +0 -634
  85. package/host/session.ts +0 -412
  86. /package/dist/{anthropic-BrUCPKUc.js → anthropic-CcLZygAr.js} +0 -0
package/host/s2s.test.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, test, vi } from "vitest";
2
2
  import { silentLogger } from "./_test-utils.ts";
3
- import type { S2sWebSocket } from "./s2s.ts";
3
+ import type { S2sCallbacks, S2sWebSocket } from "./s2s.ts";
4
4
  import { connectS2s } from "./s2s.ts";
5
5
 
6
6
  /** EventTarget-based WebSocket stub (standard API, no `.on()` adapter needed). */
@@ -37,6 +37,24 @@ function createWebSocketStub() {
37
37
 
38
38
  const s2sConfig = { wssUrl: "wss://fake", inputSampleRate: 16_000, outputSampleRate: 16_000 };
39
39
 
40
+ function makeMockCallbacks(): S2sCallbacks {
41
+ return {
42
+ onSessionReady: vi.fn(),
43
+ onReplyStarted: vi.fn(),
44
+ onReplyDone: vi.fn(),
45
+ onCancelled: vi.fn(),
46
+ onAudio: vi.fn(),
47
+ onUserTranscript: vi.fn(),
48
+ onAgentTranscript: vi.fn(),
49
+ onToolCall: vi.fn(),
50
+ onSpeechStarted: vi.fn(),
51
+ onSpeechStopped: vi.fn(),
52
+ onSessionExpired: vi.fn(),
53
+ onError: vi.fn(),
54
+ onClose: vi.fn(),
55
+ };
56
+ }
57
+
40
58
  function createTestS2s() {
41
59
  const raw = createWebSocketStub();
42
60
  const createWebSocket = () => {
@@ -49,12 +67,13 @@ function createTestS2s() {
49
67
  return { raw, createWebSocket, logger: { ...silentLogger } };
50
68
  }
51
69
 
52
- async function setupHandle() {
70
+ async function setupHandle(callbacks?: S2sCallbacks) {
53
71
  const { raw, createWebSocket, logger } = createTestS2s();
54
72
  const handle = await connectS2s({
55
73
  apiKey: "test-key",
56
74
  config: s2sConfig,
57
75
  createWebSocket,
76
+ callbacks: callbacks ?? makeMockCallbacks(),
58
77
  logger,
59
78
  });
60
79
  return { raw, handle, logger };
@@ -89,6 +108,7 @@ describe("connectS2s", () => {
89
108
  apiKey: "test-key",
90
109
  config: s2sConfig,
91
110
  createWebSocket,
111
+ callbacks: makeMockCallbacks(),
92
112
  logger: silentLogger,
93
113
  }),
94
114
  ).rejects.toThrow("connection refused");
@@ -184,10 +204,9 @@ describe("connectS2s", () => {
184
204
 
185
205
  // ─── Message dispatch ──────────────────────────────────────────────────
186
206
 
187
- test("session.ready dispatches 'ready' event", async () => {
188
- const { raw, handle } = await setupHandle();
189
- const onReady = vi.fn();
190
- handle.on("ready", onReady);
207
+ test("session.ready dispatches 'onSessionReady' callback", async () => {
208
+ const callbacks = makeMockCallbacks();
209
+ const { raw } = await setupHandle(callbacks);
191
210
 
192
211
  raw.emit(
193
212
  "message",
@@ -199,51 +218,45 @@ describe("connectS2s", () => {
199
218
  ),
200
219
  );
201
220
 
202
- expect(onReady).toHaveBeenCalledOnce();
203
- expect(onReady.mock.calls[0]?.[0].sessionId).toBe("s123");
221
+ expect(callbacks.onSessionReady).toHaveBeenCalledOnce();
222
+ expect(callbacks.onSessionReady).toHaveBeenCalledWith("s123");
204
223
  });
205
224
 
206
- test("input.speech.started dispatches 'event' with type 'speech_started'", async () => {
207
- const { raw, handle } = await setupHandle();
208
- const handler = vi.fn();
209
- handle.on("event", handler);
225
+ test("input.speech.started dispatches 'onSpeechStarted' callback", async () => {
226
+ const callbacks = makeMockCallbacks();
227
+ const { raw } = await setupHandle(callbacks);
210
228
 
211
229
  raw.emit("message", Buffer.from(JSON.stringify({ type: "input.speech.started" })));
212
230
 
213
- expect(handler).toHaveBeenCalledOnce();
214
- expect(handler.mock.calls[0]?.[0]).toEqual({ type: "speech_started" });
231
+ expect(callbacks.onSpeechStarted).toHaveBeenCalledOnce();
215
232
  });
216
233
 
217
- test("input.speech.stopped dispatches 'event' with type 'speech_stopped'", async () => {
218
- const { raw, handle } = await setupHandle();
219
- const handler = vi.fn();
220
- handle.on("event", handler);
234
+ test("input.speech.stopped dispatches 'onSpeechStopped' callback", async () => {
235
+ const callbacks = makeMockCallbacks();
236
+ const { raw } = await setupHandle(callbacks);
221
237
 
222
238
  // Prime VAD state — speech_stopped is only forwarded after a speech_started.
223
239
  raw.emit("message", Buffer.from(JSON.stringify({ type: "input.speech.started" })));
224
240
  raw.emit("message", Buffer.from(JSON.stringify({ type: "input.speech.stopped" })));
225
241
 
226
- expect(handler).toHaveBeenCalledTimes(2);
227
- expect(handler.mock.calls[0]?.[0]).toEqual({ type: "speech_started" });
228
- expect(handler.mock.calls[1]?.[0]).toEqual({ type: "speech_stopped" });
242
+ expect(callbacks.onSpeechStarted).toHaveBeenCalledOnce();
243
+ expect(callbacks.onSpeechStopped).toHaveBeenCalledOnce();
229
244
  });
230
245
 
231
246
  test("duplicate input.speech.stopped is suppressed", async () => {
232
- const { raw, handle } = await setupHandle();
233
- const handler = vi.fn();
234
- handle.on("event", handler);
247
+ const callbacks = makeMockCallbacks();
248
+ const { raw } = await setupHandle(callbacks);
235
249
 
236
250
  raw.emit("message", Buffer.from(JSON.stringify({ type: "input.speech.started" })));
237
251
  raw.emit("message", Buffer.from(JSON.stringify({ type: "input.speech.stopped" })));
238
252
  raw.emit("message", Buffer.from(JSON.stringify({ type: "input.speech.stopped" })));
239
253
 
240
- expect(handler.mock.calls.filter((c) => c[0].type === "speech_stopped")).toHaveLength(1);
254
+ expect(callbacks.onSpeechStopped).toHaveBeenCalledOnce();
241
255
  });
242
256
 
243
- test("transcript.user dispatches 'event' with user_transcript", async () => {
244
- const { raw, handle } = await setupHandle();
245
- const handler = vi.fn();
246
- handle.on("event", handler);
257
+ test("transcript.user dispatches 'onUserTranscript' callback", async () => {
258
+ const callbacks = makeMockCallbacks();
259
+ const { raw } = await setupHandle(callbacks);
247
260
 
248
261
  raw.emit(
249
262
  "message",
@@ -256,17 +269,13 @@ describe("connectS2s", () => {
256
269
  ),
257
270
  );
258
271
 
259
- expect(handler).toHaveBeenCalledOnce();
260
- expect(handler.mock.calls[0]?.[0]).toEqual({
261
- type: "user_transcript",
262
- text: "Hello world",
263
- });
272
+ expect(callbacks.onUserTranscript).toHaveBeenCalledOnce();
273
+ expect(callbacks.onUserTranscript).toHaveBeenCalledWith("Hello world");
264
274
  });
265
275
 
266
- test("reply.started dispatches 'replyStarted' event", async () => {
267
- const { raw, handle } = await setupHandle();
268
- const handler = vi.fn();
269
- handle.on("replyStarted", handler);
276
+ test("reply.started dispatches 'onReplyStarted' callback", async () => {
277
+ const callbacks = makeMockCallbacks();
278
+ const { raw } = await setupHandle(callbacks);
270
279
 
271
280
  raw.emit(
272
281
  "message",
@@ -278,14 +287,13 @@ describe("connectS2s", () => {
278
287
  ),
279
288
  );
280
289
 
281
- expect(handler).toHaveBeenCalledOnce();
282
- expect(handler.mock.calls[0]?.[0].replyId).toBe("r1");
290
+ expect(callbacks.onReplyStarted).toHaveBeenCalledOnce();
291
+ expect(callbacks.onReplyStarted).toHaveBeenCalledWith("r1");
283
292
  });
284
293
 
285
- test("transcript.agent dispatches 'event' with agent_transcript", async () => {
286
- const { raw, handle } = await setupHandle();
287
- const handler = vi.fn();
288
- handle.on("event", handler);
294
+ test("transcript.agent dispatches 'onAgentTranscript' callback", async () => {
295
+ const callbacks = makeMockCallbacks();
296
+ const { raw } = await setupHandle(callbacks);
289
297
 
290
298
  raw.emit(
291
299
  "message",
@@ -300,30 +308,25 @@ describe("connectS2s", () => {
300
308
  ),
301
309
  );
302
310
 
303
- expect(handler).toHaveBeenCalledOnce();
304
- const payload = handler.mock.calls[0]?.[0];
305
- expect(payload.type).toBe("agent_transcript");
306
- expect(payload.text).toBe("Full response");
307
- expect(payload._interrupted).toBe(false);
311
+ expect(callbacks.onAgentTranscript).toHaveBeenCalledOnce();
312
+ expect(callbacks.onAgentTranscript).toHaveBeenCalledWith("Full response", false);
308
313
  });
309
314
 
310
- test("transcript.agent defaults _interrupted to false when missing", async () => {
311
- const { raw, handle } = await setupHandle();
312
- const handler = vi.fn();
313
- handle.on("event", handler);
315
+ test("transcript.agent defaults interrupted to false when missing", async () => {
316
+ const callbacks = makeMockCallbacks();
317
+ const { raw } = await setupHandle(callbacks);
314
318
 
315
319
  raw.emit(
316
320
  "message",
317
321
  Buffer.from(JSON.stringify({ type: "transcript.agent", text: "response" })),
318
322
  );
319
323
 
320
- expect(handler.mock.calls[0]?.[0]._interrupted).toBe(false);
324
+ expect(callbacks.onAgentTranscript).toHaveBeenCalledWith("response", false);
321
325
  });
322
326
 
323
- test("transcript.agent with interrupted:true sets _interrupted:true", async () => {
324
- const { raw, handle } = await setupHandle();
325
- const handler = vi.fn();
326
- handle.on("event", handler);
327
+ test("transcript.agent with interrupted:true passes interrupted:true", async () => {
328
+ const callbacks = makeMockCallbacks();
329
+ const { raw } = await setupHandle(callbacks);
327
330
 
328
331
  raw.emit(
329
332
  "message",
@@ -336,13 +339,12 @@ describe("connectS2s", () => {
336
339
  ),
337
340
  );
338
341
 
339
- expect(handler.mock.calls[0]?.[0]._interrupted).toBe(true);
342
+ expect(callbacks.onAgentTranscript).toHaveBeenCalledWith("Interrupted response", true);
340
343
  });
341
344
 
342
- test("tool.call dispatches 'event' with tool_call shape", async () => {
343
- const { raw, handle } = await setupHandle();
344
- const handler = vi.fn();
345
- handle.on("event", handler);
345
+ test("tool.call dispatches 'onToolCall' callback", async () => {
346
+ const callbacks = makeMockCallbacks();
347
+ const { raw } = await setupHandle(callbacks);
346
348
 
347
349
  raw.emit(
348
350
  "message",
@@ -356,18 +358,13 @@ describe("connectS2s", () => {
356
358
  ),
357
359
  );
358
360
 
359
- expect(handler).toHaveBeenCalledOnce();
360
- const payload = handler.mock.calls[0]?.[0];
361
- expect(payload.type).toBe("tool_call");
362
- expect(payload.toolCallId).toBe("c1");
363
- expect(payload.toolName).toBe("web_search");
364
- expect(payload.args).toEqual({ query: "test" });
361
+ expect(callbacks.onToolCall).toHaveBeenCalledOnce();
362
+ expect(callbacks.onToolCall).toHaveBeenCalledWith("c1", "web_search", { query: "test" });
365
363
  });
366
364
 
367
- test("reply.done (non-interrupted) dispatches 'event' with type 'reply_done'", async () => {
368
- const { raw, handle } = await setupHandle();
369
- const handler = vi.fn();
370
- handle.on("event", handler);
365
+ test("reply.done (non-interrupted) dispatches 'onReplyDone' callback", async () => {
366
+ const callbacks = makeMockCallbacks();
367
+ const { raw } = await setupHandle(callbacks);
371
368
 
372
369
  raw.emit(
373
370
  "message",
@@ -379,14 +376,13 @@ describe("connectS2s", () => {
379
376
  ),
380
377
  );
381
378
 
382
- expect(handler).toHaveBeenCalledOnce();
383
- expect(handler.mock.calls[0]?.[0]).toEqual({ type: "reply_done" });
379
+ expect(callbacks.onReplyDone).toHaveBeenCalledOnce();
380
+ expect(callbacks.onCancelled).not.toHaveBeenCalled();
384
381
  });
385
382
 
386
- test("reply.done with status 'interrupted' dispatches 'event' with type 'cancelled'", async () => {
387
- const { raw, handle } = await setupHandle();
388
- const handler = vi.fn();
389
- handle.on("event", handler);
383
+ test("reply.done with status 'interrupted' dispatches 'onCancelled' callback", async () => {
384
+ const callbacks = makeMockCallbacks();
385
+ const { raw } = await setupHandle(callbacks);
390
386
 
391
387
  raw.emit(
392
388
  "message",
@@ -398,22 +394,22 @@ describe("connectS2s", () => {
398
394
  ),
399
395
  );
400
396
 
401
- expect(handler).toHaveBeenCalledOnce();
402
- expect(handler.mock.calls[0]?.[0]).toEqual({ type: "cancelled" });
397
+ expect(callbacks.onCancelled).toHaveBeenCalledOnce();
398
+ expect(callbacks.onReplyDone).not.toHaveBeenCalled();
403
399
  });
404
400
 
405
401
  test("reply.done arrival is logged with sid and status", async () => {
406
402
  const { raw, createWebSocket, logger } = createTestS2s();
407
403
  const infoSpy = vi.fn();
408
404
  logger.info = infoSpy;
409
- const handle = await connectS2s({
405
+ await connectS2s({
410
406
  apiKey: "test-key",
411
407
  config: s2sConfig,
412
408
  createWebSocket,
409
+ callbacks: makeMockCallbacks(),
413
410
  logger,
414
411
  sid: "sess-abc",
415
412
  });
416
- handle.on("event", vi.fn());
417
413
 
418
414
  raw.emit("message", Buffer.from(JSON.stringify({ type: "reply.done", status: "completed" })));
419
415
 
@@ -422,10 +418,9 @@ describe("connectS2s", () => {
422
418
  expect(arrivalCall?.[1]).toEqual({ sid: "sess-abc", status: "completed" });
423
419
  });
424
420
 
425
- test("session.error with session_not_found dispatches 'sessionExpired'", async () => {
426
- const { raw, handle } = await setupHandle();
427
- const handler = vi.fn();
428
- handle.on("sessionExpired", handler);
421
+ test("session.error with session_not_found dispatches 'onSessionExpired' callback", async () => {
422
+ const callbacks = makeMockCallbacks();
423
+ const { raw } = await setupHandle(callbacks);
429
424
 
430
425
  raw.emit(
431
426
  "message",
@@ -438,13 +433,12 @@ describe("connectS2s", () => {
438
433
  ),
439
434
  );
440
435
 
441
- expect(handler).toHaveBeenCalledOnce();
436
+ expect(callbacks.onSessionExpired).toHaveBeenCalledOnce();
442
437
  });
443
438
 
444
- test("session.error with session_forbidden dispatches 'sessionExpired'", async () => {
445
- const { raw, handle } = await setupHandle();
446
- const handler = vi.fn();
447
- handle.on("sessionExpired", handler);
439
+ test("session.error with session_forbidden dispatches 'onSessionExpired' callback", async () => {
440
+ const callbacks = makeMockCallbacks();
441
+ const { raw } = await setupHandle(callbacks);
448
442
 
449
443
  raw.emit(
450
444
  "message",
@@ -457,13 +451,12 @@ describe("connectS2s", () => {
457
451
  ),
458
452
  );
459
453
 
460
- expect(handler).toHaveBeenCalledOnce();
454
+ expect(callbacks.onSessionExpired).toHaveBeenCalledOnce();
461
455
  });
462
456
 
463
- test("session.error with other code dispatches 'error' with Error object", async () => {
464
- const { raw, handle } = await setupHandle();
465
- const handler = vi.fn();
466
- handle.on("error", handler);
457
+ test("session.error with other code dispatches 'onError' callback with Error object", async () => {
458
+ const callbacks = makeMockCallbacks();
459
+ const { raw } = await setupHandle(callbacks);
467
460
 
468
461
  raw.emit(
469
462
  "message",
@@ -476,16 +469,15 @@ describe("connectS2s", () => {
476
469
  ),
477
470
  );
478
471
 
479
- expect(handler).toHaveBeenCalledOnce();
480
- const err = handler.mock.calls[0]?.[0];
472
+ expect(callbacks.onError).toHaveBeenCalledOnce();
473
+ const err = (callbacks.onError as ReturnType<typeof vi.fn>).mock.calls[0]?.[0];
481
474
  expect(err).toBeInstanceOf(Error);
482
475
  expect(err.message).toBe("Too many requests");
483
476
  });
484
477
 
485
- test("bare error dispatches 'error' with Error object", async () => {
486
- const { raw, handle } = await setupHandle();
487
- const handler = vi.fn();
488
- handle.on("error", handler);
478
+ test("bare error dispatches 'onError' callback with Error object", async () => {
479
+ const callbacks = makeMockCallbacks();
480
+ const { raw } = await setupHandle(callbacks);
489
481
 
490
482
  raw.emit(
491
483
  "message",
@@ -497,18 +489,17 @@ describe("connectS2s", () => {
497
489
  ),
498
490
  );
499
491
 
500
- expect(handler).toHaveBeenCalledOnce();
501
- const err = handler.mock.calls[0]?.[0];
492
+ expect(callbacks.onError).toHaveBeenCalledOnce();
493
+ const err = (callbacks.onError as ReturnType<typeof vi.fn>).mock.calls[0]?.[0];
502
494
  expect(err).toBeInstanceOf(Error);
503
495
  expect(err.message).toBe("Bad gateway");
504
496
  });
505
497
 
506
498
  // ─── Audio fast path ───────────────────────────────────────────────────
507
499
 
508
- test("reply.audio dispatches 'audio' with decoded Uint8Array", async () => {
509
- const { raw, handle } = await setupHandle();
510
- const handler = vi.fn();
511
- handle.on("audio", handler);
500
+ test("reply.audio dispatches 'onAudio' callback with decoded Uint8Array", async () => {
501
+ const callbacks = makeMockCallbacks();
502
+ const { raw } = await setupHandle(callbacks);
512
503
 
513
504
  const audioBytes = new Uint8Array([10, 20, 30, 40]);
514
505
  const base64 = Buffer.from(audioBytes).toString("base64");
@@ -523,10 +514,10 @@ describe("connectS2s", () => {
523
514
  ),
524
515
  );
525
516
 
526
- expect(handler).toHaveBeenCalledOnce();
527
- const payload = handler.mock.calls[0]?.[0];
528
- expect(payload.audio).toBeInstanceOf(Uint8Array);
529
- expect(Array.from(payload.audio)).toEqual([10, 20, 30, 40]);
517
+ expect(callbacks.onAudio).toHaveBeenCalledOnce();
518
+ const payload = (callbacks.onAudio as ReturnType<typeof vi.fn>).mock.calls[0]?.[0];
519
+ expect(payload).toBeInstanceOf(Uint8Array);
520
+ expect(Array.from(payload)).toEqual([10, 20, 30, 40]);
530
521
  });
531
522
 
532
523
  // ─── Edge cases ────────────────────────────────────────────────────────
@@ -577,39 +568,39 @@ describe("connectS2s", () => {
577
568
  });
578
569
 
579
570
  test("session.updated is silently ignored (no dispatch)", async () => {
580
- const { raw, handle } = await setupHandle();
581
- const eventHandler = vi.fn();
582
- handle.on("event", eventHandler);
571
+ const callbacks = makeMockCallbacks();
572
+ const { raw } = await setupHandle(callbacks);
583
573
 
584
574
  raw.emit("message", Buffer.from(JSON.stringify({ type: "session.updated" })));
585
575
 
586
- // session.updated is dropped — no event emitted
587
- expect(eventHandler).not.toHaveBeenCalled();
576
+ // session.updated is dropped — no callbacks fired
577
+ expect(callbacks.onSessionReady).not.toHaveBeenCalled();
578
+ expect(callbacks.onReplyStarted).not.toHaveBeenCalled();
579
+ expect(callbacks.onReplyDone).not.toHaveBeenCalled();
580
+ expect(callbacks.onSpeechStarted).not.toHaveBeenCalled();
581
+ expect(callbacks.onSpeechStopped).not.toHaveBeenCalled();
588
582
  });
589
583
 
590
584
  // ─── Close and error events ────────────────────────────────────────────
591
585
 
592
- test("close event dispatches 'close' with code and reason", async () => {
593
- const { raw, handle } = await setupHandle();
594
- const handler = vi.fn();
595
- handle.on("close", handler);
586
+ test("close event dispatches 'onClose' callback with code and reason", async () => {
587
+ const callbacks = makeMockCallbacks();
588
+ const { raw } = await setupHandle(callbacks);
596
589
 
597
590
  raw.emit("close", 1000, "normal");
598
591
 
599
- expect(handler).toHaveBeenCalledOnce();
600
- expect(handler.mock.calls[0]?.[0]).toBe(1000);
601
- expect(handler.mock.calls[0]?.[1]).toBe("normal");
592
+ expect(callbacks.onClose).toHaveBeenCalledOnce();
593
+ expect(callbacks.onClose).toHaveBeenCalledWith(1000, "normal");
602
594
  });
603
595
 
604
- test("error after open dispatches 'error' with Error object", async () => {
605
- const { raw, handle } = await setupHandle();
606
- const handler = vi.fn();
607
- handle.on("error", handler);
596
+ test("error after open dispatches 'onError' callback with Error object", async () => {
597
+ const callbacks = makeMockCallbacks();
598
+ const { raw } = await setupHandle(callbacks);
608
599
 
609
600
  raw.emit("error", new Error("ws transport error"));
610
601
 
611
- expect(handler).toHaveBeenCalledOnce();
612
- const err = handler.mock.calls[0]?.[0];
602
+ expect(callbacks.onError).toHaveBeenCalledOnce();
603
+ const err = (callbacks.onError as ReturnType<typeof vi.fn>).mock.calls[0]?.[0];
613
604
  expect(err).toBeInstanceOf(Error);
614
605
  expect(err.message).toBe("ws transport error");
615
606
  });