@absolutejs/voice 0.0.22-beta.653 → 0.0.22-beta.654

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/vue/index.js CHANGED
@@ -10705,8 +10705,11 @@ var serverMessageToAction = (message) => {
10705
10705
  case "error":
10706
10706
  return {
10707
10707
  message: normalizeErrorMessage(message.message),
10708
+ recoverable: message.recoverable,
10708
10709
  type: "error"
10709
10710
  };
10711
+ case "call_control_ack":
10712
+ return null;
10710
10713
  case "final":
10711
10714
  return {
10712
10715
  transcript: message.transcript,
@@ -10834,14 +10837,17 @@ var WS_FORBIDDEN = 4403;
10834
10837
  var DEFAULT_MAX_RECONNECT_ATTEMPTS = 15;
10835
10838
  var DEFAULT_PING_INTERVAL = 30000;
10836
10839
  var DEFAULT_RECONNECT_RESET_AFTER_MS = 30000;
10840
+ var DEFAULT_CALL_CONTROL_ACK_TIMEOUT_MS = 5000;
10841
+ var MAX_PENDING_CONTROL_MESSAGES = 100;
10837
10842
  var RECONNECT_BASE_DELAY_MS = 500;
10838
10843
  var DEFAULT_RECONNECT_MAX_DELAY_MS = 8000;
10839
10844
  var computeVoiceReconnectDelayMs = (attempt, baseMs, maxDelayMs) => Math.min(maxDelayMs, baseMs * 2 ** (Math.max(1, attempt) - 1));
10840
10845
  var DEFAULT_SCENARIO_QUERY_PARAM = "scenarioId";
10841
10846
  var noop = () => {};
10847
+ var noopAsync = () => Promise.resolve();
10842
10848
  var noopUnsubscribe = () => noop;
10843
10849
  var NOOP_CONNECTION = {
10844
- callControl: noop,
10850
+ callControl: noopAsync,
10845
10851
  close: noop,
10846
10852
  disconnect: noop,
10847
10853
  endTurn: noop,
@@ -10879,6 +10885,7 @@ var isVoiceServerMessage = (value) => {
10879
10885
  case "audio":
10880
10886
  case "assistant":
10881
10887
  case "call_lifecycle":
10888
+ case "call_control_ack":
10882
10889
  case "complete":
10883
10890
  case "connection":
10884
10891
  case "error":
@@ -10909,6 +10916,7 @@ var createVoiceConnection = (path, options = {}) => {
10909
10916
  return NOOP_CONNECTION;
10910
10917
  }
10911
10918
  const listeners = new Set;
10919
+ const pendingCallControls = new Map;
10912
10920
  const shouldReconnect = options.reconnect !== false;
10913
10921
  const maxReconnectAttempts = options.maxReconnectAttempts ?? DEFAULT_MAX_RECONNECT_ATTEMPTS;
10914
10922
  const reconnectMaxDelayMs = options.reconnectMaxDelayMs ?? DEFAULT_RECONNECT_MAX_DELAY_MS;
@@ -10954,6 +10962,13 @@ var createVoiceConnection = (path, options = {}) => {
10954
10962
  }
10955
10963
  }
10956
10964
  };
10965
+ const rejectPendingCallControls = (message) => {
10966
+ for (const pending of pendingCallControls.values()) {
10967
+ clearTimeout(pending.timeout);
10968
+ pending.reject(new Error(message));
10969
+ }
10970
+ pendingCallControls.clear();
10971
+ };
10957
10972
  const scheduleReconnect = () => {
10958
10973
  state.reconnectAttempts += 1;
10959
10974
  const delayMs = computeReconnectDelayMs(state.reconnectAttempts);
@@ -10968,7 +10983,7 @@ var createVoiceConnection = (path, options = {}) => {
10968
10983
  },
10969
10984
  type: "connection"
10970
10985
  });
10971
- state.reconnectTimeout = setTimeout(() => {
10986
+ state.reconnectTimeout = setTimeout(async () => {
10972
10987
  if (state.reconnectAttempts > maxReconnectAttempts) {
10973
10988
  emitConnection({
10974
10989
  reconnect: {
@@ -10980,6 +10995,28 @@ var createVoiceConnection = (path, options = {}) => {
10980
10995
  });
10981
10996
  return;
10982
10997
  }
10998
+ try {
10999
+ await options.prepareReconnect?.({
11000
+ attempt: state.reconnectAttempts,
11001
+ path,
11002
+ scenarioId: state.scenarioId,
11003
+ sessionId: state.sessionId
11004
+ });
11005
+ } catch {
11006
+ if (state.reconnectAttempts < maxReconnectAttempts) {
11007
+ scheduleReconnect();
11008
+ } else {
11009
+ emitConnection({
11010
+ reconnect: {
11011
+ attempts: state.reconnectAttempts,
11012
+ maxAttempts: maxReconnectAttempts,
11013
+ status: "exhausted"
11014
+ },
11015
+ type: "connection"
11016
+ });
11017
+ }
11018
+ return;
11019
+ }
10983
11020
  connect();
10984
11021
  }, delayMs);
10985
11022
  };
@@ -11034,6 +11071,18 @@ var createVoiceConnection = (path, options = {}) => {
11034
11071
  state.sessionId = parsed.sessionId;
11035
11072
  state.scenarioId = parsed.scenarioId ?? state.scenarioId;
11036
11073
  }
11074
+ if (parsed.type === "call_control_ack") {
11075
+ const pending = pendingCallControls.get(parsed.requestId);
11076
+ if (pending) {
11077
+ clearTimeout(pending.timeout);
11078
+ pendingCallControls.delete(parsed.requestId);
11079
+ if (parsed.ok) {
11080
+ pending.resolve();
11081
+ } else {
11082
+ pending.reject(new Error(parsed.message ?? `${parsed.action} was not applied`));
11083
+ }
11084
+ }
11085
+ }
11037
11086
  listeners.forEach((listener) => listener(parsed));
11038
11087
  };
11039
11088
  ws.onclose = (event) => {
@@ -11043,6 +11092,14 @@ var createVoiceConnection = (path, options = {}) => {
11043
11092
  state.isConnected = false;
11044
11093
  clearTimers();
11045
11094
  const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && state.reconnectAttempts < maxReconnectAttempts;
11095
+ const authorizationFailure = event.code === WS_UNAUTHORIZED || event.code === WS_FORBIDDEN;
11096
+ if (authorizationFailure) {
11097
+ listeners.forEach((listener) => listener({
11098
+ message: event.code === WS_UNAUTHORIZED ? "Voice authorization expired or was rejected." : "Voice authorization was forbidden.",
11099
+ recoverable: false,
11100
+ type: "error"
11101
+ }));
11102
+ }
11046
11103
  if (reconnectable) {
11047
11104
  scheduleReconnect();
11048
11105
  } else if (shouldReconnect && event.code !== WS_NORMAL_CLOSURE) {
@@ -11064,6 +11121,12 @@ var createVoiceConnection = (path, options = {}) => {
11064
11121
  state.ws.send(value);
11065
11122
  return;
11066
11123
  }
11124
+ if (typeof value !== "string") {
11125
+ return;
11126
+ }
11127
+ if (state.pendingMessages.length >= MAX_PENDING_CONTROL_MESSAGES) {
11128
+ state.pendingMessages.shift();
11129
+ }
11067
11130
  state.pendingMessages.push(value);
11068
11131
  };
11069
11132
  const send = (message) => {
@@ -11089,13 +11152,23 @@ var createVoiceConnection = (path, options = {}) => {
11089
11152
  send({ type: "end_turn" });
11090
11153
  };
11091
11154
  const callControl = (message) => {
11155
+ const requestId = crypto.randomUUID();
11156
+ const { promise, reject, resolve } = Promise.withResolvers();
11157
+ const timeout = setTimeout(() => {
11158
+ pendingCallControls.delete(requestId);
11159
+ reject(new Error(`${message.action} acknowledgement timed out`));
11160
+ }, DEFAULT_CALL_CONTROL_ACK_TIMEOUT_MS);
11161
+ pendingCallControls.set(requestId, { reject, resolve, timeout });
11092
11162
  send({
11093
11163
  ...message,
11164
+ requestId,
11094
11165
  type: "call_control"
11095
11166
  });
11167
+ return promise;
11096
11168
  };
11097
11169
  const close = (reason = "client-close") => {
11098
11170
  clearTimers();
11171
+ rejectPendingCallControls("Voice connection closed");
11099
11172
  if (state.ws) {
11100
11173
  const ws = state.ws;
11101
11174
  state.ws = null;
@@ -11109,6 +11182,7 @@ var createVoiceConnection = (path, options = {}) => {
11109
11182
  };
11110
11183
  const disconnect = () => {
11111
11184
  clearTimers();
11185
+ rejectPendingCallControls("Voice connection disconnected");
11112
11186
  if (state.ws) {
11113
11187
  const ws = state.ws;
11114
11188
  state.ws = null;
@@ -11271,7 +11345,8 @@ var createVoiceStreamStore = () => {
11271
11345
  case "error":
11272
11346
  state = {
11273
11347
  ...state,
11274
- error: action.message
11348
+ error: action.message,
11349
+ ...action.recoverable === false ? { isConnected: false, status: "failed" } : {}
11275
11350
  };
11276
11351
  break;
11277
11352
  case "final":
@@ -11411,7 +11486,7 @@ var createVoiceStream = (path, options = {}) => {
11411
11486
  return store.getSnapshot().call;
11412
11487
  },
11413
11488
  callControl(message) {
11414
- connection.callControl(message);
11489
+ return connection.callControl(message);
11415
11490
  },
11416
11491
  close(reason) {
11417
11492
  unsubscribeConnection();
@@ -22,7 +22,7 @@ export declare function useVoiceStream<TResult = unknown>(path: string, options?
22
22
  sessionMetadata: import("vue").ShallowRef<Record<string, unknown> | null, Record<string, unknown> | null>;
23
23
  status: import("vue").Ref<import("..").VoiceSessionStatus | "idle", import("..").VoiceSessionStatus | "idle">;
24
24
  turns: import("vue").ShallowRef<VoiceTurnRecord<TResult>[], VoiceTurnRecord<TResult>[]>;
25
- callControl: (message: Parameters<(message: Omit<import("..").VoiceClientCallControlMessage, "type">) => void>[0]) => void;
25
+ callControl: (message: Parameters<(message: Omit<import("..").VoiceClientCallControlMessage, "requestId" | "type">) => Promise<void>>[0]) => Promise<void>;
26
26
  close: () => void;
27
27
  endTurn: () => void;
28
28
  sendAudio: (audio: Uint8Array | ArrayBuffer) => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.653",
3
+ "version": "0.0.22-beta.654",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",