@absolutejs/voice 0.0.22-beta.25 → 0.0.22-beta.27

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.
@@ -120,6 +120,12 @@ var serverMessageToAction = (message) => {
120
120
  sessionId: message.sessionId,
121
121
  type: "complete"
122
122
  };
123
+ case "call_lifecycle":
124
+ return {
125
+ event: message.event,
126
+ sessionId: message.sessionId,
127
+ type: "call_lifecycle"
128
+ };
123
129
  case "error":
124
130
  return {
125
131
  message: normalizeErrorMessage(message.message),
@@ -163,7 +169,7 @@ var DEFAULT_SCENARIO_QUERY_PARAM = "scenarioId";
163
169
  var noop = () => {};
164
170
  var noopUnsubscribe = () => noop;
165
171
  var NOOP_CONNECTION = {
166
- start: () => {},
172
+ callControl: noop,
167
173
  close: noop,
168
174
  endTurn: noop,
169
175
  getReadyState: () => WS_CLOSED,
@@ -171,6 +177,7 @@ var NOOP_CONNECTION = {
171
177
  getSessionId: () => "",
172
178
  send: noop,
173
179
  sendAudio: noop,
180
+ start: () => {},
174
181
  subscribe: noopUnsubscribe
175
182
  };
176
183
  var createSessionId = () => crypto.randomUUID();
@@ -192,6 +199,7 @@ var isVoiceServerMessage = (value) => {
192
199
  switch (value.type) {
193
200
  case "audio":
194
201
  case "assistant":
202
+ case "call_lifecycle":
195
203
  case "complete":
196
204
  case "error":
197
205
  case "final":
@@ -332,6 +340,12 @@ var createVoiceConnection = (path, options = {}) => {
332
340
  const endTurn = () => {
333
341
  send({ type: "end_turn" });
334
342
  };
343
+ const callControl = (message) => {
344
+ send({
345
+ ...message,
346
+ type: "call_control"
347
+ });
348
+ };
335
349
  const close = () => {
336
350
  clearTimers();
337
351
  if (state.ws) {
@@ -349,7 +363,7 @@ var createVoiceConnection = (path, options = {}) => {
349
363
  };
350
364
  connect();
351
365
  return {
352
- start,
366
+ callControl,
353
367
  close,
354
368
  endTurn,
355
369
  getReadyState: () => state.ws?.readyState ?? WS_CLOSED,
@@ -357,6 +371,7 @@ var createVoiceConnection = (path, options = {}) => {
357
371
  getSessionId: () => state.sessionId,
358
372
  send,
359
373
  sendAudio,
374
+ start,
360
375
  subscribe
361
376
  };
362
377
  };
@@ -365,6 +380,7 @@ var createVoiceConnection = (path, options = {}) => {
365
380
  var createInitialState = () => ({
366
381
  assistantAudio: [],
367
382
  assistantTexts: [],
383
+ call: null,
368
384
  error: null,
369
385
  isConnected: false,
370
386
  scenarioId: null,
@@ -408,6 +424,20 @@ var createVoiceStreamStore = () => {
408
424
  status: "completed"
409
425
  };
410
426
  break;
427
+ case "call_lifecycle":
428
+ state = {
429
+ ...state,
430
+ call: {
431
+ ...state.call,
432
+ disposition: action.event.type === "end" ? action.event.disposition : state.call?.disposition,
433
+ endedAt: action.event.type === "end" ? action.event.at : state.call?.endedAt,
434
+ events: [...state.call?.events ?? [], action.event],
435
+ lastEventAt: action.event.at,
436
+ startedAt: state.call?.startedAt ?? action.event.at
437
+ },
438
+ sessionId: action.sessionId
439
+ };
440
+ break;
411
441
  case "connected":
412
442
  state = {
413
443
  ...state,
@@ -494,6 +524,9 @@ var createVoiceStream = (path, options = {}) => {
494
524
  }
495
525
  });
496
526
  return {
527
+ callControl(message) {
528
+ connection.callControl(message);
529
+ },
497
530
  close() {
498
531
  unsubscribeConnection();
499
532
  connection.close();
@@ -537,6 +570,9 @@ var createVoiceStream = (path, options = {}) => {
537
570
  get assistantAudio() {
538
571
  return store.getSnapshot().assistantAudio;
539
572
  },
573
+ get call() {
574
+ return store.getSnapshot().call;
575
+ },
540
576
  sendAudio(audio) {
541
577
  connection.sendAudio(audio);
542
578
  },
@@ -1065,6 +1101,7 @@ var resolveVoiceRuntimePreset = (name = "default") => {
1065
1101
  var createInitialState2 = (stream) => ({
1066
1102
  assistantAudio: [...stream.assistantAudio],
1067
1103
  assistantTexts: [...stream.assistantTexts],
1104
+ call: stream.call,
1068
1105
  error: stream.error,
1069
1106
  isConnected: stream.isConnected,
1070
1107
  isRecording: false,
@@ -1094,6 +1131,7 @@ var createVoiceController = (path, options = {}) => {
1094
1131
  ...state,
1095
1132
  assistantAudio: [...stream.assistantAudio],
1096
1133
  assistantTexts: [...stream.assistantTexts],
1134
+ call: stream.call,
1097
1135
  error: stream.error,
1098
1136
  isConnected: stream.isConnected,
1099
1137
  partial: stream.partial,
@@ -1171,6 +1209,7 @@ var createVoiceController = (path, options = {}) => {
1171
1209
  bindHTMX(bindingOptions) {
1172
1210
  return bindVoiceHTMX(stream, bindingOptions);
1173
1211
  },
1212
+ callControl: (message) => stream.callControl(message),
1174
1213
  close,
1175
1214
  endTurn: () => stream.endTurn(),
1176
1215
  get error() {
@@ -1223,6 +1262,9 @@ var createVoiceController = (path, options = {}) => {
1223
1262
  },
1224
1263
  get assistantAudio() {
1225
1264
  return state.assistantAudio;
1265
+ },
1266
+ get call() {
1267
+ return state.call;
1226
1268
  }
1227
1269
  };
1228
1270
  };
@@ -7,6 +7,7 @@ export declare const serverMessageToAction: <TResult = unknown>(message: VoiceSe
7
7
  type: "audio";
8
8
  text?: undefined;
9
9
  sessionId?: undefined;
10
+ event?: undefined;
10
11
  message?: undefined;
11
12
  transcript?: undefined;
12
13
  scenarioId?: undefined;
@@ -20,6 +21,7 @@ export declare const serverMessageToAction: <TResult = unknown>(message: VoiceSe
20
21
  receivedAt?: undefined;
21
22
  turnId?: undefined;
22
23
  sessionId?: undefined;
24
+ event?: undefined;
23
25
  message?: undefined;
24
26
  transcript?: undefined;
25
27
  scenarioId?: undefined;
@@ -33,6 +35,21 @@ export declare const serverMessageToAction: <TResult = unknown>(message: VoiceSe
33
35
  receivedAt?: undefined;
34
36
  turnId?: undefined;
35
37
  text?: undefined;
38
+ event?: undefined;
39
+ message?: undefined;
40
+ transcript?: undefined;
41
+ scenarioId?: undefined;
42
+ status?: undefined;
43
+ turn?: undefined;
44
+ } | {
45
+ event: import("..").VoiceCallLifecycleEvent;
46
+ sessionId: string;
47
+ type: "call_lifecycle";
48
+ chunk?: undefined;
49
+ format?: undefined;
50
+ receivedAt?: undefined;
51
+ turnId?: undefined;
52
+ text?: undefined;
36
53
  message?: undefined;
37
54
  transcript?: undefined;
38
55
  scenarioId?: undefined;
@@ -47,6 +64,7 @@ export declare const serverMessageToAction: <TResult = unknown>(message: VoiceSe
47
64
  turnId?: undefined;
48
65
  text?: undefined;
49
66
  sessionId?: undefined;
67
+ event?: undefined;
50
68
  transcript?: undefined;
51
69
  scenarioId?: undefined;
52
70
  status?: undefined;
@@ -60,6 +78,7 @@ export declare const serverMessageToAction: <TResult = unknown>(message: VoiceSe
60
78
  turnId?: undefined;
61
79
  text?: undefined;
62
80
  sessionId?: undefined;
81
+ event?: undefined;
63
82
  message?: undefined;
64
83
  scenarioId?: undefined;
65
84
  status?: undefined;
@@ -73,6 +92,7 @@ export declare const serverMessageToAction: <TResult = unknown>(message: VoiceSe
73
92
  turnId?: undefined;
74
93
  text?: undefined;
75
94
  sessionId?: undefined;
95
+ event?: undefined;
76
96
  message?: undefined;
77
97
  scenarioId?: undefined;
78
98
  status?: undefined;
@@ -87,6 +107,7 @@ export declare const serverMessageToAction: <TResult = unknown>(message: VoiceSe
87
107
  receivedAt?: undefined;
88
108
  turnId?: undefined;
89
109
  text?: undefined;
110
+ event?: undefined;
90
111
  message?: undefined;
91
112
  transcript?: undefined;
92
113
  turn?: undefined;
@@ -99,6 +120,7 @@ export declare const serverMessageToAction: <TResult = unknown>(message: VoiceSe
99
120
  turnId?: undefined;
100
121
  text?: undefined;
101
122
  sessionId?: undefined;
123
+ event?: undefined;
102
124
  message?: undefined;
103
125
  transcript?: undefined;
104
126
  scenarioId?: undefined;
@@ -1,5 +1,8 @@
1
1
  import type { VoiceClientMessage, VoiceConnectionOptions, VoiceServerMessage } from '../types';
2
2
  type VoiceConnectionHandle = {
3
+ callControl: (message: Omit<VoiceClientMessage & {
4
+ type: 'call_control';
5
+ }, 'type'>) => void;
3
6
  start: (input?: {
4
7
  sessionId?: string;
5
8
  scenarioId?: string;
@@ -188,6 +188,12 @@ var serverMessageToAction = (message) => {
188
188
  sessionId: message.sessionId,
189
189
  type: "complete"
190
190
  };
191
+ case "call_lifecycle":
192
+ return {
193
+ event: message.event,
194
+ sessionId: message.sessionId,
195
+ type: "call_lifecycle"
196
+ };
191
197
  case "error":
192
198
  return {
193
199
  message: normalizeErrorMessage(message.message),
@@ -231,7 +237,7 @@ var DEFAULT_SCENARIO_QUERY_PARAM = "scenarioId";
231
237
  var noop = () => {};
232
238
  var noopUnsubscribe = () => noop;
233
239
  var NOOP_CONNECTION = {
234
- start: () => {},
240
+ callControl: noop,
235
241
  close: noop,
236
242
  endTurn: noop,
237
243
  getReadyState: () => WS_CLOSED,
@@ -239,6 +245,7 @@ var NOOP_CONNECTION = {
239
245
  getSessionId: () => "",
240
246
  send: noop,
241
247
  sendAudio: noop,
248
+ start: () => {},
242
249
  subscribe: noopUnsubscribe
243
250
  };
244
251
  var createSessionId = () => crypto.randomUUID();
@@ -260,6 +267,7 @@ var isVoiceServerMessage = (value) => {
260
267
  switch (value.type) {
261
268
  case "audio":
262
269
  case "assistant":
270
+ case "call_lifecycle":
263
271
  case "complete":
264
272
  case "error":
265
273
  case "final":
@@ -400,6 +408,12 @@ var createVoiceConnection = (path, options = {}) => {
400
408
  const endTurn = () => {
401
409
  send({ type: "end_turn" });
402
410
  };
411
+ const callControl = (message) => {
412
+ send({
413
+ ...message,
414
+ type: "call_control"
415
+ });
416
+ };
403
417
  const close = () => {
404
418
  clearTimers();
405
419
  if (state.ws) {
@@ -417,7 +431,7 @@ var createVoiceConnection = (path, options = {}) => {
417
431
  };
418
432
  connect();
419
433
  return {
420
- start,
434
+ callControl,
421
435
  close,
422
436
  endTurn,
423
437
  getReadyState: () => state.ws?.readyState ?? WS_CLOSED,
@@ -425,6 +439,7 @@ var createVoiceConnection = (path, options = {}) => {
425
439
  getSessionId: () => state.sessionId,
426
440
  send,
427
441
  sendAudio,
442
+ start,
428
443
  subscribe
429
444
  };
430
445
  };
@@ -433,6 +448,7 @@ var createVoiceConnection = (path, options = {}) => {
433
448
  var createInitialState = () => ({
434
449
  assistantAudio: [],
435
450
  assistantTexts: [],
451
+ call: null,
436
452
  error: null,
437
453
  isConnected: false,
438
454
  scenarioId: null,
@@ -476,6 +492,20 @@ var createVoiceStreamStore = () => {
476
492
  status: "completed"
477
493
  };
478
494
  break;
495
+ case "call_lifecycle":
496
+ state = {
497
+ ...state,
498
+ call: {
499
+ ...state.call,
500
+ disposition: action.event.type === "end" ? action.event.disposition : state.call?.disposition,
501
+ endedAt: action.event.type === "end" ? action.event.at : state.call?.endedAt,
502
+ events: [...state.call?.events ?? [], action.event],
503
+ lastEventAt: action.event.at,
504
+ startedAt: state.call?.startedAt ?? action.event.at
505
+ },
506
+ sessionId: action.sessionId
507
+ };
508
+ break;
479
509
  case "connected":
480
510
  state = {
481
511
  ...state,
@@ -562,6 +592,9 @@ var createVoiceStream = (path, options = {}) => {
562
592
  }
563
593
  });
564
594
  return {
595
+ callControl(message) {
596
+ connection.callControl(message);
597
+ },
565
598
  close() {
566
599
  unsubscribeConnection();
567
600
  connection.close();
@@ -605,6 +638,9 @@ var createVoiceStream = (path, options = {}) => {
605
638
  get assistantAudio() {
606
639
  return store.getSnapshot().assistantAudio;
607
640
  },
641
+ get call() {
642
+ return store.getSnapshot().call;
643
+ },
608
644
  sendAudio(audio) {
609
645
  connection.sendAudio(audio);
610
646
  },
@@ -900,6 +936,7 @@ var resolveVoiceRuntimePreset = (name = "default") => {
900
936
  var createInitialState2 = (stream) => ({
901
937
  assistantAudio: [...stream.assistantAudio],
902
938
  assistantTexts: [...stream.assistantTexts],
939
+ call: stream.call,
903
940
  error: stream.error,
904
941
  isConnected: stream.isConnected,
905
942
  isRecording: false,
@@ -929,6 +966,7 @@ var createVoiceController = (path, options = {}) => {
929
966
  ...state,
930
967
  assistantAudio: [...stream.assistantAudio],
931
968
  assistantTexts: [...stream.assistantTexts],
969
+ call: stream.call,
932
970
  error: stream.error,
933
971
  isConnected: stream.isConnected,
934
972
  partial: stream.partial,
@@ -1006,6 +1044,7 @@ var createVoiceController = (path, options = {}) => {
1006
1044
  bindHTMX(bindingOptions) {
1007
1045
  return bindVoiceHTMX(stream, bindingOptions);
1008
1046
  },
1047
+ callControl: (message) => stream.callControl(message),
1009
1048
  close,
1010
1049
  endTurn: () => stream.endTurn(),
1011
1050
  get error() {
@@ -1058,6 +1097,9 @@ var createVoiceController = (path, options = {}) => {
1058
1097
  },
1059
1098
  get assistantAudio() {
1060
1099
  return state.assistantAudio;
1100
+ },
1101
+ get call() {
1102
+ return state.call;
1061
1103
  }
1062
1104
  };
1063
1105
  };
@@ -80,7 +80,7 @@ var DEFAULT_SCENARIO_QUERY_PARAM = "scenarioId";
80
80
  var noop = () => {};
81
81
  var noopUnsubscribe = () => noop;
82
82
  var NOOP_CONNECTION = {
83
- start: () => {},
83
+ callControl: noop,
84
84
  close: noop,
85
85
  endTurn: noop,
86
86
  getReadyState: () => WS_CLOSED,
@@ -88,6 +88,7 @@ var NOOP_CONNECTION = {
88
88
  getSessionId: () => "",
89
89
  send: noop,
90
90
  sendAudio: noop,
91
+ start: () => {},
91
92
  subscribe: noopUnsubscribe
92
93
  };
93
94
  var createSessionId = () => crypto.randomUUID();
@@ -109,6 +110,7 @@ var isVoiceServerMessage = (value) => {
109
110
  switch (value.type) {
110
111
  case "audio":
111
112
  case "assistant":
113
+ case "call_lifecycle":
112
114
  case "complete":
113
115
  case "error":
114
116
  case "final":
@@ -249,6 +251,12 @@ var createVoiceConnection = (path, options = {}) => {
249
251
  const endTurn = () => {
250
252
  send({ type: "end_turn" });
251
253
  };
254
+ const callControl = (message) => {
255
+ send({
256
+ ...message,
257
+ type: "call_control"
258
+ });
259
+ };
252
260
  const close = () => {
253
261
  clearTimers();
254
262
  if (state.ws) {
@@ -266,7 +274,7 @@ var createVoiceConnection = (path, options = {}) => {
266
274
  };
267
275
  connect();
268
276
  return {
269
- start,
277
+ callControl,
270
278
  close,
271
279
  endTurn,
272
280
  getReadyState: () => state.ws?.readyState ?? WS_CLOSED,
@@ -274,6 +282,7 @@ var createVoiceConnection = (path, options = {}) => {
274
282
  getSessionId: () => state.sessionId,
275
283
  send,
276
284
  sendAudio,
285
+ start,
277
286
  subscribe
278
287
  };
279
288
  };
@@ -671,6 +680,12 @@ var serverMessageToAction = (message) => {
671
680
  sessionId: message.sessionId,
672
681
  type: "complete"
673
682
  };
683
+ case "call_lifecycle":
684
+ return {
685
+ event: message.event,
686
+ sessionId: message.sessionId,
687
+ type: "call_lifecycle"
688
+ };
674
689
  case "error":
675
690
  return {
676
691
  message: normalizeErrorMessage(message.message),
@@ -707,6 +722,7 @@ var serverMessageToAction = (message) => {
707
722
  var createInitialState2 = () => ({
708
723
  assistantAudio: [],
709
724
  assistantTexts: [],
725
+ call: null,
710
726
  error: null,
711
727
  isConnected: false,
712
728
  scenarioId: null,
@@ -750,6 +766,20 @@ var createVoiceStreamStore = () => {
750
766
  status: "completed"
751
767
  };
752
768
  break;
769
+ case "call_lifecycle":
770
+ state = {
771
+ ...state,
772
+ call: {
773
+ ...state.call,
774
+ disposition: action.event.type === "end" ? action.event.disposition : state.call?.disposition,
775
+ endedAt: action.event.type === "end" ? action.event.at : state.call?.endedAt,
776
+ events: [...state.call?.events ?? [], action.event],
777
+ lastEventAt: action.event.at,
778
+ startedAt: state.call?.startedAt ?? action.event.at
779
+ },
780
+ sessionId: action.sessionId
781
+ };
782
+ break;
753
783
  case "connected":
754
784
  state = {
755
785
  ...state,
@@ -836,6 +866,9 @@ var createVoiceStream = (path, options = {}) => {
836
866
  }
837
867
  });
838
868
  return {
869
+ callControl(message) {
870
+ connection.callControl(message);
871
+ },
839
872
  close() {
840
873
  unsubscribeConnection();
841
874
  connection.close();
@@ -879,6 +912,9 @@ var createVoiceStream = (path, options = {}) => {
879
912
  get assistantAudio() {
880
913
  return store.getSnapshot().assistantAudio;
881
914
  },
915
+ get call() {
916
+ return store.getSnapshot().call;
917
+ },
882
918
  sendAudio(audio) {
883
919
  connection.sendAudio(audio);
884
920
  },
@@ -1352,6 +1388,7 @@ var resolveVoiceRuntimePreset = (name = "default") => {
1352
1388
  var createInitialState3 = (stream) => ({
1353
1389
  assistantAudio: [...stream.assistantAudio],
1354
1390
  assistantTexts: [...stream.assistantTexts],
1391
+ call: stream.call,
1355
1392
  error: stream.error,
1356
1393
  isConnected: stream.isConnected,
1357
1394
  isRecording: false,
@@ -1381,6 +1418,7 @@ var createVoiceController = (path, options = {}) => {
1381
1418
  ...state,
1382
1419
  assistantAudio: [...stream.assistantAudio],
1383
1420
  assistantTexts: [...stream.assistantTexts],
1421
+ call: stream.call,
1384
1422
  error: stream.error,
1385
1423
  isConnected: stream.isConnected,
1386
1424
  partial: stream.partial,
@@ -1458,6 +1496,7 @@ var createVoiceController = (path, options = {}) => {
1458
1496
  bindHTMX(bindingOptions) {
1459
1497
  return bindVoiceHTMX(stream, bindingOptions);
1460
1498
  },
1499
+ callControl: (message) => stream.callControl(message),
1461
1500
  close,
1462
1501
  endTurn: () => stream.endTurn(),
1463
1502
  get error() {
@@ -1510,6 +1549,9 @@ var createVoiceController = (path, options = {}) => {
1510
1549
  },
1511
1550
  get assistantAudio() {
1512
1551
  return state.assistantAudio;
1552
+ },
1553
+ get call() {
1554
+ return state.call;
1513
1555
  }
1514
1556
  };
1515
1557
  };
package/dist/index.js CHANGED
@@ -3288,6 +3288,7 @@ var pushCallLifecycleEvent = (session, input) => {
3288
3288
  }
3289
3289
  return lifecycle;
3290
3290
  };
3291
+ var getLatestCallLifecycleEvent = (session) => session.call?.events.at(-1);
3291
3292
  var createVoiceSession = (options) => {
3292
3293
  const logger = resolveLogger(options.logger);
3293
3294
  const reconnect = {
@@ -3388,6 +3389,17 @@ var createVoiceSession = (options) => {
3388
3389
  });
3389
3390
  }
3390
3391
  };
3392
+ const sendCallLifecycle = async (session) => {
3393
+ const event = getLatestCallLifecycleEvent(session);
3394
+ if (!event) {
3395
+ return;
3396
+ }
3397
+ await send({
3398
+ event,
3399
+ sessionId: options.id,
3400
+ type: "call_lifecycle"
3401
+ });
3402
+ };
3391
3403
  const readSession = async () => options.store.getOrCreate(options.id);
3392
3404
  const writeSession = async (mutate) => {
3393
3405
  const session = await options.store.getOrCreate(options.id);
@@ -3578,6 +3590,7 @@ var createVoiceSession = (options) => {
3578
3590
  await appendTrace({
3579
3591
  payload: {
3580
3592
  disposition,
3593
+ metadata: input.metadata,
3581
3594
  reason: input.reason,
3582
3595
  target: input.target,
3583
3596
  type: "end"
@@ -3585,6 +3598,7 @@ var createVoiceSession = (options) => {
3585
3598
  session,
3586
3599
  type: "call.lifecycle"
3587
3600
  });
3601
+ await sendCallLifecycle(session);
3588
3602
  await send({
3589
3603
  sessionId: options.id,
3590
3604
  type: "complete"
@@ -3664,6 +3678,7 @@ var createVoiceSession = (options) => {
3664
3678
  session,
3665
3679
  type: "call.lifecycle"
3666
3680
  });
3681
+ await sendCallLifecycle(session);
3667
3682
  await completeInternal(input.result, {
3668
3683
  disposition: "transferred",
3669
3684
  invokeOnComplete: false,
@@ -3689,6 +3704,7 @@ var createVoiceSession = (options) => {
3689
3704
  session,
3690
3705
  type: "call.lifecycle"
3691
3706
  });
3707
+ await sendCallLifecycle(session);
3692
3708
  await completeInternal(input.result, {
3693
3709
  disposition: "escalated",
3694
3710
  invokeOnComplete: false,
@@ -3711,6 +3727,7 @@ var createVoiceSession = (options) => {
3711
3727
  session,
3712
3728
  type: "call.lifecycle"
3713
3729
  });
3730
+ await sendCallLifecycle(session);
3714
3731
  await completeInternal(input?.result, {
3715
3732
  disposition: "no-answer",
3716
3733
  invokeOnComplete: false,
@@ -3732,6 +3749,7 @@ var createVoiceSession = (options) => {
3732
3749
  session,
3733
3750
  type: "call.lifecycle"
3734
3751
  });
3752
+ await sendCallLifecycle(session);
3735
3753
  await completeInternal(input?.result, {
3736
3754
  disposition: "voicemail",
3737
3755
  invokeOnComplete: false,
@@ -4518,6 +4536,7 @@ var createVoiceSession = (options) => {
4518
4536
  session,
4519
4537
  type: "call.lifecycle"
4520
4538
  });
4539
+ await sendCallLifecycle(session);
4521
4540
  }
4522
4541
  await send({
4523
4542
  sessionId: options.id,
@@ -4755,6 +4774,14 @@ var isVoiceClientMessage = (value) => {
4755
4774
  return false;
4756
4775
  }
4757
4776
  switch (value.type) {
4777
+ case "call_control":
4778
+ if (!("action" in value)) {
4779
+ return false;
4780
+ }
4781
+ if (value.action !== "complete" && value.action !== "escalate" && value.action !== "no-answer" && value.action !== "transfer" && value.action !== "voicemail") {
4782
+ return false;
4783
+ }
4784
+ return (!("metadata" in value) || value.metadata === undefined || value.metadata !== null && typeof value.metadata === "object") && (!("reason" in value) || value.reason === undefined || typeof value.reason === "string") && (!("target" in value) || value.target === undefined || typeof value.target === "string");
4758
4785
  case "close":
4759
4786
  return true;
4760
4787
  case "end_turn":
@@ -5006,6 +5033,42 @@ var voice = (config) => {
5006
5033
  await current.close(message.reason);
5007
5034
  runtime.activeSessions.delete(sessionState.sessionId);
5008
5035
  }
5036
+ if (message.type === "call_control" && current) {
5037
+ if (message.action === "transfer") {
5038
+ if (message.target) {
5039
+ await current.transfer({
5040
+ metadata: message.metadata,
5041
+ reason: message.reason,
5042
+ target: message.target
5043
+ });
5044
+ } else {
5045
+ ws.send(JSON.stringify({
5046
+ message: "call_control transfer requires target",
5047
+ recoverable: true,
5048
+ type: "error"
5049
+ }));
5050
+ }
5051
+ }
5052
+ if (message.action === "escalate") {
5053
+ await current.escalate({
5054
+ metadata: message.metadata,
5055
+ reason: message.reason ?? "client-requested-escalation"
5056
+ });
5057
+ }
5058
+ if (message.action === "voicemail") {
5059
+ await current.markVoicemail({
5060
+ metadata: message.metadata
5061
+ });
5062
+ }
5063
+ if (message.action === "no-answer") {
5064
+ await current.markNoAnswer({
5065
+ metadata: message.metadata
5066
+ });
5067
+ }
5068
+ if (message.action === "complete") {
5069
+ await current.complete();
5070
+ }
5071
+ }
5009
5072
  if (message.type === "start" && message.sessionId && message.sessionId !== sessionState.sessionId) {
5010
5073
  const currentSession = runtime.activeSessions.get(sessionState.sessionId);
5011
5074
  if (currentSession) {
@@ -9003,11 +9066,11 @@ var verifyVoiceOpsWebhookSignature = async (input) => {
9003
9066
  };
9004
9067
  var createVoiceOpsWebhookReceiverRoutes = (options = {}) => {
9005
9068
  const path = options.path ?? "/api/voice-ops/webhook";
9006
- return new Elysia5().post(path, async ({ request, set }) => {
9007
- const body = await request.text();
9069
+ return new Elysia5().post(path, async ({ body, request, set }) => {
9070
+ const bodyText = typeof body === "string" ? body : JSON.stringify(body);
9008
9071
  if (options.signingSecret) {
9009
9072
  const verification = await verifyVoiceOpsWebhookSignature({
9010
- body,
9073
+ body: bodyText,
9011
9074
  secret: options.signingSecret,
9012
9075
  signature: request.headers.get("x-absolutejs-signature"),
9013
9076
  timestamp: request.headers.get("x-absolutejs-timestamp"),
@@ -9021,7 +9084,7 @@ var createVoiceOpsWebhookReceiverRoutes = (options = {}) => {
9021
9084
  };
9022
9085
  }
9023
9086
  }
9024
- const envelope = JSON.parse(body);
9087
+ const envelope = JSON.parse(bodyText);
9025
9088
  await options.onEnvelope?.({
9026
9089
  envelope,
9027
9090
  request
@@ -9031,6 +9094,8 @@ var createVoiceOpsWebhookReceiverRoutes = (options = {}) => {
9031
9094
  ok: true,
9032
9095
  type: envelope.event?.type
9033
9096
  };
9097
+ }, {
9098
+ parse: "text"
9034
9099
  });
9035
9100
  };
9036
9101
  // src/queue.ts