@absolutejs/voice 0.0.22-beta.664 → 0.0.22-beta.666

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.
@@ -162,9 +162,8 @@ export type TelnyxVoiceRoutesOptions<TContext = unknown, TSession extends VoiceS
162
162
  };
163
163
  };
164
164
  export type VoiceTelnyxWebhookEventStore = {
165
- claim?: (eventId: string) => Promise<boolean> | boolean;
166
- has: (eventId: string) => Promise<boolean> | boolean;
167
- set: (eventId: string) => Promise<void> | void;
165
+ /** Atomically accepts an unseen event id and rejects every duplicate. */
166
+ claim: (eventId: string) => Promise<boolean> | boolean;
168
167
  };
169
168
  export type VoiceTelnyxWebhookEventStoreOptions = {
170
169
  ttlSeconds?: number;
@@ -182,7 +181,7 @@ export type VoicePostgresTelnyxWebhookEventStoreOptions = VoiceTelnyxWebhookEven
182
181
  tableName?: string;
183
182
  tablePrefix?: string;
184
183
  };
185
- export type VoiceRedisTelnyxWebhookEventClient = Pick<RedisClient, "exists" | "set">;
184
+ export type VoiceRedisTelnyxWebhookEventClient = Pick<RedisClient, "set">;
186
185
  export type VoiceRedisTelnyxWebhookEventStoreOptions = VoiceTelnyxWebhookEventStoreOptions & {
187
186
  client?: VoiceRedisTelnyxWebhookEventClient;
188
187
  keyPrefix?: string;
@@ -2914,6 +2914,11 @@ var serverMessageToAction = (message) => {
2914
2914
  transcript: message.transcript,
2915
2915
  type: "partial"
2916
2916
  };
2917
+ case "playback_rate":
2918
+ return {
2919
+ rate: message.rate,
2920
+ type: "playback_rate"
2921
+ };
2917
2922
  case "replay":
2918
2923
  return {
2919
2924
  assistantTexts: message.assistantTexts,
@@ -3096,6 +3101,7 @@ var isVoiceServerMessage = (value) => {
3096
3101
  case "error":
3097
3102
  case "final":
3098
3103
  case "partial":
3104
+ case "playback_rate":
3099
3105
  case "pong":
3100
3106
  case "replay":
3101
3107
  case "session":
@@ -3522,6 +3528,7 @@ var createInitialState2 = () => ({
3522
3528
  isConnected: false,
3523
3529
  paused: false,
3524
3530
  partial: "",
3531
+ playbackRate: null,
3525
3532
  reconnect: createInitialReconnectState(),
3526
3533
  scenarioId: null,
3527
3534
  sessionId: null,
@@ -3630,6 +3637,12 @@ var createVoiceStreamStore = () => {
3630
3637
  partial: joinPartial(turnFinalText, action.transcript.text)
3631
3638
  };
3632
3639
  break;
3640
+ case "playback_rate":
3641
+ state = {
3642
+ ...state,
3643
+ playbackRate: Math.max(0.5, Math.min(2, action.rate))
3644
+ };
3645
+ break;
3633
3646
  case "replay":
3634
3647
  turnFinalText = action.partial;
3635
3648
  state = {
@@ -3795,6 +3808,9 @@ var createVoiceStream = (path, options = {}) => {
3795
3808
  get partial() {
3796
3809
  return store.getSnapshot().partial;
3797
3810
  },
3811
+ get playbackRate() {
3812
+ return store.getSnapshot().playbackRate;
3813
+ },
3798
3814
  get paused() {
3799
3815
  return store.getSnapshot().paused;
3800
3816
  },
@@ -4167,6 +4183,7 @@ var createInitialState3 = (stream) => ({
4167
4183
  paused: stream.paused,
4168
4184
  pauseExpiresAt: stream.pauseExpiresAt,
4169
4185
  partial: stream.partial,
4186
+ playbackRate: stream.playbackRate,
4170
4187
  reconnect: stream.reconnect,
4171
4188
  recordingError: null,
4172
4189
  sessionId: stream.sessionId,
@@ -4204,6 +4221,7 @@ var createVoiceController = (path, options = {}) => {
4204
4221
  paused: stream.paused,
4205
4222
  pauseExpiresAt: stream.pauseExpiresAt,
4206
4223
  partial: stream.partial,
4224
+ playbackRate: stream.playbackRate,
4207
4225
  reconnect: stream.reconnect,
4208
4226
  sessionId: stream.sessionId,
4209
4227
  sessionMetadata: stream.sessionMetadata,
@@ -4350,6 +4368,9 @@ var createVoiceController = (path, options = {}) => {
4350
4368
  get partial() {
4351
4369
  return state.partial;
4352
4370
  },
4371
+ get playbackRate() {
4372
+ return state.playbackRate;
4373
+ },
4353
4374
  get paused() {
4354
4375
  return state.paused;
4355
4376
  },
@@ -4459,10 +4480,18 @@ var createVoiceDuplexController = (path, options = {}) => {
4459
4480
  }
4460
4481
  });
4461
4482
  const audioPlayer = createVoiceAudioPlayer(controller, options.audioPlayer);
4483
+ let appliedPlaybackRate = controller.playbackRate;
4484
+ const unsubscribePlaybackRate = controller.subscribe(() => {
4485
+ if (controller.playbackRate !== null && controller.playbackRate !== appliedPlaybackRate) {
4486
+ appliedPlaybackRate = controller.playbackRate;
4487
+ audioPlayer.setPlaybackRate(controller.playbackRate);
4488
+ }
4489
+ });
4462
4490
  bargeInBinding = bindVoiceBargeIn(controller, audioPlayer, options.bargeIn);
4463
4491
  const close = () => {
4464
4492
  bargeInBinding?.close();
4465
4493
  bargeInBinding = null;
4494
+ unsubscribePlaybackRate();
4466
4495
  audioPlayer.close();
4467
4496
  controller.close();
4468
4497
  };
@@ -7250,6 +7279,11 @@ var createVoiceSession = (options) => {
7250
7279
  let lastSpeechEnergyAt = 0;
7251
7280
  let sttHealthPhaseStart = 0;
7252
7281
  let sttDeafConfirmedAt = 0;
7282
+ const resetSttHealthWindow = () => {
7283
+ lastSpeechEnergyAt = 0;
7284
+ sttHealthPhaseStart = 0;
7285
+ sttDeafConfirmedAt = 0;
7286
+ };
7253
7287
  let lastSttRecoverySpokenAt = 0;
7254
7288
  let activeTTSTurnId;
7255
7289
  let assistantSpeechEndsAt = 0;
@@ -9681,6 +9715,7 @@ var createVoiceSession = (options) => {
9681
9715
  markTurnCommitted(currentSession, finalText, transcripts);
9682
9716
  });
9683
9717
  speechDetected = false;
9718
+ resetSttHealthWindow();
9684
9719
  rewindFallbackTurnAudio();
9685
9720
  logger.info("voice turn committed", {
9686
9721
  reason,
@@ -10008,30 +10043,35 @@ var createVoiceSession = (options) => {
10008
10043
  clearSilenceTimer();
10009
10044
  }
10010
10045
  const nowMs = Date.now();
10011
- if (nowMs - lastSpeechEnergyAt > STT_HEALTH_SPEECH_GAP_MS) {
10012
- sttHealthPhaseStart = nowMs;
10013
- }
10014
- lastSpeechEnergyAt = nowMs;
10015
- const lastTranscriptAt = latest.currentTurn.lastTranscriptAt ?? 0;
10016
- if (!options.realtime && sttSession && lastTranscriptAt < sttHealthPhaseStart && nowMs - sttHealthPhaseStart >= STT_HEALTH_STALE_MS) {
10017
- sttDeafConfirmedAt = nowMs;
10018
- sttReconnectCount = nowMs - lastSttReconnectAt < STT_RECONNECT_FLAP_WINDOW_MS ? sttReconnectCount + 1 : 1;
10019
- lastSttReconnectAt = nowMs;
10020
- sttHealthPhaseStart = nowMs;
10021
- maybeSpeakSttRecovery(nowMs, latest);
10022
- if (sttReconnectCount <= MAX_STT_RECONNECTS_IN_FLAP_WINDOW) {
10023
- await appendTrace({
10024
- payload: {
10025
- action: "stt-health-reconnect",
10026
- attempt: sttReconnectCount,
10027
- classification: "stt-deaf",
10028
- reason: `no transcript for ${STT_HEALTH_STALE_MS}ms of continuous speech`
10029
- },
10030
- session: latest,
10031
- type: "session.error"
10032
- });
10033
- await closeAdapter("stt stale; health-reconnect");
10034
- return;
10046
+ if (assistantTurnInProgress) {
10047
+ resetSttHealthWindow();
10048
+ } else {
10049
+ if (nowMs - lastSpeechEnergyAt > STT_HEALTH_SPEECH_GAP_MS) {
10050
+ sttHealthPhaseStart = nowMs;
10051
+ }
10052
+ lastSpeechEnergyAt = nowMs;
10053
+ const lastTranscriptAt = latest.currentTurn.lastTranscriptAt ?? 0;
10054
+ if (!options.realtime && sttSession && lastTranscriptAt < sttHealthPhaseStart && nowMs - sttHealthPhaseStart >= STT_HEALTH_STALE_MS) {
10055
+ sttDeafConfirmedAt = nowMs;
10056
+ sttReconnectCount = nowMs - lastSttReconnectAt < STT_RECONNECT_FLAP_WINDOW_MS ? sttReconnectCount + 1 : 1;
10057
+ lastSttReconnectAt = nowMs;
10058
+ sttHealthPhaseStart = nowMs;
10059
+ maybeSpeakSttRecovery(nowMs, latest);
10060
+ if (sttReconnectCount <= MAX_STT_RECONNECTS_IN_FLAP_WINDOW) {
10061
+ await appendTrace({
10062
+ payload: {
10063
+ action: "stt-health-reconnect",
10064
+ attempt: sttReconnectCount,
10065
+ classification: "stt-deaf",
10066
+ recoverable: true,
10067
+ reason: `no transcript for ${STT_HEALTH_STALE_MS}ms of continuous speech`
10068
+ },
10069
+ session: latest,
10070
+ type: "session.error"
10071
+ });
10072
+ await closeAdapter("stt stale; health-reconnect");
10073
+ return;
10074
+ }
10035
10075
  }
10036
10076
  }
10037
10077
  } else if (speechDetected) {
@@ -10161,6 +10201,11 @@ var createVoiceSession = (options) => {
10161
10201
  speechThreshold: turnDetection.speechThreshold,
10162
10202
  transcriptStabilityMs: turnDetection.transcriptStabilityMs
10163
10203
  };
10204
+ }),
10205
+ setPlaybackRate: async (rate) => runSerial("api.setPlaybackRate", async () => {
10206
+ const clamped = Math.max(0.5, Math.min(2, Number(rate) || 1));
10207
+ await send({ rate: clamped, type: "playback_rate" });
10208
+ return clamped;
10164
10209
  })
10165
10210
  };
10166
10211
  return api;
package/dist/vue/index.js CHANGED
@@ -10720,6 +10720,11 @@ var serverMessageToAction = (message) => {
10720
10720
  transcript: message.transcript,
10721
10721
  type: "partial"
10722
10722
  };
10723
+ case "playback_rate":
10724
+ return {
10725
+ rate: message.rate,
10726
+ type: "playback_rate"
10727
+ };
10723
10728
  case "replay":
10724
10729
  return {
10725
10730
  assistantTexts: message.assistantTexts,
@@ -10902,6 +10907,7 @@ var isVoiceServerMessage = (value) => {
10902
10907
  case "error":
10903
10908
  case "final":
10904
10909
  case "partial":
10910
+ case "playback_rate":
10905
10911
  case "pong":
10906
10912
  case "replay":
10907
10913
  case "session":
@@ -11328,6 +11334,7 @@ var createInitialState = () => ({
11328
11334
  isConnected: false,
11329
11335
  paused: false,
11330
11336
  partial: "",
11337
+ playbackRate: null,
11331
11338
  reconnect: createInitialReconnectState(),
11332
11339
  scenarioId: null,
11333
11340
  sessionId: null,
@@ -11436,6 +11443,12 @@ var createVoiceStreamStore = () => {
11436
11443
  partial: joinPartial(turnFinalText, action.transcript.text)
11437
11444
  };
11438
11445
  break;
11446
+ case "playback_rate":
11447
+ state = {
11448
+ ...state,
11449
+ playbackRate: Math.max(0.5, Math.min(2, action.rate))
11450
+ };
11451
+ break;
11439
11452
  case "replay":
11440
11453
  turnFinalText = action.partial;
11441
11454
  state = {
@@ -11601,6 +11614,9 @@ var createVoiceStream = (path, options = {}) => {
11601
11614
  get partial() {
11602
11615
  return store.getSnapshot().partial;
11603
11616
  },
11617
+ get playbackRate() {
11618
+ return store.getSnapshot().playbackRate;
11619
+ },
11604
11620
  get paused() {
11605
11621
  return store.getSnapshot().paused;
11606
11622
  },
@@ -12226,6 +12242,7 @@ var createInitialState2 = (stream) => ({
12226
12242
  paused: stream.paused,
12227
12243
  pauseExpiresAt: stream.pauseExpiresAt,
12228
12244
  partial: stream.partial,
12245
+ playbackRate: stream.playbackRate,
12229
12246
  reconnect: stream.reconnect,
12230
12247
  recordingError: null,
12231
12248
  sessionId: stream.sessionId,
@@ -12263,6 +12280,7 @@ var createVoiceController = (path, options = {}) => {
12263
12280
  paused: stream.paused,
12264
12281
  pauseExpiresAt: stream.pauseExpiresAt,
12265
12282
  partial: stream.partial,
12283
+ playbackRate: stream.playbackRate,
12266
12284
  reconnect: stream.reconnect,
12267
12285
  sessionId: stream.sessionId,
12268
12286
  sessionMetadata: stream.sessionMetadata,
@@ -12409,6 +12427,9 @@ var createVoiceController = (path, options = {}) => {
12409
12427
  get partial() {
12410
12428
  return state.partial;
12411
12429
  },
12430
+ get playbackRate() {
12431
+ return state.playbackRate;
12432
+ },
12412
12433
  get paused() {
12413
12434
  return state.paused;
12414
12435
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.664",
3
+ "version": "0.0.22-beta.666",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",