@layercode/js-sdk 2.0.6 → 2.1.1

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.
@@ -3235,6 +3235,8 @@ class StreamProcessor extends AudioWorkletProcessor {
3235
3235
  this.isPaused = true;
3236
3236
  } else if (payload.event === 'play') {
3237
3237
  this.isPaused = false;
3238
+ } else if (payload.event === 'stop') {
3239
+ this.hasInterrupted = true;
3238
3240
  } else {
3239
3241
  throw new Error(\`Unhandled event "\${payload.event}"\`);
3240
3242
  }
@@ -3331,6 +3333,7 @@ registerProcessor('stream_processor', StreamProcessor);
3331
3333
  this.interruptedTrackIds = {};
3332
3334
  this.finishedPlayingCallback = finishedPlayingCallback;
3333
3335
  this.isPlaying = false;
3336
+ this.amplitudeMonitorRaf = undefined;
3334
3337
  }
3335
3338
 
3336
3339
  /**
@@ -3416,14 +3419,22 @@ registerProcessor('stream_processor', StreamProcessor);
3416
3419
  * @param {function} callback - Function to call with amplitude value
3417
3420
  */
3418
3421
  startAmplitudeMonitoring(callback) {
3422
+ this.stopAmplitudeMonitoring();
3419
3423
  const monitor = () => {
3420
3424
  const amplitude = this.getAmplitude();
3421
3425
  callback(amplitude);
3422
- requestAnimationFrame(monitor);
3426
+ this.amplitudeMonitorRaf = requestAnimationFrame(monitor);
3423
3427
  };
3424
3428
  monitor();
3425
3429
  }
3426
3430
 
3431
+ stopAmplitudeMonitoring() {
3432
+ if (this.amplitudeMonitorRaf !== undefined) {
3433
+ cancelAnimationFrame(this.amplitudeMonitorRaf);
3434
+ this.amplitudeMonitorRaf = undefined;
3435
+ }
3436
+ }
3437
+
3427
3438
  /**
3428
3439
  * Starts audio streaming
3429
3440
  * @private
@@ -3570,11 +3581,18 @@ registerProcessor('stream_processor', StreamProcessor);
3570
3581
  return true;
3571
3582
  }
3572
3583
 
3584
+ stop() {
3585
+ if (this.stream) {
3586
+ this.stream.port.postMessage({ event: 'stop' });
3587
+ }
3588
+ }
3589
+
3573
3590
  /**
3574
3591
  * Disconnects the audio context and cleans up resources
3575
3592
  * @returns {void}
3576
3593
  */
3577
3594
  disconnect() {
3595
+ this.stopAmplitudeMonitoring();
3578
3596
  if (this.stream) {
3579
3597
  this.stream.disconnect();
3580
3598
  this.stream = null;
@@ -3858,6 +3876,7 @@ registerProcessor('audio_processor', AudioProcessor);
3858
3876
  raw: new ArrayBuffer(0),
3859
3877
  mono: new ArrayBuffer(0),
3860
3878
  };
3879
+ this.amplitudeMonitorRaf = void 0;
3861
3880
  }
3862
3881
 
3863
3882
  /**
@@ -4257,14 +4276,22 @@ registerProcessor('audio_processor', AudioProcessor);
4257
4276
  * @param {function} callback - Function to call with amplitude value
4258
4277
  */
4259
4278
  startAmplitudeMonitoring(callback) {
4279
+ this.stopAmplitudeMonitoring();
4260
4280
  const monitor = () => {
4261
4281
  const amplitude = this.getAmplitude();
4262
4282
  callback(amplitude);
4263
- requestAnimationFrame(monitor);
4283
+ this.amplitudeMonitorRaf = requestAnimationFrame(monitor);
4264
4284
  };
4265
4285
  monitor();
4266
4286
  }
4267
4287
 
4288
+ stopAmplitudeMonitoring() {
4289
+ if (this.amplitudeMonitorRaf !== void 0) {
4290
+ cancelAnimationFrame(this.amplitudeMonitorRaf);
4291
+ this.amplitudeMonitorRaf = void 0;
4292
+ }
4293
+ }
4294
+
4268
4295
  /**
4269
4296
  * Pauses the recording
4270
4297
  * Keeps microphone stream open but halts storage of audio
@@ -4397,6 +4424,7 @@ registerProcessor('audio_processor', AudioProcessor);
4397
4424
  * @returns {Promise<true>}
4398
4425
  */
4399
4426
  async quit() {
4427
+ this.stopAmplitudeMonitoring();
4400
4428
  this.listenForDeviceChange(null);
4401
4429
  if (this.processor) {
4402
4430
  await this.end();
@@ -6300,6 +6328,8 @@ registerProcessor('audio_processor', AudioProcessor);
6300
6328
  }
6301
6329
 
6302
6330
  var _a;
6331
+ const NOOP = () => { };
6332
+ const DEFAULT_WS_URL = 'wss://api.layercode.com/v1/agents/web/websocket';
6303
6333
  // SDK version - updated when publishing
6304
6334
  const SDK_VERSION = '2.0.2';
6305
6335
  const ORT_WARNING_MUTE_LEVEL = 'error';
@@ -6358,27 +6388,28 @@ registerProcessor('audio_processor', AudioProcessor);
6358
6388
  * @param {Object} options - Configuration options
6359
6389
  */
6360
6390
  constructor(options) {
6391
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
6361
6392
  this.deviceId = null;
6362
6393
  this.options = {
6363
6394
  agentId: options.agentId,
6364
- conversationId: options.conversationId || null,
6395
+ conversationId: (_a = options.conversationId) !== null && _a !== void 0 ? _a : null,
6365
6396
  authorizeSessionEndpoint: options.authorizeSessionEndpoint,
6366
- metadata: options.metadata || {},
6367
- vadResumeDelay: options.vadResumeDelay || 500,
6368
- onConnect: options.onConnect || (() => { }),
6369
- onDisconnect: options.onDisconnect || (() => { }),
6370
- onError: options.onError || (() => { }),
6371
- onDeviceSwitched: options.onDeviceSwitched || (() => { }),
6372
- onDataMessage: options.onDataMessage || (() => { }),
6373
- onMessage: options.onMessage || (() => { }),
6374
- onUserAmplitudeChange: options.onUserAmplitudeChange || (() => { }),
6375
- onAgentAmplitudeChange: options.onAgentAmplitudeChange || (() => { }),
6376
- onStatusChange: options.onStatusChange || (() => { }),
6377
- onUserIsSpeakingChange: options.onUserIsSpeakingChange || (() => { }),
6378
- onMuteStateChange: options.onMuteStateChange || (() => { }),
6397
+ metadata: (_b = options.metadata) !== null && _b !== void 0 ? _b : {},
6398
+ vadResumeDelay: (_c = options.vadResumeDelay) !== null && _c !== void 0 ? _c : 500,
6399
+ onConnect: (_d = options.onConnect) !== null && _d !== void 0 ? _d : NOOP,
6400
+ onDisconnect: (_e = options.onDisconnect) !== null && _e !== void 0 ? _e : NOOP,
6401
+ onError: (_f = options.onError) !== null && _f !== void 0 ? _f : NOOP,
6402
+ onDeviceSwitched: (_g = options.onDeviceSwitched) !== null && _g !== void 0 ? _g : NOOP,
6403
+ onDataMessage: (_h = options.onDataMessage) !== null && _h !== void 0 ? _h : NOOP,
6404
+ onMessage: (_j = options.onMessage) !== null && _j !== void 0 ? _j : NOOP,
6405
+ onUserAmplitudeChange: (_k = options.onUserAmplitudeChange) !== null && _k !== void 0 ? _k : NOOP,
6406
+ onAgentAmplitudeChange: (_l = options.onAgentAmplitudeChange) !== null && _l !== void 0 ? _l : NOOP,
6407
+ onStatusChange: (_m = options.onStatusChange) !== null && _m !== void 0 ? _m : NOOP,
6408
+ onUserIsSpeakingChange: (_o = options.onUserIsSpeakingChange) !== null && _o !== void 0 ? _o : NOOP,
6409
+ onMuteStateChange: (_p = options.onMuteStateChange) !== null && _p !== void 0 ? _p : NOOP,
6379
6410
  };
6380
6411
  this.AMPLITUDE_MONITORING_SAMPLE_RATE = 2;
6381
- this._websocketUrl = 'wss://api.layercode.com/v1/agents/web/websocket';
6412
+ this._websocketUrl = DEFAULT_WS_URL;
6382
6413
  this.wavRecorder = new WavRecorder({ sampleRate: 8000 }); // TODO should be set my fetched agent config
6383
6414
  this.wavPlayer = new WavStreamPlayer({
6384
6415
  finishedPlayingCallback: this._clientResponseAudioReplayFinished.bind(this),
@@ -6389,7 +6420,7 @@ registerProcessor('audio_processor', AudioProcessor);
6389
6420
  this.status = 'disconnected';
6390
6421
  this.userAudioAmplitude = 0;
6391
6422
  this.agentAudioAmplitude = 0;
6392
- this.conversationId = options.conversationId || null;
6423
+ this.conversationId = this.options.conversationId;
6393
6424
  this.pushToTalkActive = false;
6394
6425
  this.pushToTalkEnabled = false;
6395
6426
  this.canInterrupt = false;
@@ -6404,11 +6435,13 @@ registerProcessor('audio_processor', AudioProcessor);
6404
6435
  this.lastReportedDeviceId = null;
6405
6436
  this.lastKnownSystemDefaultDeviceKey = null;
6406
6437
  this.isMuted = false;
6438
+ this.stopPlayerAmplitude = undefined;
6439
+ this.stopRecorderAmplitude = undefined;
6440
+ this.deviceChangeListener = null;
6407
6441
  // this.audioPauseTime = null;
6408
6442
  // Bind event handlers
6409
6443
  this._handleWebSocketMessage = this._handleWebSocketMessage.bind(this);
6410
6444
  this._handleDataAvailable = this._handleDataAvailable.bind(this);
6411
- this._setupDeviceChangeListener();
6412
6445
  }
6413
6446
  _initializeVAD() {
6414
6447
  var _a;
@@ -6673,31 +6706,56 @@ registerProcessor('audio_processor', AudioProcessor);
6673
6706
  * @param {(amplitude: number) => void} updateInternalState - Function to update the internal amplitude state.
6674
6707
  */
6675
6708
  _setupAmplitudeMonitoring(source, callback, updateInternalState) {
6676
- // Set up amplitude monitoring only if a callback is provided
6677
- // Check against the default no-op function defined in the constructor options
6678
- if (callback !== (() => { })) {
6679
- let updateCounter = 0;
6680
- source.startAmplitudeMonitoring((amplitude) => {
6681
- // Only update and call callback at the specified sample rate
6682
- if (updateCounter >= this.AMPLITUDE_MONITORING_SAMPLE_RATE) {
6683
- updateInternalState(amplitude);
6709
+ let updateCounter = 0;
6710
+ source.startAmplitudeMonitoring((amplitude) => {
6711
+ // Only update and call callback at the specified sample rate
6712
+ if (updateCounter >= this.AMPLITUDE_MONITORING_SAMPLE_RATE) {
6713
+ updateInternalState(amplitude);
6714
+ if (callback !== NOOP) {
6684
6715
  callback(amplitude);
6685
- updateCounter = 0; // Reset counter after sampling
6686
6716
  }
6687
- updateCounter++;
6688
- });
6717
+ updateCounter = 0; // Reset counter after sampling
6718
+ }
6719
+ updateCounter++;
6720
+ });
6721
+ const stop = () => { var _a; return (_a = source.stopAmplitudeMonitoring) === null || _a === void 0 ? void 0 : _a.call(source); };
6722
+ if (source === this.wavPlayer) {
6723
+ this.stopPlayerAmplitude = stop;
6724
+ }
6725
+ if (source === this.wavRecorder) {
6726
+ this.stopRecorderAmplitude = stop;
6689
6727
  }
6690
6728
  }
6729
+ _stopAmplitudeMonitoring() {
6730
+ var _a, _b;
6731
+ (_a = this.stopPlayerAmplitude) === null || _a === void 0 ? void 0 : _a.call(this);
6732
+ (_b = this.stopRecorderAmplitude) === null || _b === void 0 ? void 0 : _b.call(this);
6733
+ this.stopPlayerAmplitude = undefined;
6734
+ this.stopRecorderAmplitude = undefined;
6735
+ }
6691
6736
  /**
6692
6737
  * Connects to the Layercode agent and starts the audio conversation
6693
6738
  * @async
6694
6739
  * @returns {Promise<void>}
6695
6740
  */
6696
- async connect() {
6741
+ async connect(opts) {
6742
+ if (this.status === 'connecting') {
6743
+ return;
6744
+ }
6745
+ if (opts === null || opts === void 0 ? void 0 : opts.newConversation) {
6746
+ this.options.conversationId = null;
6747
+ this.conversationId = null;
6748
+ }
6749
+ else if (opts === null || opts === void 0 ? void 0 : opts.conversationId) {
6750
+ this.options.conversationId = opts.conversationId;
6751
+ this.conversationId = opts.conversationId;
6752
+ }
6697
6753
  try {
6698
6754
  this._setStatus('connecting');
6699
6755
  // Reset turn tracking for clean start
6700
6756
  this._resetTurnTracking();
6757
+ this._stopAmplitudeMonitoring();
6758
+ this._setupDeviceChangeListener();
6701
6759
  // Get conversation key from server
6702
6760
  let authorizeSessionRequestBody = {
6703
6761
  agent_id: this.options.agentId,
@@ -6720,6 +6778,7 @@ registerProcessor('audio_processor', AudioProcessor);
6720
6778
  }
6721
6779
  const authorizeSessionResponseBody = await authorizeSessionResponse.json();
6722
6780
  this.conversationId = authorizeSessionResponseBody.conversation_id; // Save the conversation_id for use in future reconnects
6781
+ this.options.conversationId = this.conversationId;
6723
6782
  // Connect WebSocket
6724
6783
  this.ws = new WebSocket(`${this._websocketUrl}?${new URLSearchParams({
6725
6784
  client_session_key: authorizeSessionResponseBody.client_session_key,
@@ -6749,8 +6808,12 @@ registerProcessor('audio_processor', AudioProcessor);
6749
6808
  };
6750
6809
  this.ws.onclose = () => {
6751
6810
  console.log('WebSocket connection closed');
6752
- this._setStatus('disconnected');
6753
- this.options.onDisconnect();
6811
+ this.ws = null;
6812
+ this._performDisconnectCleanup()
6813
+ .catch((error) => {
6814
+ console.error('Error during disconnect cleanup:', error);
6815
+ this.options.onError(error instanceof Error ? error : new Error(String(error)));
6816
+ });
6754
6817
  };
6755
6818
  this.ws.onerror = (error) => {
6756
6819
  console.error('WebSocket error:', error);
@@ -6776,31 +6839,19 @@ registerProcessor('audio_processor', AudioProcessor);
6776
6839
  this.currentTurnId = null;
6777
6840
  console.debug('Reset turn tracking state');
6778
6841
  }
6779
- async disconnect() {
6780
- this.deviceId = null;
6781
- this.activeDeviceId = null;
6782
- this.useSystemDefaultDevice = false;
6783
- this.lastReportedDeviceId = null;
6784
- this.lastKnownSystemDefaultDeviceKey = null;
6785
- this.recorderStarted = false;
6786
- this.readySent = false;
6787
- // Clean up VAD if it exists
6788
- if (this.vad) {
6789
- this.vad.pause();
6790
- this.vad.destroy();
6791
- this.vad = null;
6842
+ async disconnect(opts) {
6843
+ if (this.status === 'disconnected') {
6844
+ return;
6792
6845
  }
6793
- this.wavRecorder.listenForDeviceChange(null);
6794
- this.wavRecorder.quit();
6795
- this.wavPlayer.disconnect();
6796
- // Reset turn tracking
6797
- this._resetTurnTracking();
6798
- // Close websocket and ensure status is updated
6799
6846
  if (this.ws) {
6847
+ this.ws.onopen = null;
6848
+ this.ws.onclose = null;
6849
+ this.ws.onerror = null;
6850
+ this.ws.onmessage = null;
6800
6851
  this.ws.close();
6801
- this._setStatus('disconnected');
6802
- this.options.onDisconnect();
6852
+ this.ws = null;
6803
6853
  }
6854
+ await this._performDisconnectCleanup(opts === null || opts === void 0 ? void 0 : opts.clearConversationId);
6804
6855
  }
6805
6856
  /**
6806
6857
  * Gets the microphone MediaStream used by this client
@@ -6898,50 +6949,88 @@ registerProcessor('audio_processor', AudioProcessor);
6898
6949
  * Sets up the device change event listener
6899
6950
  */
6900
6951
  _setupDeviceChangeListener() {
6901
- this.wavRecorder.listenForDeviceChange(async (devices) => {
6902
- try {
6903
- const defaultDevice = devices.find((device) => device.default);
6904
- const usingDefaultDevice = this.useSystemDefaultDevice;
6905
- const previousDefaultDeviceKey = this.lastKnownSystemDefaultDeviceKey;
6906
- const currentDefaultDeviceKey = this._getDeviceComparisonKey(defaultDevice);
6907
- let shouldSwitch = !this.recorderStarted;
6908
- if (!shouldSwitch) {
6909
- if (usingDefaultDevice) {
6910
- if (!defaultDevice) {
6911
- shouldSwitch = true;
6912
- }
6913
- else if (this.activeDeviceId &&
6914
- defaultDevice.deviceId !== 'default' &&
6915
- defaultDevice.deviceId !== this.activeDeviceId) {
6916
- shouldSwitch = true;
6952
+ if (!this.deviceChangeListener) {
6953
+ this.deviceChangeListener = async (devices) => {
6954
+ try {
6955
+ const defaultDevice = devices.find((device) => device.default);
6956
+ const usingDefaultDevice = this.useSystemDefaultDevice;
6957
+ const previousDefaultDeviceKey = this.lastKnownSystemDefaultDeviceKey;
6958
+ const currentDefaultDeviceKey = this._getDeviceComparisonKey(defaultDevice);
6959
+ let shouldSwitch = !this.recorderStarted;
6960
+ if (!shouldSwitch) {
6961
+ if (usingDefaultDevice) {
6962
+ if (!defaultDevice) {
6963
+ shouldSwitch = true;
6964
+ }
6965
+ else if (this.activeDeviceId &&
6966
+ defaultDevice.deviceId !== 'default' &&
6967
+ defaultDevice.deviceId !== this.activeDeviceId) {
6968
+ shouldSwitch = true;
6969
+ }
6970
+ else if ((previousDefaultDeviceKey && previousDefaultDeviceKey !== currentDefaultDeviceKey) ||
6971
+ (!previousDefaultDeviceKey && !currentDefaultDeviceKey && this.recorderStarted)) {
6972
+ shouldSwitch = true;
6973
+ }
6917
6974
  }
6918
- else if ((previousDefaultDeviceKey && previousDefaultDeviceKey !== currentDefaultDeviceKey) ||
6919
- (!previousDefaultDeviceKey && !currentDefaultDeviceKey && this.recorderStarted)) {
6920
- shouldSwitch = true;
6975
+ else {
6976
+ const matchesRequestedDevice = devices.some((device) => device.deviceId === this.deviceId || device.deviceId === this.activeDeviceId);
6977
+ shouldSwitch = !matchesRequestedDevice;
6921
6978
  }
6922
6979
  }
6923
- else {
6924
- const matchesRequestedDevice = devices.some((device) => device.deviceId === this.deviceId || device.deviceId === this.activeDeviceId);
6925
- shouldSwitch = !matchesRequestedDevice;
6980
+ this.lastKnownSystemDefaultDeviceKey = currentDefaultDeviceKey;
6981
+ if (shouldSwitch) {
6982
+ console.debug('Selecting fallback audio input device');
6983
+ const fallbackDevice = defaultDevice || devices[0];
6984
+ if (fallbackDevice) {
6985
+ const fallbackId = fallbackDevice.default ? 'default' : fallbackDevice.deviceId;
6986
+ await this.setInputDevice(fallbackId);
6987
+ }
6988
+ else {
6989
+ console.warn('No alternative audio device found');
6990
+ }
6926
6991
  }
6927
6992
  }
6928
- this.lastKnownSystemDefaultDeviceKey = currentDefaultDeviceKey;
6929
- if (shouldSwitch) {
6930
- console.debug('Selecting fallback audio input device');
6931
- const fallbackDevice = defaultDevice || devices[0];
6932
- if (fallbackDevice) {
6933
- const fallbackId = fallbackDevice.default ? 'default' : fallbackDevice.deviceId;
6934
- await this.setInputDevice(fallbackId);
6935
- }
6936
- else {
6937
- console.warn('No alternative audio device found');
6938
- }
6993
+ catch (error) {
6994
+ this.options.onError(error instanceof Error ? error : new Error(String(error)));
6939
6995
  }
6940
- }
6941
- catch (error) {
6942
- this.options.onError(error instanceof Error ? error : new Error(String(error)));
6943
- }
6944
- });
6996
+ };
6997
+ }
6998
+ this.wavRecorder.listenForDeviceChange(this.deviceChangeListener);
6999
+ }
7000
+ _teardownDeviceListeners() {
7001
+ this.wavRecorder.listenForDeviceChange(null);
7002
+ }
7003
+ async _performDisconnectCleanup(clearConversationId) {
7004
+ var _a, _b;
7005
+ this.deviceId = null;
7006
+ this.activeDeviceId = null;
7007
+ this.useSystemDefaultDevice = false;
7008
+ this.lastReportedDeviceId = null;
7009
+ this.lastKnownSystemDefaultDeviceKey = null;
7010
+ this.recorderStarted = false;
7011
+ this.readySent = false;
7012
+ this._stopAmplitudeMonitoring();
7013
+ this._teardownDeviceListeners();
7014
+ if (this.vad) {
7015
+ this.vad.pause();
7016
+ this.vad.destroy();
7017
+ this.vad = null;
7018
+ }
7019
+ await this.wavRecorder.quit();
7020
+ (_b = (_a = this.wavPlayer).stop) === null || _b === void 0 ? void 0 : _b.call(_a);
7021
+ this.wavPlayer.disconnect();
7022
+ this._resetTurnTracking();
7023
+ if (clearConversationId) {
7024
+ this.options.conversationId = null;
7025
+ this.conversationId = null;
7026
+ }
7027
+ else {
7028
+ this.options.conversationId = this.conversationId;
7029
+ }
7030
+ this.userAudioAmplitude = 0;
7031
+ this.agentAudioAmplitude = 0;
7032
+ this._setStatus('disconnected');
7033
+ this.options.onDisconnect();
6945
7034
  }
6946
7035
  _getDeviceComparisonKey(device) {
6947
7036
  if (!device || typeof device !== 'object') {