@estuary-ai/sdk 0.1.7 → 0.1.8

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 {
@@ -9272,6 +9292,7 @@ var AudioPlayer = class {
9272
9292
  return ctx;
9273
9293
  }
9274
9294
  playNext() {
9295
+ if (this._isCleared) return;
9275
9296
  const ctx = this.getAudioContext();
9276
9297
  if (!ctx || this.queue.length === 0) {
9277
9298
  if (this.isPlaying && this.currentMessageId) {
@@ -9295,6 +9316,7 @@ var AudioPlayer = class {
9295
9316
  source.connect(this.mediaStreamDest ?? ctx.destination);
9296
9317
  this.currentSource = source;
9297
9318
  source.onended = () => {
9319
+ if (this._isCleared) return;
9298
9320
  this.currentSource = null;
9299
9321
  this.playNext();
9300
9322
  };
@@ -9406,6 +9428,7 @@ var EstuaryClient = class extends TypedEventEmitter {
9406
9428
  _memory;
9407
9429
  _sessionInfo = null;
9408
9430
  actionParsers = /* @__PURE__ */ new Map();
9431
+ _hasAutoInterrupted = false;
9409
9432
  constructor(config) {
9410
9433
  super();
9411
9434
  this.config = config;
@@ -9456,6 +9479,7 @@ var EstuaryClient = class extends TypedEventEmitter {
9456
9479
  interrupt(messageId) {
9457
9480
  this.ensureConnected();
9458
9481
  this.socketManager.emitEvent("client_interrupt", { message_id: messageId });
9482
+ this.audioPlayer?.setInterruptedMessageId(messageId ?? this.audioPlayer.playingMessageId);
9459
9483
  this.audioPlayer?.clear();
9460
9484
  if (this.config.suppressMicDuringPlayback) {
9461
9485
  this.voiceManager?.setSuppressed?.(false);
@@ -9497,6 +9521,7 @@ var EstuaryClient = class extends TypedEventEmitter {
9497
9521
  if (!this.audioPlayer && typeof AudioContext !== "undefined") {
9498
9522
  this.audioPlayer = new AudioPlayer(sampleRate, (event) => {
9499
9523
  if (event.type === "started") {
9524
+ this._hasAutoInterrupted = false;
9500
9525
  this.emit("audioPlaybackStarted", event.messageId);
9501
9526
  if (this.config.suppressMicDuringPlayback) {
9502
9527
  this.voiceManager?.setSuppressed?.(true);
@@ -9557,10 +9582,15 @@ var EstuaryClient = class extends TypedEventEmitter {
9557
9582
  this.socketManager.on("connectionStateChanged", (state) => this.emit("connectionStateChanged", state));
9558
9583
  this.socketManager.on("botResponse", (response) => this.handleBotResponse(response));
9559
9584
  this.socketManager.on("botVoice", (voice) => this.handleBotVoice(voice));
9560
- this.socketManager.on("sttResponse", (response) => this.emit("sttResponse", response));
9585
+ this.socketManager.on("sttResponse", (response) => {
9586
+ this.maybeAutoInterrupt(response);
9587
+ this.emit("sttResponse", response);
9588
+ });
9561
9589
  this.socketManager.on("interrupt", (data) => {
9590
+ this.audioPlayer?.setInterruptedMessageId(data.messageId ?? null);
9562
9591
  this.audioPlayer?.clear();
9563
9592
  this.actionParsers.clear();
9593
+ this._hasAutoInterrupted = false;
9564
9594
  if (this.config.suppressMicDuringPlayback) {
9565
9595
  this.voiceManager?.setSuppressed?.(false);
9566
9596
  }
@@ -9600,6 +9630,15 @@ var EstuaryClient = class extends TypedEventEmitter {
9600
9630
  this.emit("botVoice", voice);
9601
9631
  this.audioPlayer?.enqueue(voice);
9602
9632
  }
9633
+ maybeAutoInterrupt(stt) {
9634
+ if ((this.config.autoInterruptOnSpeech ?? true) === false) return;
9635
+ if (this.config.suppressMicDuringPlayback) return;
9636
+ if (stt.isFinal) return;
9637
+ if (!this.audioPlayer?.playing) return;
9638
+ if (this._hasAutoInterrupted) return;
9639
+ this._hasAutoInterrupted = true;
9640
+ this.interrupt();
9641
+ }
9603
9642
  };
9604
9643
 
9605
9644
  // src/index.ts