@absolutejs/voice 0.0.22-beta.129 → 0.0.22-beta.130

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.
@@ -2105,6 +2105,11 @@ var serverMessageToAction = (message) => {
2105
2105
  sessionId: message.sessionId,
2106
2106
  type: "complete"
2107
2107
  };
2108
+ case "connection":
2109
+ return {
2110
+ reconnect: message.reconnect,
2111
+ type: "connection"
2112
+ };
2108
2113
  case "call_lifecycle":
2109
2114
  return {
2110
2115
  event: message.event,
@@ -2197,6 +2202,7 @@ var isVoiceServerMessage = (value) => {
2197
2202
  case "assistant":
2198
2203
  case "call_lifecycle":
2199
2204
  case "complete":
2205
+ case "connection":
2200
2206
  case "error":
2201
2207
  case "final":
2202
2208
  case "partial":
@@ -2238,6 +2244,9 @@ var createVoiceConnection = (path, options = {}) => {
2238
2244
  sessionId: options.sessionId ?? createSessionId(),
2239
2245
  ws: null
2240
2246
  };
2247
+ const emitConnection = (reconnect) => {
2248
+ listeners.forEach((listener) => listener(reconnect));
2249
+ };
2241
2250
  const clearTimers = () => {
2242
2251
  if (state.pingInterval) {
2243
2252
  clearInterval(state.pingInterval);
@@ -2260,9 +2269,28 @@ var createVoiceConnection = (path, options = {}) => {
2260
2269
  }
2261
2270
  };
2262
2271
  const scheduleReconnect = () => {
2272
+ const nextAttemptAt = Date.now() + RECONNECT_DELAY_MS;
2263
2273
  state.reconnectAttempts += 1;
2274
+ emitConnection({
2275
+ reconnect: {
2276
+ attempts: state.reconnectAttempts,
2277
+ lastDisconnectAt: Date.now(),
2278
+ maxAttempts: maxReconnectAttempts,
2279
+ nextAttemptAt,
2280
+ status: "reconnecting"
2281
+ },
2282
+ type: "connection"
2283
+ });
2264
2284
  state.reconnectTimeout = setTimeout(() => {
2265
2285
  if (state.reconnectAttempts > maxReconnectAttempts) {
2286
+ emitConnection({
2287
+ reconnect: {
2288
+ attempts: state.reconnectAttempts,
2289
+ maxAttempts: maxReconnectAttempts,
2290
+ status: "exhausted"
2291
+ },
2292
+ type: "connection"
2293
+ });
2266
2294
  return;
2267
2295
  }
2268
2296
  connect();
@@ -2272,9 +2300,21 @@ var createVoiceConnection = (path, options = {}) => {
2272
2300
  const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId));
2273
2301
  ws.binaryType = "arraybuffer";
2274
2302
  ws.onopen = () => {
2303
+ const wasReconnecting = state.reconnectAttempts > 0;
2275
2304
  state.isConnected = true;
2276
- state.reconnectAttempts = 0;
2277
2305
  flushPendingMessages();
2306
+ if (wasReconnecting) {
2307
+ emitConnection({
2308
+ reconnect: {
2309
+ attempts: state.reconnectAttempts,
2310
+ lastResumedAt: Date.now(),
2311
+ maxAttempts: maxReconnectAttempts,
2312
+ status: "resumed"
2313
+ },
2314
+ type: "connection"
2315
+ });
2316
+ state.reconnectAttempts = 0;
2317
+ }
2278
2318
  listeners.forEach((listener) => listener({
2279
2319
  scenarioId: state.scenarioId ?? undefined,
2280
2320
  sessionId: state.sessionId,
@@ -2304,6 +2344,16 @@ var createVoiceConnection = (path, options = {}) => {
2304
2344
  const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && state.reconnectAttempts < maxReconnectAttempts;
2305
2345
  if (reconnectable) {
2306
2346
  scheduleReconnect();
2347
+ } else if (shouldReconnect && event.code !== WS_NORMAL_CLOSURE) {
2348
+ emitConnection({
2349
+ reconnect: {
2350
+ attempts: state.reconnectAttempts,
2351
+ lastDisconnectAt: Date.now(),
2352
+ maxAttempts: maxReconnectAttempts,
2353
+ status: "exhausted"
2354
+ },
2355
+ type: "connection"
2356
+ });
2307
2357
  }
2308
2358
  };
2309
2359
  state.ws = ws;
@@ -2374,6 +2424,11 @@ var createVoiceConnection = (path, options = {}) => {
2374
2424
  };
2375
2425
 
2376
2426
  // src/client/store.ts
2427
+ var createInitialReconnectState = () => ({
2428
+ attempts: 0,
2429
+ maxAttempts: 0,
2430
+ status: "idle"
2431
+ });
2377
2432
  var createInitialState2 = () => ({
2378
2433
  assistantAudio: [],
2379
2434
  assistantTexts: [],
@@ -2382,6 +2437,7 @@ var createInitialState2 = () => ({
2382
2437
  isConnected: false,
2383
2438
  scenarioId: null,
2384
2439
  partial: "",
2440
+ reconnect: createInitialReconnectState(),
2385
2441
  sessionId: null,
2386
2442
  status: "idle",
2387
2443
  turns: []
@@ -2438,7 +2494,19 @@ var createVoiceStreamStore = () => {
2438
2494
  case "connected":
2439
2495
  state = {
2440
2496
  ...state,
2441
- isConnected: true
2497
+ isConnected: true,
2498
+ reconnect: state.reconnect.status === "reconnecting" ? {
2499
+ ...state.reconnect,
2500
+ lastResumedAt: Date.now(),
2501
+ nextAttemptAt: undefined,
2502
+ status: "resumed"
2503
+ } : state.reconnect
2504
+ };
2505
+ break;
2506
+ case "connection":
2507
+ state = {
2508
+ ...state,
2509
+ reconnect: action.reconnect
2442
2510
  };
2443
2511
  break;
2444
2512
  case "disconnected":
@@ -2474,6 +2542,12 @@ var createVoiceStreamStore = () => {
2474
2542
  error: null,
2475
2543
  isConnected: action.status === "active",
2476
2544
  partial: action.partial,
2545
+ reconnect: state.reconnect.status === "reconnecting" ? {
2546
+ ...state.reconnect,
2547
+ lastResumedAt: Date.now(),
2548
+ nextAttemptAt: undefined,
2549
+ status: "resumed"
2550
+ } : state.reconnect,
2477
2551
  scenarioId: action.scenarioId ?? state.scenarioId,
2478
2552
  sessionId: action.sessionId,
2479
2553
  status: action.status,
@@ -2566,6 +2640,9 @@ var createVoiceStream = (path, options = {}) => {
2566
2640
  get partial() {
2567
2641
  return store.getSnapshot().partial;
2568
2642
  },
2643
+ get reconnect() {
2644
+ return store.getSnapshot().reconnect;
2645
+ },
2569
2646
  get sessionId() {
2570
2647
  return connection.getSessionId();
2571
2648
  },
@@ -2921,6 +2998,7 @@ var createInitialState3 = (stream) => ({
2921
2998
  isConnected: stream.isConnected,
2922
2999
  isRecording: false,
2923
3000
  partial: stream.partial,
3001
+ reconnect: stream.reconnect,
2924
3002
  recordingError: null,
2925
3003
  sessionId: stream.sessionId,
2926
3004
  scenarioId: stream.scenarioId,
@@ -2950,6 +3028,7 @@ var createVoiceController = (path, options = {}) => {
2950
3028
  error: stream.error,
2951
3029
  isConnected: stream.isConnected,
2952
3030
  partial: stream.partial,
3031
+ reconnect: stream.reconnect,
2953
3032
  sessionId: stream.sessionId,
2954
3033
  scenarioId: stream.scenarioId,
2955
3034
  status: stream.status,
@@ -3050,6 +3129,9 @@ var createVoiceController = (path, options = {}) => {
3050
3129
  get recordingError() {
3051
3130
  return state.recordingError;
3052
3131
  },
3132
+ get reconnect() {
3133
+ return state.reconnect;
3134
+ },
3053
3135
  sendAudio: (audio) => stream.sendAudio(audio),
3054
3136
  get sessionId() {
3055
3137
  return state.sessionId;
package/dist/types.d.ts CHANGED
@@ -193,6 +193,15 @@ export type RealtimeAdapter<TOptions extends RealtimeAdapterOpenOptions = Realti
193
193
  open: (options: TOptions) => Promise<RealtimeAdapterSession> | RealtimeAdapterSession;
194
194
  };
195
195
  export type VoiceSessionStatus = 'active' | 'reconnecting' | 'completed' | 'failed';
196
+ export type VoiceReconnectClientStatus = 'idle' | 'reconnecting' | 'resumed' | 'exhausted';
197
+ export type VoiceReconnectClientState = {
198
+ attempts: number;
199
+ lastDisconnectAt?: number;
200
+ lastResumedAt?: number;
201
+ maxAttempts: number;
202
+ nextAttemptAt?: number;
203
+ status: VoiceReconnectClientStatus;
204
+ };
196
205
  export type VoiceTurnRecord<TResult = unknown> = {
197
206
  id: string;
198
207
  text: string;
@@ -737,7 +746,11 @@ export type VoiceServerErrorMessage = {
737
746
  export type VoiceServerPongMessage = {
738
747
  type: 'pong';
739
748
  };
740
- export type VoiceServerMessage<TResult = unknown> = VoiceServerSessionMessage | VoiceServerReplayMessage<TResult> | VoiceServerPartialMessage | VoiceServerFinalMessage | VoiceServerTurnMessage<TResult> | VoiceServerAssistantMessage | VoiceServerAudioMessage | VoiceServerCallLifecycleMessage | VoiceServerCompleteMessage | VoiceServerErrorMessage | VoiceServerPongMessage;
749
+ export type VoiceServerConnectionMessage = {
750
+ type: 'connection';
751
+ reconnect: VoiceReconnectClientState;
752
+ };
753
+ export type VoiceServerMessage<TResult = unknown> = VoiceServerSessionMessage | VoiceServerReplayMessage<TResult> | VoiceServerPartialMessage | VoiceServerFinalMessage | VoiceServerTurnMessage<TResult> | VoiceServerAssistantMessage | VoiceServerAudioMessage | VoiceServerCallLifecycleMessage | VoiceServerCompleteMessage | VoiceServerErrorMessage | VoiceServerPongMessage | VoiceServerConnectionMessage;
741
754
  export type VoiceConnectionOptions = {
742
755
  protocols?: string[];
743
756
  scenarioId?: string;
@@ -860,6 +873,7 @@ export type VoiceStreamState<TResult = unknown> = {
860
873
  sessionId: string | null;
861
874
  scenarioId: string | null;
862
875
  status: VoiceSessionStatus | 'idle';
876
+ reconnect: VoiceReconnectClientState;
863
877
  partial: string;
864
878
  turns: VoiceTurnRecord<TResult>[];
865
879
  assistantTexts: string[];
@@ -886,6 +900,7 @@ export type VoiceStream<TResult = unknown> = {
886
900
  getSnapshot: () => VoiceStreamState<TResult>;
887
901
  isConnected: boolean;
888
902
  partial: string;
903
+ reconnect: VoiceReconnectClientState;
889
904
  sendAudio: (audio: Uint8Array | ArrayBuffer) => void;
890
905
  sessionId: string | null;
891
906
  scenarioId: string | null;
@@ -955,6 +970,7 @@ export type VoiceController<TResult = unknown> = {
955
970
  isConnected: boolean;
956
971
  isRecording: boolean;
957
972
  partial: string;
973
+ reconnect: VoiceReconnectClientState;
958
974
  recordingError: string | null;
959
975
  sendAudio: (audio: Uint8Array | ArrayBuffer) => void;
960
976
  sessionId: string | null;
@@ -1027,6 +1043,9 @@ export type VoiceStoreAction<TResult = unknown> = {
1027
1043
  message: string;
1028
1044
  } | {
1029
1045
  type: 'connected';
1046
+ } | {
1047
+ type: 'connection';
1048
+ reconnect: VoiceReconnectClientState;
1030
1049
  } | {
1031
1050
  type: 'disconnected';
1032
1051
  };
package/dist/vue/index.js CHANGED
@@ -2317,6 +2317,11 @@ var serverMessageToAction = (message) => {
2317
2317
  sessionId: message.sessionId,
2318
2318
  type: "complete"
2319
2319
  };
2320
+ case "connection":
2321
+ return {
2322
+ reconnect: message.reconnect,
2323
+ type: "connection"
2324
+ };
2320
2325
  case "call_lifecycle":
2321
2326
  return {
2322
2327
  event: message.event,
@@ -2409,6 +2414,7 @@ var isVoiceServerMessage = (value) => {
2409
2414
  case "assistant":
2410
2415
  case "call_lifecycle":
2411
2416
  case "complete":
2417
+ case "connection":
2412
2418
  case "error":
2413
2419
  case "final":
2414
2420
  case "partial":
@@ -2450,6 +2456,9 @@ var createVoiceConnection = (path, options = {}) => {
2450
2456
  sessionId: options.sessionId ?? createSessionId(),
2451
2457
  ws: null
2452
2458
  };
2459
+ const emitConnection = (reconnect) => {
2460
+ listeners.forEach((listener) => listener(reconnect));
2461
+ };
2453
2462
  const clearTimers = () => {
2454
2463
  if (state.pingInterval) {
2455
2464
  clearInterval(state.pingInterval);
@@ -2472,9 +2481,28 @@ var createVoiceConnection = (path, options = {}) => {
2472
2481
  }
2473
2482
  };
2474
2483
  const scheduleReconnect = () => {
2484
+ const nextAttemptAt = Date.now() + RECONNECT_DELAY_MS;
2475
2485
  state.reconnectAttempts += 1;
2486
+ emitConnection({
2487
+ reconnect: {
2488
+ attempts: state.reconnectAttempts,
2489
+ lastDisconnectAt: Date.now(),
2490
+ maxAttempts: maxReconnectAttempts,
2491
+ nextAttemptAt,
2492
+ status: "reconnecting"
2493
+ },
2494
+ type: "connection"
2495
+ });
2476
2496
  state.reconnectTimeout = setTimeout(() => {
2477
2497
  if (state.reconnectAttempts > maxReconnectAttempts) {
2498
+ emitConnection({
2499
+ reconnect: {
2500
+ attempts: state.reconnectAttempts,
2501
+ maxAttempts: maxReconnectAttempts,
2502
+ status: "exhausted"
2503
+ },
2504
+ type: "connection"
2505
+ });
2478
2506
  return;
2479
2507
  }
2480
2508
  connect();
@@ -2484,9 +2512,21 @@ var createVoiceConnection = (path, options = {}) => {
2484
2512
  const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId));
2485
2513
  ws.binaryType = "arraybuffer";
2486
2514
  ws.onopen = () => {
2515
+ const wasReconnecting = state.reconnectAttempts > 0;
2487
2516
  state.isConnected = true;
2488
- state.reconnectAttempts = 0;
2489
2517
  flushPendingMessages();
2518
+ if (wasReconnecting) {
2519
+ emitConnection({
2520
+ reconnect: {
2521
+ attempts: state.reconnectAttempts,
2522
+ lastResumedAt: Date.now(),
2523
+ maxAttempts: maxReconnectAttempts,
2524
+ status: "resumed"
2525
+ },
2526
+ type: "connection"
2527
+ });
2528
+ state.reconnectAttempts = 0;
2529
+ }
2490
2530
  listeners.forEach((listener) => listener({
2491
2531
  scenarioId: state.scenarioId ?? undefined,
2492
2532
  sessionId: state.sessionId,
@@ -2516,6 +2556,16 @@ var createVoiceConnection = (path, options = {}) => {
2516
2556
  const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && state.reconnectAttempts < maxReconnectAttempts;
2517
2557
  if (reconnectable) {
2518
2558
  scheduleReconnect();
2559
+ } else if (shouldReconnect && event.code !== WS_NORMAL_CLOSURE) {
2560
+ emitConnection({
2561
+ reconnect: {
2562
+ attempts: state.reconnectAttempts,
2563
+ lastDisconnectAt: Date.now(),
2564
+ maxAttempts: maxReconnectAttempts,
2565
+ status: "exhausted"
2566
+ },
2567
+ type: "connection"
2568
+ });
2519
2569
  }
2520
2570
  };
2521
2571
  state.ws = ws;
@@ -2586,6 +2636,11 @@ var createVoiceConnection = (path, options = {}) => {
2586
2636
  };
2587
2637
 
2588
2638
  // src/client/store.ts
2639
+ var createInitialReconnectState = () => ({
2640
+ attempts: 0,
2641
+ maxAttempts: 0,
2642
+ status: "idle"
2643
+ });
2589
2644
  var createInitialState = () => ({
2590
2645
  assistantAudio: [],
2591
2646
  assistantTexts: [],
@@ -2594,6 +2649,7 @@ var createInitialState = () => ({
2594
2649
  isConnected: false,
2595
2650
  scenarioId: null,
2596
2651
  partial: "",
2652
+ reconnect: createInitialReconnectState(),
2597
2653
  sessionId: null,
2598
2654
  status: "idle",
2599
2655
  turns: []
@@ -2650,7 +2706,19 @@ var createVoiceStreamStore = () => {
2650
2706
  case "connected":
2651
2707
  state = {
2652
2708
  ...state,
2653
- isConnected: true
2709
+ isConnected: true,
2710
+ reconnect: state.reconnect.status === "reconnecting" ? {
2711
+ ...state.reconnect,
2712
+ lastResumedAt: Date.now(),
2713
+ nextAttemptAt: undefined,
2714
+ status: "resumed"
2715
+ } : state.reconnect
2716
+ };
2717
+ break;
2718
+ case "connection":
2719
+ state = {
2720
+ ...state,
2721
+ reconnect: action.reconnect
2654
2722
  };
2655
2723
  break;
2656
2724
  case "disconnected":
@@ -2686,6 +2754,12 @@ var createVoiceStreamStore = () => {
2686
2754
  error: null,
2687
2755
  isConnected: action.status === "active",
2688
2756
  partial: action.partial,
2757
+ reconnect: state.reconnect.status === "reconnecting" ? {
2758
+ ...state.reconnect,
2759
+ lastResumedAt: Date.now(),
2760
+ nextAttemptAt: undefined,
2761
+ status: "resumed"
2762
+ } : state.reconnect,
2689
2763
  scenarioId: action.scenarioId ?? state.scenarioId,
2690
2764
  sessionId: action.sessionId,
2691
2765
  status: action.status,
@@ -2778,6 +2852,9 @@ var createVoiceStream = (path, options = {}) => {
2778
2852
  get partial() {
2779
2853
  return store.getSnapshot().partial;
2780
2854
  },
2855
+ get reconnect() {
2856
+ return store.getSnapshot().reconnect;
2857
+ },
2781
2858
  get sessionId() {
2782
2859
  return connection.getSessionId();
2783
2860
  },
@@ -2817,6 +2894,7 @@ function useVoiceStream(path, options = {}) {
2817
2894
  const error = ref5(null);
2818
2895
  const isConnected = ref5(false);
2819
2896
  const partial = ref5("");
2897
+ const reconnect = shallowRef8(stream.reconnect);
2820
2898
  const sessionId = ref5(stream.sessionId);
2821
2899
  const status = ref5(stream.status);
2822
2900
  const turns = shallowRef8([]);
@@ -2827,6 +2905,7 @@ function useVoiceStream(path, options = {}) {
2827
2905
  error.value = stream.error;
2828
2906
  isConnected.value = stream.isConnected;
2829
2907
  partial.value = stream.partial;
2908
+ reconnect.value = stream.reconnect;
2830
2909
  sessionId.value = stream.sessionId;
2831
2910
  status.value = stream.status;
2832
2911
  turns.value = [...stream.turns];
@@ -2848,6 +2927,7 @@ function useVoiceStream(path, options = {}) {
2848
2927
  error,
2849
2928
  isConnected,
2850
2929
  partial,
2930
+ reconnect,
2851
2931
  sendAudio: (audio) => stream.sendAudio(audio),
2852
2932
  sessionId,
2853
2933
  status,
@@ -3324,6 +3404,7 @@ var createInitialState2 = (stream) => ({
3324
3404
  isConnected: stream.isConnected,
3325
3405
  isRecording: false,
3326
3406
  partial: stream.partial,
3407
+ reconnect: stream.reconnect,
3327
3408
  recordingError: null,
3328
3409
  sessionId: stream.sessionId,
3329
3410
  scenarioId: stream.scenarioId,
@@ -3353,6 +3434,7 @@ var createVoiceController = (path, options = {}) => {
3353
3434
  error: stream.error,
3354
3435
  isConnected: stream.isConnected,
3355
3436
  partial: stream.partial,
3437
+ reconnect: stream.reconnect,
3356
3438
  sessionId: stream.sessionId,
3357
3439
  scenarioId: stream.scenarioId,
3358
3440
  status: stream.status,
@@ -3453,6 +3535,9 @@ var createVoiceController = (path, options = {}) => {
3453
3535
  get recordingError() {
3454
3536
  return state.recordingError;
3455
3537
  },
3538
+ get reconnect() {
3539
+ return state.reconnect;
3540
+ },
3456
3541
  sendAudio: (audio) => stream.sendAudio(audio),
3457
3542
  get sessionId() {
3458
3543
  return state.sessionId;
@@ -3502,6 +3587,7 @@ function useVoiceController(path, options = {}) {
3502
3587
  const isConnected = ref6(false);
3503
3588
  const isRecording = ref6(false);
3504
3589
  const partial = ref6("");
3590
+ const reconnect = shallowRef9(controller.reconnect);
3505
3591
  const recordingError = ref6(null);
3506
3592
  const sessionId = ref6(controller.sessionId);
3507
3593
  const status = ref6(controller.status);
@@ -3513,6 +3599,7 @@ function useVoiceController(path, options = {}) {
3513
3599
  isConnected.value = controller.isConnected;
3514
3600
  isRecording.value = controller.isRecording;
3515
3601
  partial.value = controller.partial;
3602
+ reconnect.value = controller.reconnect;
3516
3603
  recordingError.value = controller.recordingError;
3517
3604
  sessionId.value = controller.sessionId;
3518
3605
  status.value = controller.status;
@@ -3535,6 +3622,7 @@ function useVoiceController(path, options = {}) {
3535
3622
  isConnected,
3536
3623
  isRecording,
3537
3624
  partial,
3625
+ reconnect,
3538
3626
  recordingError,
3539
3627
  sendAudio: (audio) => controller.sendAudio(audio),
3540
3628
  sessionId,
@@ -19,6 +19,7 @@ export declare function useVoiceController<TResult = unknown>(path: string, opti
19
19
  isConnected: import("vue").Ref<boolean, boolean>;
20
20
  isRecording: import("vue").Ref<boolean, boolean>;
21
21
  partial: import("vue").Ref<string, string>;
22
+ reconnect: import("vue").ShallowRef<import("..").VoiceReconnectClientState, import("..").VoiceReconnectClientState>;
22
23
  recordingError: import("vue").Ref<string | null, string | null>;
23
24
  sendAudio: (audio: Uint8Array | ArrayBuffer) => void;
24
25
  sessionId: import("vue").Ref<string | null, string | null>;
@@ -19,6 +19,7 @@ export declare function useVoiceStream<TResult = unknown>(path: string, options?
19
19
  error: import("vue").Ref<string | null, string | null>;
20
20
  isConnected: import("vue").Ref<boolean, boolean>;
21
21
  partial: import("vue").Ref<string, string>;
22
+ reconnect: import("vue").ShallowRef<import("..").VoiceReconnectClientState, import("..").VoiceReconnectClientState>;
22
23
  sendAudio: (audio: Uint8Array | ArrayBuffer) => void;
23
24
  sessionId: import("vue").Ref<string | null, string | null>;
24
25
  status: import("vue").Ref<import("..").VoiceSessionStatus | "idle", import("..").VoiceSessionStatus | "idle">;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.129",
3
+ "version": "0.0.22-beta.130",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",