@absolutejs/voice 0.0.22-beta.557 → 0.0.22-beta.559

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.
@@ -923,6 +923,14 @@ export type CreateVoiceSessionOptions<TContext = unknown, TSession extends Voice
923
923
  * Set to "" to opt out entirely.
924
924
  */
925
925
  defaultSilentTurnAck?: string;
926
+ /**
927
+ * Hard timeout on a single `route.onTurn` call. If onTurn hasn't resolved
928
+ * in this many ms, it's rejected with a hard-timeout error which falls
929
+ * through to defaultSilentTurnAck. Default 45s — generous for normal
930
+ * conversational LLM calls (1-3s typical), but catches hangs where the
931
+ * model adapter's own timeout doesn't fire. Set to 0 to disable.
932
+ */
933
+ routeOnTurnTimeoutMs?: number;
926
934
  assistantMode?: import("./assistantMode").VoiceAssistantMode;
927
935
  modalities?: ReadonlyArray<"audio" | "text">;
928
936
  prosody?: VoiceTTSProsody;
package/dist/index.js CHANGED
@@ -4273,6 +4273,17 @@ var createVoiceSession = (options) => {
4273
4273
  turnId: cancelledTurnId
4274
4274
  });
4275
4275
  }
4276
+ try {
4277
+ ttsSession = null;
4278
+ ttsSessionPromise = null;
4279
+ await activeSession.close("post-cancel-reset");
4280
+ } catch (error) {
4281
+ logger.warn("voice tts adapter close-after-cancel failed", {
4282
+ error: toError(error).message,
4283
+ reason,
4284
+ sessionId: options.id
4285
+ });
4286
+ }
4276
4287
  };
4277
4288
  const sendAssistantAudio = async (chunk, input) => {
4278
4289
  const normalizedChunk = chunk instanceof Uint8Array ? new Uint8Array(chunk) : chunk instanceof ArrayBuffer ? new Uint8Array(chunk.slice(0)) : new Uint8Array(chunk.buffer.slice(chunk.byteOffset, chunk.byteOffset + chunk.byteLength));
@@ -5313,10 +5324,11 @@ var createVoiceSession = (options) => {
5313
5324
  });
5314
5325
  }, fillerDelayMs);
5315
5326
  }
5327
+ const onTurnTimeoutMs = options.routeOnTurnTimeoutMs ?? 45000;
5316
5328
  let committedOutput;
5317
5329
  const onTurnStartedAt = Date.now();
5318
5330
  try {
5319
- committedOutput = await options.route.onTurn({
5331
+ const onTurnPromise = options.route.onTurn({
5320
5332
  api,
5321
5333
  context: options.context,
5322
5334
  liveOps: liveOpsControl ? {
@@ -5327,6 +5339,25 @@ var createVoiceSession = (options) => {
5327
5339
  session,
5328
5340
  turn
5329
5341
  });
5342
+ if (onTurnTimeoutMs > 0) {
5343
+ let timer = null;
5344
+ const timeoutPromise = new Promise((_resolve, reject) => {
5345
+ timer = setTimeout(() => {
5346
+ reject(new Error(`route.onTurn hard-timeout after ${onTurnTimeoutMs}ms`));
5347
+ }, onTurnTimeoutMs);
5348
+ });
5349
+ try {
5350
+ committedOutput = await Promise.race([
5351
+ onTurnPromise,
5352
+ timeoutPromise
5353
+ ]);
5354
+ } finally {
5355
+ if (timer)
5356
+ clearTimeout(timer);
5357
+ }
5358
+ } else {
5359
+ committedOutput = await onTurnPromise;
5360
+ }
5330
5361
  } catch (error) {
5331
5362
  const message = toError(error).message;
5332
5363
  logger.warn("voice route.onTurn failed", {
@@ -6110,6 +6110,17 @@ var createVoiceSession = (options) => {
6110
6110
  turnId: cancelledTurnId
6111
6111
  });
6112
6112
  }
6113
+ try {
6114
+ ttsSession = null;
6115
+ ttsSessionPromise = null;
6116
+ await activeSession.close("post-cancel-reset");
6117
+ } catch (error) {
6118
+ logger.warn("voice tts adapter close-after-cancel failed", {
6119
+ error: toError(error).message,
6120
+ reason,
6121
+ sessionId: options.id
6122
+ });
6123
+ }
6113
6124
  };
6114
6125
  const sendAssistantAudio = async (chunk, input) => {
6115
6126
  const normalizedChunk = chunk instanceof Uint8Array ? new Uint8Array(chunk) : chunk instanceof ArrayBuffer ? new Uint8Array(chunk.slice(0)) : new Uint8Array(chunk.buffer.slice(chunk.byteOffset, chunk.byteOffset + chunk.byteLength));
@@ -7150,10 +7161,11 @@ var createVoiceSession = (options) => {
7150
7161
  });
7151
7162
  }, fillerDelayMs);
7152
7163
  }
7164
+ const onTurnTimeoutMs = options.routeOnTurnTimeoutMs ?? 45000;
7153
7165
  let committedOutput;
7154
7166
  const onTurnStartedAt = Date.now();
7155
7167
  try {
7156
- committedOutput = await options.route.onTurn({
7168
+ const onTurnPromise = options.route.onTurn({
7157
7169
  api,
7158
7170
  context: options.context,
7159
7171
  liveOps: liveOpsControl ? {
@@ -7164,6 +7176,25 @@ var createVoiceSession = (options) => {
7164
7176
  session,
7165
7177
  turn
7166
7178
  });
7179
+ if (onTurnTimeoutMs > 0) {
7180
+ let timer = null;
7181
+ const timeoutPromise = new Promise((_resolve, reject) => {
7182
+ timer = setTimeout(() => {
7183
+ reject(new Error(`route.onTurn hard-timeout after ${onTurnTimeoutMs}ms`));
7184
+ }, onTurnTimeoutMs);
7185
+ });
7186
+ try {
7187
+ committedOutput = await Promise.race([
7188
+ onTurnPromise,
7189
+ timeoutPromise
7190
+ ]);
7191
+ } finally {
7192
+ if (timer)
7193
+ clearTimeout(timer);
7194
+ }
7195
+ } else {
7196
+ committedOutput = await onTurnPromise;
7197
+ }
7167
7198
  } catch (error) {
7168
7199
  const message = toError(error).message;
7169
7200
  logger.warn("voice route.onTurn failed", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.557",
3
+ "version": "0.0.22-beta.559",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",