@absolutejs/voice 0.0.22-beta.594 → 0.0.22-beta.596

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/index.js CHANGED
@@ -3723,6 +3723,8 @@ var EXTENDED_VENDOR_COMMIT_SILENCE_THRESHOLD_MS = 200;
3723
3723
  var MAX_VENDOR_COMMIT_GRACE_MS = 1200;
3724
3724
  var STT_RECONNECT_FLAP_WINDOW_MS = 4000;
3725
3725
  var MAX_STT_RECONNECTS_IN_FLAP_WINDOW = 3;
3726
+ var STT_HEALTH_STALE_MS = 6000;
3727
+ var STT_HEALTH_SPEECH_GAP_MS = 2000;
3726
3728
  var DEFAULT_FORMAT = {
3727
3729
  channels: 1,
3728
3730
  container: "raw",
@@ -3972,6 +3974,8 @@ var createVoiceSession = (options) => {
3972
3974
  let activeAdapterGeneration = 0;
3973
3975
  let sttReconnectCount = 0;
3974
3976
  let lastSttReconnectAt = 0;
3977
+ let lastSpeechEnergyAt = 0;
3978
+ let sttHealthPhaseStart = 0;
3975
3979
  let activeTTSTurnId;
3976
3980
  let assistantSpeechEndsAt = 0;
3977
3981
  let lastAssistantAudioAt = 0;
@@ -4639,13 +4643,11 @@ var createVoiceSession = (options) => {
4639
4643
  if (!didComplete) {
4640
4644
  return;
4641
4645
  }
4642
- if (shouldInvokeOnComplete) {
4643
- await options.route.onComplete({
4644
- api,
4645
- context: options.context,
4646
- session
4647
- });
4648
- }
4646
+ const onCompletePromise = shouldInvokeOnComplete ? Promise.resolve(options.route.onComplete({
4647
+ api,
4648
+ context: options.context,
4649
+ session
4650
+ })) : Promise.resolve();
4649
4651
  if (disposition === "completed") {
4650
4652
  await drainAssistantSpeech(lastTtsSendAt);
4651
4653
  await assistantAudioQueue;
@@ -4708,6 +4710,7 @@ var createVoiceSession = (options) => {
4708
4710
  session
4709
4711
  });
4710
4712
  }
4713
+ await onCompletePromise;
4711
4714
  await options.route.onCallEnd?.({
4712
4715
  api,
4713
4716
  context: options.context,
@@ -6304,6 +6307,30 @@ var createVoiceSession = (options) => {
6304
6307
  } else {
6305
6308
  clearSilenceTimer();
6306
6309
  }
6310
+ const nowMs = Date.now();
6311
+ if (nowMs - lastSpeechEnergyAt > STT_HEALTH_SPEECH_GAP_MS) {
6312
+ sttHealthPhaseStart = nowMs;
6313
+ }
6314
+ lastSpeechEnergyAt = nowMs;
6315
+ const lastTranscriptAt = latest.currentTurn.lastTranscriptAt ?? 0;
6316
+ if (!options.realtime && sttSession && lastTranscriptAt < sttHealthPhaseStart && nowMs - sttHealthPhaseStart >= STT_HEALTH_STALE_MS) {
6317
+ sttReconnectCount = nowMs - lastSttReconnectAt < STT_RECONNECT_FLAP_WINDOW_MS ? sttReconnectCount + 1 : 1;
6318
+ lastSttReconnectAt = nowMs;
6319
+ sttHealthPhaseStart = nowMs;
6320
+ if (sttReconnectCount <= MAX_STT_RECONNECTS_IN_FLAP_WINDOW) {
6321
+ await appendTrace({
6322
+ payload: {
6323
+ action: "stt-health-reconnect",
6324
+ attempt: sttReconnectCount,
6325
+ reason: `no transcript for ${STT_HEALTH_STALE_MS}ms of continuous speech`
6326
+ },
6327
+ session: latest,
6328
+ type: "session.error"
6329
+ });
6330
+ await closeAdapter("stt stale; health-reconnect");
6331
+ return;
6332
+ }
6333
+ }
6307
6334
  } else if (speechDetected) {
6308
6335
  backchannelDriver?.noteSilence();
6309
6336
  const currentSession = await readSession();
@@ -5950,6 +5950,8 @@ var EXTENDED_VENDOR_COMMIT_SILENCE_THRESHOLD_MS = 200;
5950
5950
  var MAX_VENDOR_COMMIT_GRACE_MS = 1200;
5951
5951
  var STT_RECONNECT_FLAP_WINDOW_MS = 4000;
5952
5952
  var MAX_STT_RECONNECTS_IN_FLAP_WINDOW = 3;
5953
+ var STT_HEALTH_STALE_MS = 6000;
5954
+ var STT_HEALTH_SPEECH_GAP_MS = 2000;
5953
5955
  var DEFAULT_FORMAT = {
5954
5956
  channels: 1,
5955
5957
  container: "raw",
@@ -6199,6 +6201,8 @@ var createVoiceSession = (options) => {
6199
6201
  let activeAdapterGeneration = 0;
6200
6202
  let sttReconnectCount = 0;
6201
6203
  let lastSttReconnectAt = 0;
6204
+ let lastSpeechEnergyAt = 0;
6205
+ let sttHealthPhaseStart = 0;
6202
6206
  let activeTTSTurnId;
6203
6207
  let assistantSpeechEndsAt = 0;
6204
6208
  let lastAssistantAudioAt = 0;
@@ -6866,13 +6870,11 @@ var createVoiceSession = (options) => {
6866
6870
  if (!didComplete) {
6867
6871
  return;
6868
6872
  }
6869
- if (shouldInvokeOnComplete) {
6870
- await options.route.onComplete({
6871
- api,
6872
- context: options.context,
6873
- session
6874
- });
6875
- }
6873
+ const onCompletePromise = shouldInvokeOnComplete ? Promise.resolve(options.route.onComplete({
6874
+ api,
6875
+ context: options.context,
6876
+ session
6877
+ })) : Promise.resolve();
6876
6878
  if (disposition === "completed") {
6877
6879
  await drainAssistantSpeech(lastTtsSendAt);
6878
6880
  await assistantAudioQueue;
@@ -6935,6 +6937,7 @@ var createVoiceSession = (options) => {
6935
6937
  session
6936
6938
  });
6937
6939
  }
6940
+ await onCompletePromise;
6938
6941
  await options.route.onCallEnd?.({
6939
6942
  api,
6940
6943
  context: options.context,
@@ -8531,6 +8534,30 @@ var createVoiceSession = (options) => {
8531
8534
  } else {
8532
8535
  clearSilenceTimer();
8533
8536
  }
8537
+ const nowMs = Date.now();
8538
+ if (nowMs - lastSpeechEnergyAt > STT_HEALTH_SPEECH_GAP_MS) {
8539
+ sttHealthPhaseStart = nowMs;
8540
+ }
8541
+ lastSpeechEnergyAt = nowMs;
8542
+ const lastTranscriptAt = latest.currentTurn.lastTranscriptAt ?? 0;
8543
+ if (!options.realtime && sttSession && lastTranscriptAt < sttHealthPhaseStart && nowMs - sttHealthPhaseStart >= STT_HEALTH_STALE_MS) {
8544
+ sttReconnectCount = nowMs - lastSttReconnectAt < STT_RECONNECT_FLAP_WINDOW_MS ? sttReconnectCount + 1 : 1;
8545
+ lastSttReconnectAt = nowMs;
8546
+ sttHealthPhaseStart = nowMs;
8547
+ if (sttReconnectCount <= MAX_STT_RECONNECTS_IN_FLAP_WINDOW) {
8548
+ await appendTrace({
8549
+ payload: {
8550
+ action: "stt-health-reconnect",
8551
+ attempt: sttReconnectCount,
8552
+ reason: `no transcript for ${STT_HEALTH_STALE_MS}ms of continuous speech`
8553
+ },
8554
+ session: latest,
8555
+ type: "session.error"
8556
+ });
8557
+ await closeAdapter("stt stale; health-reconnect");
8558
+ return;
8559
+ }
8560
+ }
8534
8561
  } else if (speechDetected) {
8535
8562
  backchannelDriver?.noteSilence();
8536
8563
  const currentSession = await readSession();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.594",
3
+ "version": "0.0.22-beta.596",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",