@lokutor/sdk 1.1.35 → 1.1.37

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
@@ -236,10 +236,14 @@ declare class VoiceAgentClient {
236
236
  private enableAudio;
237
237
  private currentGeneration;
238
238
  private listeners;
239
+ private configConfirmed;
239
240
  private isUserDisconnect;
240
241
  private reconnecting;
241
242
  private reconnectAttempts;
242
243
  private maxReconnectAttempts;
244
+ private reconnectTimer;
245
+ private serverPlaybackRate;
246
+ private serverInputRate;
243
247
  private serverUrl;
244
248
  constructor(config: LokutorConfig & {
245
249
  prompt: string;
@@ -346,6 +350,18 @@ declare class VoiceAgentClient {
346
350
  * Update the system prompt mid-conversation
347
351
  */
348
352
  updatePrompt(newPrompt: string): void;
353
+ /**
354
+ * Change the voice style mid-conversation
355
+ */
356
+ updateVoice(voice: VoiceStyle): void;
357
+ /**
358
+ * Change the language mid-conversation
359
+ */
360
+ updateLanguage(language: Language): void;
361
+ /**
362
+ * Update tool definitions mid-conversation
363
+ */
364
+ updateTools(tools: ToolDefinition[]): void;
349
365
  /**
350
366
  * Get full conversation transcript
351
367
  */
package/dist/index.d.ts CHANGED
@@ -236,10 +236,14 @@ declare class VoiceAgentClient {
236
236
  private enableAudio;
237
237
  private currentGeneration;
238
238
  private listeners;
239
+ private configConfirmed;
239
240
  private isUserDisconnect;
240
241
  private reconnecting;
241
242
  private reconnectAttempts;
242
243
  private maxReconnectAttempts;
244
+ private reconnectTimer;
245
+ private serverPlaybackRate;
246
+ private serverInputRate;
243
247
  private serverUrl;
244
248
  constructor(config: LokutorConfig & {
245
249
  prompt: string;
@@ -346,6 +350,18 @@ declare class VoiceAgentClient {
346
350
  * Update the system prompt mid-conversation
347
351
  */
348
352
  updatePrompt(newPrompt: string): void;
353
+ /**
354
+ * Change the voice style mid-conversation
355
+ */
356
+ updateVoice(voice: VoiceStyle): void;
357
+ /**
358
+ * Change the language mid-conversation
359
+ */
360
+ updateLanguage(language: Language): void;
361
+ /**
362
+ * Update tool definitions mid-conversation
363
+ */
364
+ updateTools(tools: ToolDefinition[]): void;
349
365
  /**
350
366
  * Get full conversation transcript
351
367
  */
package/dist/index.js CHANGED
@@ -408,6 +408,9 @@ var BrowserAudioManager = class {
408
408
  console.warn("AudioContext not initialized");
409
409
  return;
410
410
  }
411
+ if (this.audioContext.state === "suspended") {
412
+ this.audioContext.resume();
413
+ }
411
414
  if (pcm16Data.length % 2 !== 0) {
412
415
  console.warn(`Discarding odd-length PCM buffer (${pcm16Data.length} bytes)`);
413
416
  return;
@@ -656,11 +659,16 @@ var VoiceAgentClient = class {
656
659
  enableAudio = false;
657
660
  currentGeneration = 0;
658
661
  listeners = {};
662
+ configConfirmed = false;
659
663
  // Connection resilience
660
664
  isUserDisconnect = false;
661
665
  reconnecting = false;
662
666
  reconnectAttempts = 0;
663
667
  maxReconnectAttempts = 5;
668
+ reconnectTimer = null;
669
+ // Runtime state
670
+ serverPlaybackRate = 44100;
671
+ serverInputRate = 16e3;
664
672
  serverUrl;
665
673
  constructor(config) {
666
674
  this.apiKey = config.apiKey;
@@ -759,6 +767,7 @@ var VoiceAgentClient = class {
759
767
  };
760
768
  this.ws.onclose = (event) => {
761
769
  this.isConnected = false;
770
+ this.configConfirmed = false;
762
771
  const diagnostic = {
763
772
  code: event.code,
764
773
  reason: event.reason,
@@ -787,12 +796,19 @@ var VoiceAgentClient = class {
787
796
  const backoffDelay = Math.min(1e3 * Math.pow(2, this.reconnectAttempts), 1e4);
788
797
  console.warn(`Connection lost. Reconnecting in ${backoffDelay}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
789
798
  if (this.onStatus) this.onStatus("reconnecting");
790
- setTimeout(() => {
791
- this.connect().catch((e) => console.error("Reconnect failed", e));
799
+ if (this.reconnectTimer) clearTimeout(this.reconnectTimer);
800
+ this.reconnectTimer = setTimeout(() => {
801
+ this.reconnectTimer = null;
802
+ this.connect().then(() => {
803
+ this.emit("reconnected");
804
+ }).catch((e) => console.error("Reconnect failed", e));
792
805
  }, backoffDelay);
793
- } else {
806
+ } else if (this.isUserDisconnect) {
794
807
  console.log("Disconnected");
795
808
  if (this.onStatus) this.onStatus("disconnected");
809
+ } else {
810
+ console.log("Max reconnection attempts reached");
811
+ if (this.onStatus) this.onStatus("disconnected");
796
812
  }
797
813
  };
798
814
  } catch (err) {
@@ -824,9 +840,13 @@ var VoiceAgentClient = class {
824
840
  */
825
841
  sendConfig() {
826
842
  if (!this.ws || !this.isConnected) return;
827
- this.ws.send(JSON.stringify({ type: "visemes", data: this.wantVisemes }));
843
+ this.configConfirmed = false;
828
844
  this.ws.send(JSON.stringify({ type: "voice", data: this.voice }));
829
845
  this.ws.send(JSON.stringify({ type: "language", data: this.language }));
846
+ if (this.tools && this.tools.length > 0) {
847
+ this.ws.send(JSON.stringify({ type: "tools", data: this.tools }));
848
+ }
849
+ this.ws.send(JSON.stringify({ type: "visemes", data: this.wantVisemes }));
830
850
  this.ws.send(JSON.stringify({ type: "prompt", data: this.prompt }));
831
851
  this.ws.send(JSON.stringify({ type: "rates", playback: 44100, input: 16e3 }));
832
852
  sdkTrace("ws.send.config", {
@@ -836,9 +856,6 @@ var VoiceAgentClient = class {
836
856
  visemes: this.wantVisemes,
837
857
  tools: this.tools?.length || 0
838
858
  });
839
- if (this.tools && this.tools.length > 0) {
840
- this.ws.send(JSON.stringify({ type: "tools", data: this.tools }));
841
- }
842
859
  console.log(`\u2699\uFE0F Configured: voice=${this.voice}, language=${this.language}, visemes=${this.wantVisemes}, tools=${this.tools.length}`);
843
860
  }
844
861
  /**
@@ -885,6 +902,11 @@ var VoiceAgentClient = class {
885
902
  this.handleBinaryMessage(buffer, msg.generation);
886
903
  }
887
904
  break;
905
+ case "server_rates":
906
+ if (msg.playback) this.serverPlaybackRate = msg.playback;
907
+ if (msg.input) this.serverInputRate = msg.input;
908
+ sdkTrace("server_rates", { playback: this.serverPlaybackRate, input: this.serverInputRate });
909
+ break;
888
910
  case "transcript":
889
911
  const role = msg.role === "user" ? "user" : "agent";
890
912
  this.messages.push({
@@ -903,12 +925,17 @@ var VoiceAgentClient = class {
903
925
  }
904
926
  break;
905
927
  case "status":
928
+ if (msg.data === "prompt_set") {
929
+ this.configConfirmed = true;
930
+ }
906
931
  if (msg.data === "thinking") {
907
932
  const newGen = msg.generation || 0;
908
933
  if (newGen > this.currentGeneration) {
909
- console.log(`\u{1F9E0} New thought (Gen ${newGen}) - Clearing audio queue`);
934
+ console.log(`\u{1F9E0} New thought (Gen ${newGen})`);
910
935
  this.currentGeneration = newGen;
911
- if (this.audioManager) this.audioManager.stopPlayback();
936
+ if (this.audioManager) {
937
+ this.audioManager.stopPlayback();
938
+ }
912
939
  }
913
940
  }
914
941
  if (msg.data === "interrupted" && this.audioManager) {
@@ -1019,6 +1046,10 @@ var VoiceAgentClient = class {
1019
1046
  */
1020
1047
  disconnect() {
1021
1048
  this.isUserDisconnect = true;
1049
+ if (this.reconnectTimer) {
1050
+ clearTimeout(this.reconnectTimer);
1051
+ this.reconnectTimer = null;
1052
+ }
1022
1053
  if (this.ws) {
1023
1054
  this.ws.close();
1024
1055
  this.ws = null;
@@ -1027,6 +1058,7 @@ var VoiceAgentClient = class {
1027
1058
  this.audioManager.cleanup();
1028
1059
  }
1029
1060
  this.isConnected = false;
1061
+ this.configConfirmed = false;
1030
1062
  this.reconnecting = false;
1031
1063
  this.reconnectAttempts = 0;
1032
1064
  }
@@ -1133,6 +1165,36 @@ var VoiceAgentClient = class {
1133
1165
  console.warn("Not connected - prompt will be updated on next connection");
1134
1166
  }
1135
1167
  }
1168
+ /**
1169
+ * Change the voice style mid-conversation
1170
+ */
1171
+ updateVoice(voice) {
1172
+ this.voice = voice;
1173
+ if (this.ws && this.ws.readyState === WebSocket.OPEN && this.isConnected) {
1174
+ this.ws.send(JSON.stringify({ type: "voice", data: voice }));
1175
+ console.log(`\u2699\uFE0F Updated voice: ${voice}`);
1176
+ }
1177
+ }
1178
+ /**
1179
+ * Change the language mid-conversation
1180
+ */
1181
+ updateLanguage(language) {
1182
+ this.language = language;
1183
+ if (this.ws && this.ws.readyState === WebSocket.OPEN && this.isConnected) {
1184
+ this.ws.send(JSON.stringify({ type: "language", data: language }));
1185
+ console.log(`\u2699\uFE0F Updated language: ${language}`);
1186
+ }
1187
+ }
1188
+ /**
1189
+ * Update tool definitions mid-conversation
1190
+ */
1191
+ updateTools(tools) {
1192
+ this.tools = tools;
1193
+ if (this.ws && this.ws.readyState === WebSocket.OPEN && this.isConnected) {
1194
+ this.ws.send(JSON.stringify({ type: "tools", data: tools }));
1195
+ console.log(`\u2699\uFE0F Updated ${tools.length} tool(s)`);
1196
+ }
1197
+ }
1136
1198
  /**
1137
1199
  * Get full conversation transcript
1138
1200
  */
package/dist/index.mjs CHANGED
@@ -361,6 +361,9 @@ var BrowserAudioManager = class {
361
361
  console.warn("AudioContext not initialized");
362
362
  return;
363
363
  }
364
+ if (this.audioContext.state === "suspended") {
365
+ this.audioContext.resume();
366
+ }
364
367
  if (pcm16Data.length % 2 !== 0) {
365
368
  console.warn(`Discarding odd-length PCM buffer (${pcm16Data.length} bytes)`);
366
369
  return;
@@ -609,11 +612,16 @@ var VoiceAgentClient = class {
609
612
  enableAudio = false;
610
613
  currentGeneration = 0;
611
614
  listeners = {};
615
+ configConfirmed = false;
612
616
  // Connection resilience
613
617
  isUserDisconnect = false;
614
618
  reconnecting = false;
615
619
  reconnectAttempts = 0;
616
620
  maxReconnectAttempts = 5;
621
+ reconnectTimer = null;
622
+ // Runtime state
623
+ serverPlaybackRate = 44100;
624
+ serverInputRate = 16e3;
617
625
  serverUrl;
618
626
  constructor(config) {
619
627
  this.apiKey = config.apiKey;
@@ -712,6 +720,7 @@ var VoiceAgentClient = class {
712
720
  };
713
721
  this.ws.onclose = (event) => {
714
722
  this.isConnected = false;
723
+ this.configConfirmed = false;
715
724
  const diagnostic = {
716
725
  code: event.code,
717
726
  reason: event.reason,
@@ -740,12 +749,19 @@ var VoiceAgentClient = class {
740
749
  const backoffDelay = Math.min(1e3 * Math.pow(2, this.reconnectAttempts), 1e4);
741
750
  console.warn(`Connection lost. Reconnecting in ${backoffDelay}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
742
751
  if (this.onStatus) this.onStatus("reconnecting");
743
- setTimeout(() => {
744
- this.connect().catch((e) => console.error("Reconnect failed", e));
752
+ if (this.reconnectTimer) clearTimeout(this.reconnectTimer);
753
+ this.reconnectTimer = setTimeout(() => {
754
+ this.reconnectTimer = null;
755
+ this.connect().then(() => {
756
+ this.emit("reconnected");
757
+ }).catch((e) => console.error("Reconnect failed", e));
745
758
  }, backoffDelay);
746
- } else {
759
+ } else if (this.isUserDisconnect) {
747
760
  console.log("Disconnected");
748
761
  if (this.onStatus) this.onStatus("disconnected");
762
+ } else {
763
+ console.log("Max reconnection attempts reached");
764
+ if (this.onStatus) this.onStatus("disconnected");
749
765
  }
750
766
  };
751
767
  } catch (err) {
@@ -777,9 +793,13 @@ var VoiceAgentClient = class {
777
793
  */
778
794
  sendConfig() {
779
795
  if (!this.ws || !this.isConnected) return;
780
- this.ws.send(JSON.stringify({ type: "visemes", data: this.wantVisemes }));
796
+ this.configConfirmed = false;
781
797
  this.ws.send(JSON.stringify({ type: "voice", data: this.voice }));
782
798
  this.ws.send(JSON.stringify({ type: "language", data: this.language }));
799
+ if (this.tools && this.tools.length > 0) {
800
+ this.ws.send(JSON.stringify({ type: "tools", data: this.tools }));
801
+ }
802
+ this.ws.send(JSON.stringify({ type: "visemes", data: this.wantVisemes }));
783
803
  this.ws.send(JSON.stringify({ type: "prompt", data: this.prompt }));
784
804
  this.ws.send(JSON.stringify({ type: "rates", playback: 44100, input: 16e3 }));
785
805
  sdkTrace("ws.send.config", {
@@ -789,9 +809,6 @@ var VoiceAgentClient = class {
789
809
  visemes: this.wantVisemes,
790
810
  tools: this.tools?.length || 0
791
811
  });
792
- if (this.tools && this.tools.length > 0) {
793
- this.ws.send(JSON.stringify({ type: "tools", data: this.tools }));
794
- }
795
812
  console.log(`\u2699\uFE0F Configured: voice=${this.voice}, language=${this.language}, visemes=${this.wantVisemes}, tools=${this.tools.length}`);
796
813
  }
797
814
  /**
@@ -838,6 +855,11 @@ var VoiceAgentClient = class {
838
855
  this.handleBinaryMessage(buffer, msg.generation);
839
856
  }
840
857
  break;
858
+ case "server_rates":
859
+ if (msg.playback) this.serverPlaybackRate = msg.playback;
860
+ if (msg.input) this.serverInputRate = msg.input;
861
+ sdkTrace("server_rates", { playback: this.serverPlaybackRate, input: this.serverInputRate });
862
+ break;
841
863
  case "transcript":
842
864
  const role = msg.role === "user" ? "user" : "agent";
843
865
  this.messages.push({
@@ -856,12 +878,17 @@ var VoiceAgentClient = class {
856
878
  }
857
879
  break;
858
880
  case "status":
881
+ if (msg.data === "prompt_set") {
882
+ this.configConfirmed = true;
883
+ }
859
884
  if (msg.data === "thinking") {
860
885
  const newGen = msg.generation || 0;
861
886
  if (newGen > this.currentGeneration) {
862
- console.log(`\u{1F9E0} New thought (Gen ${newGen}) - Clearing audio queue`);
887
+ console.log(`\u{1F9E0} New thought (Gen ${newGen})`);
863
888
  this.currentGeneration = newGen;
864
- if (this.audioManager) this.audioManager.stopPlayback();
889
+ if (this.audioManager) {
890
+ this.audioManager.stopPlayback();
891
+ }
865
892
  }
866
893
  }
867
894
  if (msg.data === "interrupted" && this.audioManager) {
@@ -972,6 +999,10 @@ var VoiceAgentClient = class {
972
999
  */
973
1000
  disconnect() {
974
1001
  this.isUserDisconnect = true;
1002
+ if (this.reconnectTimer) {
1003
+ clearTimeout(this.reconnectTimer);
1004
+ this.reconnectTimer = null;
1005
+ }
975
1006
  if (this.ws) {
976
1007
  this.ws.close();
977
1008
  this.ws = null;
@@ -980,6 +1011,7 @@ var VoiceAgentClient = class {
980
1011
  this.audioManager.cleanup();
981
1012
  }
982
1013
  this.isConnected = false;
1014
+ this.configConfirmed = false;
983
1015
  this.reconnecting = false;
984
1016
  this.reconnectAttempts = 0;
985
1017
  }
@@ -1086,6 +1118,36 @@ var VoiceAgentClient = class {
1086
1118
  console.warn("Not connected - prompt will be updated on next connection");
1087
1119
  }
1088
1120
  }
1121
+ /**
1122
+ * Change the voice style mid-conversation
1123
+ */
1124
+ updateVoice(voice) {
1125
+ this.voice = voice;
1126
+ if (this.ws && this.ws.readyState === WebSocket.OPEN && this.isConnected) {
1127
+ this.ws.send(JSON.stringify({ type: "voice", data: voice }));
1128
+ console.log(`\u2699\uFE0F Updated voice: ${voice}`);
1129
+ }
1130
+ }
1131
+ /**
1132
+ * Change the language mid-conversation
1133
+ */
1134
+ updateLanguage(language) {
1135
+ this.language = language;
1136
+ if (this.ws && this.ws.readyState === WebSocket.OPEN && this.isConnected) {
1137
+ this.ws.send(JSON.stringify({ type: "language", data: language }));
1138
+ console.log(`\u2699\uFE0F Updated language: ${language}`);
1139
+ }
1140
+ }
1141
+ /**
1142
+ * Update tool definitions mid-conversation
1143
+ */
1144
+ updateTools(tools) {
1145
+ this.tools = tools;
1146
+ if (this.ws && this.ws.readyState === WebSocket.OPEN && this.isConnected) {
1147
+ this.ws.send(JSON.stringify({ type: "tools", data: tools }));
1148
+ console.log(`\u2699\uFE0F Updated ${tools.length} tool(s)`);
1149
+ }
1150
+ }
1089
1151
  /**
1090
1152
  * Get full conversation transcript
1091
1153
  */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lokutor/sdk",
3
3
 
4
- "version": "1.1.35",
4
+ "version": "1.1.37",
5
5
  "description": "JavaScript/TypeScript SDK for Lokutor Real-time Voice AI",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.mjs",
@@ -230,6 +230,10 @@ export class BrowserAudioManager {
230
230
  return;
231
231
  }
232
232
 
233
+ if (this.audioContext.state === 'suspended') {
234
+ this.audioContext.resume();
235
+ }
236
+
233
237
  if (pcm16Data.length % 2 !== 0) {
234
238
  console.warn(`Discarding odd-length PCM buffer (${pcm16Data.length} bytes)`);
235
239
  return;
package/src/client.ts CHANGED
@@ -166,12 +166,18 @@ export class VoiceAgentClient {
166
166
  private enableAudio: boolean = false;
167
167
  private currentGeneration: number = 0;
168
168
  private listeners: Record<string, Function[]> = {};
169
+ private configConfirmed: boolean = false;
169
170
 
170
171
  // Connection resilience
171
172
  private isUserDisconnect: boolean = false;
172
173
  private reconnecting: boolean = false;
173
174
  private reconnectAttempts: number = 0;
174
175
  private maxReconnectAttempts: number = 5;
176
+ private reconnectTimer: ReturnType<typeof setTimeout> | null = null;
177
+
178
+ // Runtime state
179
+ private serverPlaybackRate: number = 44100;
180
+ private serverInputRate: number = 16000;
175
181
 
176
182
  private serverUrl: string;
177
183
 
@@ -296,6 +302,7 @@ export class VoiceAgentClient {
296
302
 
297
303
  this.ws.onclose = (event) => {
298
304
  this.isConnected = false;
305
+ this.configConfirmed = false;
299
306
  const diagnostic = {
300
307
  code: event.code,
301
308
  reason: event.reason,
@@ -331,12 +338,19 @@ export class VoiceAgentClient {
331
338
 
332
339
  if (this.onStatus) this.onStatus('reconnecting');
333
340
 
334
- setTimeout(() => {
335
- this.connect().catch(e => console.error("Reconnect failed", e));
341
+ if (this.reconnectTimer) clearTimeout(this.reconnectTimer);
342
+ this.reconnectTimer = setTimeout(() => {
343
+ this.reconnectTimer = null;
344
+ this.connect().then(() => {
345
+ this.emit('reconnected');
346
+ }).catch(e => console.error("Reconnect failed", e));
336
347
  }, backoffDelay);
337
- } else {
348
+ } else if (this.isUserDisconnect) {
338
349
  console.log('Disconnected');
339
350
  if (this.onStatus) this.onStatus('disconnected');
351
+ } else {
352
+ console.log('Max reconnection attempts reached');
353
+ if (this.onStatus) this.onStatus('disconnected');
340
354
  }
341
355
  };
342
356
 
@@ -374,14 +388,18 @@ export class VoiceAgentClient {
374
388
  private sendConfig() {
375
389
  if (!this.ws || !this.isConnected) return;
376
390
 
377
- // Send feature/config flags first so the first generated response uses them.
378
- this.ws.send(JSON.stringify({ type: 'visemes', data: this.wantVisemes }));
391
+ this.configConfirmed = false;
392
+
393
+ // Send feature/config flags — order matches the test agent for consistency.
379
394
  this.ws.send(JSON.stringify({ type: 'voice', data: this.voice }));
380
395
  this.ws.send(JSON.stringify({ type: 'language', data: this.language }));
396
+ if (this.tools && this.tools.length > 0) {
397
+ this.ws.send(JSON.stringify({ type: 'tools', data: this.tools }));
398
+ }
399
+ this.ws.send(JSON.stringify({ type: 'visemes', data: this.wantVisemes }));
381
400
  this.ws.send(JSON.stringify({ type: 'prompt', data: this.prompt }));
382
401
 
383
402
  // Inform the server of our sample rates for echo cancellation.
384
- // These match the SDK defaults: 16 kHz mic input, 44.1 kHz speaker output.
385
403
  this.ws.send(JSON.stringify({ type: 'rates', playback: 44100, input: 16000 }));
386
404
 
387
405
  sdkTrace('ws.send.config', {
@@ -392,10 +410,6 @@ export class VoiceAgentClient {
392
410
  tools: this.tools?.length || 0
393
411
  });
394
412
 
395
- if (this.tools && this.tools.length > 0) {
396
- this.ws.send(JSON.stringify({ type: 'tools', data: this.tools }));
397
- }
398
-
399
413
  console.log(`⚙️ Configured: voice=${this.voice}, language=${this.language}, visemes=${this.wantVisemes}, tools=${this.tools.length}`);
400
414
  }
401
415
 
@@ -445,6 +459,11 @@ export class VoiceAgentClient {
445
459
  this.handleBinaryMessage(buffer, msg.generation);
446
460
  }
447
461
  break;
462
+ case 'server_rates':
463
+ if (msg.playback) this.serverPlaybackRate = msg.playback;
464
+ if (msg.input) this.serverInputRate = msg.input;
465
+ sdkTrace('server_rates', { playback: this.serverPlaybackRate, input: this.serverInputRate });
466
+ break;
448
467
  case 'transcript':
449
468
  const role = msg.role === 'user' ? 'user' : 'agent';
450
469
  // Store in history
@@ -465,12 +484,17 @@ export class VoiceAgentClient {
465
484
  }
466
485
  break;
467
486
  case 'status':
487
+ if (msg.data === 'prompt_set') {
488
+ this.configConfirmed = true;
489
+ }
468
490
  if (msg.data === 'thinking') {
469
491
  const newGen = msg.generation || 0;
470
492
  if (newGen > this.currentGeneration) {
471
- console.log(`🧠 New thought (Gen ${newGen}) - Clearing audio queue`);
493
+ console.log(`🧠 New thought (Gen ${newGen})`);
472
494
  this.currentGeneration = newGen;
473
- if (this.audioManager) this.audioManager.stopPlayback();
495
+ if (this.audioManager) {
496
+ this.audioManager.stopPlayback();
497
+ }
474
498
  }
475
499
  }
476
500
  if (msg.data === 'interrupted' && this.audioManager) {
@@ -590,6 +614,10 @@ export class VoiceAgentClient {
590
614
  */
591
615
  public disconnect() {
592
616
  this.isUserDisconnect = true;
617
+ if (this.reconnectTimer) {
618
+ clearTimeout(this.reconnectTimer);
619
+ this.reconnectTimer = null;
620
+ }
593
621
  if (this.ws) {
594
622
  this.ws.close();
595
623
  this.ws = null;
@@ -598,6 +626,7 @@ export class VoiceAgentClient {
598
626
  this.audioManager.cleanup();
599
627
  }
600
628
  this.isConnected = false;
629
+ this.configConfirmed = false;
601
630
  this.reconnecting = false;
602
631
  this.reconnectAttempts = 0;
603
632
  }
@@ -716,6 +745,39 @@ export class VoiceAgentClient {
716
745
  }
717
746
  }
718
747
 
748
+ /**
749
+ * Change the voice style mid-conversation
750
+ */
751
+ public updateVoice(voice: VoiceStyle) {
752
+ this.voice = voice;
753
+ if (this.ws && this.ws.readyState === WebSocket.OPEN && this.isConnected) {
754
+ this.ws.send(JSON.stringify({ type: 'voice', data: voice }));
755
+ console.log(`⚙️ Updated voice: ${voice}`);
756
+ }
757
+ }
758
+
759
+ /**
760
+ * Change the language mid-conversation
761
+ */
762
+ public updateLanguage(language: Language) {
763
+ this.language = language;
764
+ if (this.ws && this.ws.readyState === WebSocket.OPEN && this.isConnected) {
765
+ this.ws.send(JSON.stringify({ type: 'language', data: language }));
766
+ console.log(`⚙️ Updated language: ${language}`);
767
+ }
768
+ }
769
+
770
+ /**
771
+ * Update tool definitions mid-conversation
772
+ */
773
+ public updateTools(tools: ToolDefinition[]) {
774
+ this.tools = tools;
775
+ if (this.ws && this.ws.readyState === WebSocket.OPEN && this.isConnected) {
776
+ this.ws.send(JSON.stringify({ type: 'tools', data: tools }));
777
+ console.log(`⚙️ Updated ${tools.length} tool(s)`);
778
+ }
779
+ }
780
+
719
781
  /**
720
782
  * Get full conversation transcript
721
783
  */