@estuary-ai/sdk 0.1.6 → 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,9 +9316,12 @@ 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
  };
9323
+ ctx.resume().catch(() => {
9324
+ });
9301
9325
  source.start();
9302
9326
  }
9303
9327
  };
@@ -9404,6 +9428,7 @@ var EstuaryClient = class extends TypedEventEmitter {
9404
9428
  _memory;
9405
9429
  _sessionInfo = null;
9406
9430
  actionParsers = /* @__PURE__ */ new Map();
9431
+ _hasAutoInterrupted = false;
9407
9432
  constructor(config) {
9408
9433
  super();
9409
9434
  this.config = config;
@@ -9454,6 +9479,7 @@ var EstuaryClient = class extends TypedEventEmitter {
9454
9479
  interrupt(messageId) {
9455
9480
  this.ensureConnected();
9456
9481
  this.socketManager.emitEvent("client_interrupt", { message_id: messageId });
9482
+ this.audioPlayer?.setInterruptedMessageId(messageId ?? this.audioPlayer.playingMessageId);
9457
9483
  this.audioPlayer?.clear();
9458
9484
  if (this.config.suppressMicDuringPlayback) {
9459
9485
  this.voiceManager?.setSuppressed?.(false);
@@ -9495,6 +9521,7 @@ var EstuaryClient = class extends TypedEventEmitter {
9495
9521
  if (!this.audioPlayer && typeof AudioContext !== "undefined") {
9496
9522
  this.audioPlayer = new AudioPlayer(sampleRate, (event) => {
9497
9523
  if (event.type === "started") {
9524
+ this._hasAutoInterrupted = false;
9498
9525
  this.emit("audioPlaybackStarted", event.messageId);
9499
9526
  if (this.config.suppressMicDuringPlayback) {
9500
9527
  this.voiceManager?.setSuppressed?.(true);
@@ -9555,10 +9582,15 @@ var EstuaryClient = class extends TypedEventEmitter {
9555
9582
  this.socketManager.on("connectionStateChanged", (state) => this.emit("connectionStateChanged", state));
9556
9583
  this.socketManager.on("botResponse", (response) => this.handleBotResponse(response));
9557
9584
  this.socketManager.on("botVoice", (voice) => this.handleBotVoice(voice));
9558
- 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
+ });
9559
9589
  this.socketManager.on("interrupt", (data) => {
9590
+ this.audioPlayer?.setInterruptedMessageId(data.messageId ?? null);
9560
9591
  this.audioPlayer?.clear();
9561
9592
  this.actionParsers.clear();
9593
+ this._hasAutoInterrupted = false;
9562
9594
  if (this.config.suppressMicDuringPlayback) {
9563
9595
  this.voiceManager?.setSuppressed?.(false);
9564
9596
  }
@@ -9598,6 +9630,15 @@ var EstuaryClient = class extends TypedEventEmitter {
9598
9630
  this.emit("botVoice", voice);
9599
9631
  this.audioPlayer?.enqueue(voice);
9600
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
+ }
9601
9642
  };
9602
9643
 
9603
9644
  // src/index.ts