@estuary-ai/sdk 0.1.7 → 0.1.9

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.d.mts CHANGED
@@ -23,6 +23,8 @@ interface EstuaryConfig {
23
23
  realtimeMemory?: boolean;
24
24
  /** Suppress mic during TTS playback (software AEC fallback, disables barge-in). Default: false */
25
25
  suppressMicDuringPlayback?: boolean;
26
+ /** Proactively interrupt bot audio when user starts speaking (default: true) */
27
+ autoInterruptOnSpeech?: boolean;
26
28
  }
27
29
  type VoiceTransport = 'websocket' | 'livekit' | 'auto';
28
30
  declare enum ConnectionState {
@@ -243,6 +245,7 @@ declare class EstuaryClient extends TypedEventEmitter<EstuaryEventMap> {
243
245
  private _memory;
244
246
  private _sessionInfo;
245
247
  private actionParsers;
248
+ private _hasAutoInterrupted;
246
249
  constructor(config: EstuaryConfig);
247
250
  /** Memory API client for querying memories, graphs, and facts */
248
251
  get memory(): MemoryClient;
@@ -282,6 +285,7 @@ declare class EstuaryClient extends TypedEventEmitter<EstuaryEventMap> {
282
285
  private forwardSocketEvents;
283
286
  private handleBotResponse;
284
287
  private handleBotVoice;
288
+ private maybeAutoInterrupt;
285
289
  }
286
290
 
287
291
  declare enum ErrorCode {
package/dist/index.js CHANGED
@@ -9203,11 +9203,30 @@ var AudioPlayer = class {
9203
9203
  currentSource = null;
9204
9204
  currentMessageId = null;
9205
9205
  isPlaying = false;
9206
+ _isCleared = false;
9207
+ _interruptedMessageId = null;
9206
9208
  constructor(sampleRate, onEvent) {
9207
9209
  this.sampleRate = sampleRate;
9208
9210
  this.onEvent = onEvent;
9209
9211
  }
9212
+ /** Whether audio is currently playing */
9213
+ get playing() {
9214
+ return this.isPlaying;
9215
+ }
9216
+ /** The messageId of the currently playing audio, or null */
9217
+ get playingMessageId() {
9218
+ return this.currentMessageId;
9219
+ }
9220
+ /** Mark a messageId as interrupted so late-arriving chunks are dropped */
9221
+ setInterruptedMessageId(id) {
9222
+ this._interruptedMessageId = id;
9223
+ }
9210
9224
  enqueue(voice) {
9225
+ if (voice.messageId === this._interruptedMessageId) return;
9226
+ if (this._interruptedMessageId && voice.messageId !== this._interruptedMessageId) {
9227
+ this._interruptedMessageId = null;
9228
+ }
9229
+ this._isCleared = false;
9211
9230
  const ctx = this.getAudioContext();
9212
9231
  if (!ctx) return;
9213
9232
  const pcm16 = base64ToInt16Array(voice.audio);
@@ -9220,6 +9239,7 @@ var AudioPlayer = class {
9220
9239
  }
9221
9240
  }
9222
9241
  clear() {
9242
+ this._isCleared = true;
9223
9243
  this.queue.length = 0;
9224
9244
  if (this.currentSource) {
9225
9245
  try {
@@ -9229,6 +9249,7 @@ var AudioPlayer = class {
9229
9249
  }
9230
9250
  this.currentSource = null;
9231
9251
  }
9252
+ if (this.audioElement) this.audioElement.muted = true;
9232
9253
  this.isPlaying = false;
9233
9254
  this.currentMessageId = null;
9234
9255
  }
@@ -9272,6 +9293,7 @@ var AudioPlayer = class {
9272
9293
  return ctx;
9273
9294
  }
9274
9295
  playNext() {
9296
+ if (this._isCleared) return;
9275
9297
  const ctx = this.getAudioContext();
9276
9298
  if (!ctx || this.queue.length === 0) {
9277
9299
  if (this.isPlaying && this.currentMessageId) {
@@ -9295,9 +9317,11 @@ var AudioPlayer = class {
9295
9317
  source.connect(this.mediaStreamDest ?? ctx.destination);
9296
9318
  this.currentSource = source;
9297
9319
  source.onended = () => {
9320
+ if (this._isCleared) return;
9298
9321
  this.currentSource = null;
9299
9322
  this.playNext();
9300
9323
  };
9324
+ if (this.audioElement) this.audioElement.muted = false;
9301
9325
  ctx.resume().catch(() => {
9302
9326
  });
9303
9327
  source.start();
@@ -9406,6 +9430,7 @@ var EstuaryClient = class extends TypedEventEmitter {
9406
9430
  _memory;
9407
9431
  _sessionInfo = null;
9408
9432
  actionParsers = /* @__PURE__ */ new Map();
9433
+ _hasAutoInterrupted = false;
9409
9434
  constructor(config) {
9410
9435
  super();
9411
9436
  this.config = config;
@@ -9456,6 +9481,7 @@ var EstuaryClient = class extends TypedEventEmitter {
9456
9481
  interrupt(messageId) {
9457
9482
  this.ensureConnected();
9458
9483
  this.socketManager.emitEvent("client_interrupt", { message_id: messageId });
9484
+ this.audioPlayer?.setInterruptedMessageId(messageId ?? this.audioPlayer.playingMessageId);
9459
9485
  this.audioPlayer?.clear();
9460
9486
  if (this.config.suppressMicDuringPlayback) {
9461
9487
  this.voiceManager?.setSuppressed?.(false);
@@ -9497,6 +9523,7 @@ var EstuaryClient = class extends TypedEventEmitter {
9497
9523
  if (!this.audioPlayer && typeof AudioContext !== "undefined") {
9498
9524
  this.audioPlayer = new AudioPlayer(sampleRate, (event) => {
9499
9525
  if (event.type === "started") {
9526
+ this._hasAutoInterrupted = false;
9500
9527
  this.emit("audioPlaybackStarted", event.messageId);
9501
9528
  if (this.config.suppressMicDuringPlayback) {
9502
9529
  this.voiceManager?.setSuppressed?.(true);
@@ -9557,10 +9584,15 @@ var EstuaryClient = class extends TypedEventEmitter {
9557
9584
  this.socketManager.on("connectionStateChanged", (state) => this.emit("connectionStateChanged", state));
9558
9585
  this.socketManager.on("botResponse", (response) => this.handleBotResponse(response));
9559
9586
  this.socketManager.on("botVoice", (voice) => this.handleBotVoice(voice));
9560
- this.socketManager.on("sttResponse", (response) => this.emit("sttResponse", response));
9587
+ this.socketManager.on("sttResponse", (response) => {
9588
+ this.maybeAutoInterrupt(response);
9589
+ this.emit("sttResponse", response);
9590
+ });
9561
9591
  this.socketManager.on("interrupt", (data) => {
9592
+ this.audioPlayer?.setInterruptedMessageId(data.messageId ?? null);
9562
9593
  this.audioPlayer?.clear();
9563
9594
  this.actionParsers.clear();
9595
+ this._hasAutoInterrupted = false;
9564
9596
  if (this.config.suppressMicDuringPlayback) {
9565
9597
  this.voiceManager?.setSuppressed?.(false);
9566
9598
  }
@@ -9600,6 +9632,15 @@ var EstuaryClient = class extends TypedEventEmitter {
9600
9632
  this.emit("botVoice", voice);
9601
9633
  this.audioPlayer?.enqueue(voice);
9602
9634
  }
9635
+ maybeAutoInterrupt(stt) {
9636
+ if ((this.config.autoInterruptOnSpeech ?? true) === false) return;
9637
+ if (this.config.suppressMicDuringPlayback) return;
9638
+ if (stt.isFinal) return;
9639
+ if (!this.audioPlayer?.playing) return;
9640
+ if (this._hasAutoInterrupted) return;
9641
+ this._hasAutoInterrupted = true;
9642
+ this.interrupt();
9643
+ }
9603
9644
  };
9604
9645
 
9605
9646
  // src/index.ts