@anganyai/voice-sdk 0.0.1 → 0.0.4

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.js CHANGED
@@ -3459,8 +3459,18 @@ var AuthManager = class extends EventEmitter {
3459
3459
  }
3460
3460
  /**
3461
3461
  * Get ephemeral credentials for WebRTC connections
3462
+ *
3463
+ * Returns cached credentials if available and valid, otherwise fetches new ones.
3464
+ * This supports both:
3465
+ * - Externally-provided credentials (via setEphemeralCredentials)
3466
+ * - SDK-managed credentials (fetched using access token)
3462
3467
  */
3463
3468
  async getEphemeralCredentials() {
3469
+ const cached = this.credentialManager.getCachedEphemeralCredentials();
3470
+ if (cached) {
3471
+ this.logger.debug("Using cached ephemeral credentials");
3472
+ return cached;
3473
+ }
3464
3474
  const accessToken = await this.getAccessToken();
3465
3475
  if (!accessToken) {
3466
3476
  throw new AuthenticationError("No access token available for ephemeral credentials");
@@ -16236,6 +16246,26 @@ var UserAgent = class _UserAgent {
16236
16246
  }
16237
16247
  };
16238
16248
 
16249
+ // src/utils/platform.ts
16250
+ function detectPlatform() {
16251
+ if (typeof navigator !== "undefined" && navigator.product === "ReactNative") {
16252
+ return "react-native";
16253
+ }
16254
+ if (typeof window !== "undefined" && typeof document !== "undefined" && typeof navigator !== "undefined") {
16255
+ return "browser";
16256
+ }
16257
+ return "node";
16258
+ }
16259
+ function isReactNative() {
16260
+ return detectPlatform() === "react-native";
16261
+ }
16262
+ function isBrowser() {
16263
+ return detectPlatform() === "browser";
16264
+ }
16265
+ function hasAudioContext() {
16266
+ return typeof AudioContext !== "undefined" || typeof globalThis.webkitAudioContext !== "undefined";
16267
+ }
16268
+
16239
16269
  // src/services/SipManager.ts
16240
16270
  var SipManager = class extends EventEmitter {
16241
16271
  constructor() {
@@ -16534,7 +16564,9 @@ var SipManager = class extends EventEmitter {
16534
16564
  }
16535
16565
  }
16536
16566
  setupAudioHandling(session) {
16537
- this.logger.debug("Setting up audio handling using SIP.js streams");
16567
+ this.logger.debug("Setting up audio handling using SIP.js streams", {
16568
+ platform: isReactNative() ? "react-native" : "browser"
16569
+ });
16538
16570
  const sessionDescriptionHandler = session.sessionDescriptionHandler;
16539
16571
  if (sessionDescriptionHandler) {
16540
16572
  this.logger.debug("Session description handler found");
@@ -16542,18 +16574,22 @@ var SipManager = class extends EventEmitter {
16542
16574
  if (remoteStream) {
16543
16575
  this.logger.info("Remote media stream available");
16544
16576
  this.remoteStream = remoteStream;
16545
- this.createAudioElement();
16546
- if (this.audioElement) {
16547
- this.audioElement.srcObject = remoteStream;
16548
- this.audioElement.play().catch((error) => {
16549
- this.logger.warn("Autoplay prevented, enabling controls for user interaction", {
16550
- error
16577
+ if (isReactNative()) {
16578
+ this.logger.info("React Native: Remote audio will be played automatically by WebRTC");
16579
+ } else {
16580
+ this.createAudioElement();
16581
+ if (this.audioElement) {
16582
+ this.audioElement.srcObject = remoteStream;
16583
+ this.audioElement.play().catch((error) => {
16584
+ this.logger.warn("Autoplay prevented, enabling controls for user interaction", {
16585
+ error
16586
+ });
16587
+ if (this.audioElement) {
16588
+ this.audioElement.controls = true;
16589
+ this.audioElement.style.display = "block";
16590
+ }
16551
16591
  });
16552
- if (this.audioElement) {
16553
- this.audioElement.controls = true;
16554
- this.audioElement.style.display = "block";
16555
- }
16556
- });
16592
+ }
16557
16593
  }
16558
16594
  this.startAudioMonitoring();
16559
16595
  } else {
@@ -16572,16 +16608,22 @@ var SipManager = class extends EventEmitter {
16572
16608
  if (event.streams[0]) {
16573
16609
  this.remoteStream = event.streams[0];
16574
16610
  this.logger.info("Remote audio stream received via ontrack fallback");
16575
- this.createAudioElement();
16576
- if (this.audioElement && this.remoteStream) {
16577
- this.audioElement.srcObject = this.remoteStream;
16578
- this.audioElement.play().catch((error) => {
16579
- this.logger.warn("Failed to auto-play remote audio", { error });
16580
- if (this.audioElement) {
16581
- this.audioElement.controls = true;
16582
- this.audioElement.style.display = "block";
16583
- }
16584
- });
16611
+ if (isReactNative()) {
16612
+ this.logger.info(
16613
+ "React Native: Remote audio from ontrack will be played automatically"
16614
+ );
16615
+ } else {
16616
+ this.createAudioElement();
16617
+ if (this.audioElement && this.remoteStream) {
16618
+ this.audioElement.srcObject = this.remoteStream;
16619
+ this.audioElement.play().catch((error) => {
16620
+ this.logger.warn("Failed to auto-play remote audio", { error });
16621
+ if (this.audioElement) {
16622
+ this.audioElement.controls = true;
16623
+ this.audioElement.style.display = "block";
16624
+ }
16625
+ });
16626
+ }
16585
16627
  }
16586
16628
  this.startAudioMonitoring();
16587
16629
  }
@@ -16610,8 +16652,19 @@ var SipManager = class extends EventEmitter {
16610
16652
  }
16611
16653
  /**
16612
16654
  * Create and configure the audio element for remote audio playback
16655
+ * In React Native, audio is handled differently through WebRTC streams
16613
16656
  */
16614
16657
  createAudioElement() {
16658
+ if (isReactNative()) {
16659
+ this.logger.debug(
16660
+ "React Native detected - skipping audio element creation (handled by WebRTC)"
16661
+ );
16662
+ return;
16663
+ }
16664
+ if (!isBrowser()) {
16665
+ this.logger.debug("Non-browser environment - skipping audio element creation");
16666
+ return;
16667
+ }
16615
16668
  if (this.audioElement) {
16616
16669
  return;
16617
16670
  }
@@ -16670,6 +16723,22 @@ var SipManager = class extends EventEmitter {
16670
16723
  if (!this.localStream || this.audioMonitorInterval) {
16671
16724
  return;
16672
16725
  }
16726
+ if (isReactNative() || !hasAudioContext()) {
16727
+ this.logger.debug("AudioContext not available - using simulated audio monitoring");
16728
+ this.audioMonitorInterval = setInterval(() => {
16729
+ if (this.state === "incall" && this.localStream) {
16730
+ const audioTrack = this.localStream.getAudioTracks()[0];
16731
+ if (audioTrack && audioTrack.enabled) {
16732
+ const simulatedLevel = 0.2 + Math.random() * 0.5;
16733
+ this.emit("audioLevel", simulatedLevel);
16734
+ } else {
16735
+ this.emit("audioLevel", 0);
16736
+ }
16737
+ }
16738
+ }, 100);
16739
+ this.logger.debug("Simulated audio monitoring started");
16740
+ return;
16741
+ }
16673
16742
  try {
16674
16743
  this.audioContext = new AudioContext();
16675
16744
  const source = this.audioContext.createMediaStreamSource(this.localStream);
@@ -16701,9 +16770,11 @@ var SipManager = class extends EventEmitter {
16701
16770
  delete this.audioAnalyser;
16702
16771
  }
16703
16772
  cleanupAudioResources() {
16704
- this.logger.debug("\u{1F9F9} Performing complete audio resource cleanup...");
16773
+ this.logger.debug("\u{1F9F9} Performing complete audio resource cleanup...", {
16774
+ platform: isReactNative() ? "react-native" : "browser"
16775
+ });
16705
16776
  this.stopAudioMonitoring();
16706
- if (this.audioElement) {
16777
+ if (this.audioElement && isBrowser()) {
16707
16778
  this.logger.debug("\u{1F9F9} Cleaning up audio element...");
16708
16779
  if (this.audioElement.srcObject) {
16709
16780
  const tracks = this.audioElement.srcObject.getTracks();
@@ -16864,7 +16935,7 @@ var TranscriptionService = class extends EventEmitter {
16864
16935
  * Connect to SSE endpoint with token refresh support
16865
16936
  */
16866
16937
  async connectToSSE(accessToken, isRetry = false) {
16867
- const sseUrl = `${this.apiUrl}/api/v1/events?event_types=transcription`;
16938
+ const sseUrl = `${this.apiUrl}/api/v1/events?event_types=transcription,call_event`;
16868
16939
  this.logger.debug("Connecting to SSE endpoint", { url: sseUrl, isRetry });
16869
16940
  const response = await fetch(sseUrl, {
16870
16941
  method: "GET",
@@ -16994,13 +17065,36 @@ var TranscriptionService = class extends EventEmitter {
16994
17065
  }
16995
17066
  }
16996
17067
  handleMessage(data) {
16997
- this.logger.debug("Handling SSE message", { data });
16998
- if (data.type === "transcription" && data.text) {
16999
- if (data.call_id && !this.currentCallId) {
17000
- this.currentCallId = data.call_id;
17001
- this.emit("callId", data.call_id);
17002
- this.logger.info("Captured call ID from transcription", { callId: data.call_id });
17068
+ this.logger.debug("Handling SSE message", { type: data.type, data });
17069
+ if (data.call_id && !this.currentCallId) {
17070
+ this.currentCallId = data.call_id;
17071
+ this.emit("callId", data.call_id);
17072
+ this.logger.info("Captured call ID", { callId: data.call_id, messageType: data.type });
17073
+ }
17074
+ if (data.type === "connection" || data.type === "welcome" || data.type === "connected") {
17075
+ this.logger.info("Received connection event", {
17076
+ type: data.type,
17077
+ status: data.status
17078
+ });
17079
+ if (data.type === "connected" || data.status === "connected") {
17080
+ this.emit("connected");
17081
+ }
17082
+ } else if (data.type === "call_started") {
17083
+ this.logger.info("Received call_started event", {
17084
+ callId: data.call_id,
17085
+ organizationId: data.organization_id
17086
+ });
17087
+ if (data.call_id) {
17088
+ this.emit("callStarted", data.call_id);
17003
17089
  }
17090
+ } else if (data.type === "call_ended") {
17091
+ this.logger.info("Received call_ended event", {
17092
+ callId: data.call_id
17093
+ });
17094
+ if (data.call_id) {
17095
+ this.emit("callEnded", data.call_id);
17096
+ }
17097
+ } else if (data.type === "transcription" && data.text) {
17004
17098
  const event = {
17005
17099
  speaker: data.direction === "agent" ? "agent" : "user",
17006
17100
  text: data.text.trim(),
@@ -17008,6 +17102,9 @@ var TranscriptionService = class extends EventEmitter {
17008
17102
  isFinal: true,
17009
17103
  // Assume final for now
17010
17104
  callId: data.call_id,
17105
+ humanTurnId: data.human_turn_id ?? void 0,
17106
+ agentTurnId: data.agent_turn_id ?? void 0,
17107
+ speakerId: data.speaker_id ?? void 0,
17011
17108
  metadata: data
17012
17109
  };
17013
17110
  this.logger.debug("Emitting transcription event", { event });
@@ -17079,7 +17176,8 @@ var ApiService = class {
17079
17176
  this.logger.debug("Sending voice text", {
17080
17177
  callId: options.callId,
17081
17178
  textLength: options.text.length,
17082
- interrupt: options.interrupt,
17179
+ interruptsConversation: options.interruptsConversation,
17180
+ queueWhenSpeaking: options.queueWhenSpeaking,
17083
17181
  muteAgent: options.muteAgent
17084
17182
  });
17085
17183
  try {
@@ -17093,10 +17191,10 @@ var ApiService = class {
17093
17191
  },
17094
17192
  body: JSON.stringify({
17095
17193
  text: options.text,
17096
- interrupts_conversation: options.interrupt !== false,
17097
- // Default true
17098
- queue_when_speaking: false,
17099
- mute_agent: options.muteAgent
17194
+ interrupts_conversation: options.interruptsConversation ?? true,
17195
+ queue_when_speaking: options.queueWhenSpeaking ?? false,
17196
+ mute_agent: options.muteAgent ?? null,
17197
+ voice_settings: options.voiceSettings ?? null
17100
17198
  })
17101
17199
  }
17102
17200
  );
@@ -17182,11 +17280,103 @@ var ApiService = class {
17182
17280
  throw new NetworkError("Failed to set agent mute status", { cause: error });
17183
17281
  }
17184
17282
  }
17283
+ /**
17284
+ * Mute a call (POST /calls/{call_id}/mute)
17285
+ */
17286
+ async muteCall(callId, accessToken) {
17287
+ this.logger.debug("Muting call", { callId });
17288
+ try {
17289
+ const response = await fetch(`${this.apiUrl}/api/v1/conversations/calls/${callId}/mute`, {
17290
+ method: "POST",
17291
+ headers: {
17292
+ Authorization: `Bearer ${accessToken}`
17293
+ }
17294
+ });
17295
+ if (response.status === 401) {
17296
+ throw new AuthenticationError("Authentication failed");
17297
+ }
17298
+ if (!response.ok) {
17299
+ const errorText = await response.text();
17300
+ throw new NetworkError(
17301
+ `Failed to mute call: ${response.status} ${response.statusText} - ${errorText}`
17302
+ );
17303
+ }
17304
+ this.logger.info("Call muted successfully", { callId });
17305
+ } catch (error) {
17306
+ if (error instanceof AuthenticationError || error instanceof NetworkError) {
17307
+ throw error;
17308
+ }
17309
+ this.logger.error("Failed to mute call", { error });
17310
+ throw new NetworkError("Failed to mute call", { cause: error });
17311
+ }
17312
+ }
17313
+ /**
17314
+ * Unmute a call (DELETE /calls/{call_id}/mute)
17315
+ */
17316
+ async unmuteCall(callId, accessToken) {
17317
+ this.logger.debug("Unmuting call", { callId });
17318
+ try {
17319
+ const response = await fetch(`${this.apiUrl}/api/v1/conversations/calls/${callId}/mute`, {
17320
+ method: "DELETE",
17321
+ headers: {
17322
+ Authorization: `Bearer ${accessToken}`
17323
+ }
17324
+ });
17325
+ if (response.status === 401) {
17326
+ throw new AuthenticationError("Authentication failed");
17327
+ }
17328
+ if (!response.ok) {
17329
+ const errorText = await response.text();
17330
+ throw new NetworkError(
17331
+ `Failed to unmute call: ${response.status} ${response.statusText} - ${errorText}`
17332
+ );
17333
+ }
17334
+ this.logger.info("Call unmuted successfully", { callId });
17335
+ } catch (error) {
17336
+ if (error instanceof AuthenticationError || error instanceof NetworkError) {
17337
+ throw error;
17338
+ }
17339
+ this.logger.error("Failed to unmute call", { error });
17340
+ throw new NetworkError("Failed to unmute call", { cause: error });
17341
+ }
17342
+ }
17343
+ /**
17344
+ * Get call mute status (GET /calls/{call_id}/mute)
17345
+ */
17346
+ async getCallMuteStatus(callId, accessToken) {
17347
+ this.logger.debug("Getting call mute status", { callId });
17348
+ try {
17349
+ const response = await fetch(`${this.apiUrl}/api/v1/conversations/calls/${callId}/mute`, {
17350
+ method: "GET",
17351
+ headers: {
17352
+ Authorization: `Bearer ${accessToken}`
17353
+ }
17354
+ });
17355
+ if (response.status === 401) {
17356
+ throw new AuthenticationError("Authentication failed");
17357
+ }
17358
+ if (!response.ok) {
17359
+ const errorText = await response.text();
17360
+ throw new NetworkError(
17361
+ `Failed to get call mute status: ${response.status} ${response.statusText} - ${errorText}`
17362
+ );
17363
+ }
17364
+ const data = await response.json();
17365
+ this.logger.debug("Call mute status retrieved", { callId, muted: data.muted });
17366
+ return data;
17367
+ } catch (error) {
17368
+ if (error instanceof AuthenticationError || error instanceof NetworkError) {
17369
+ throw error;
17370
+ }
17371
+ this.logger.error("Failed to get call mute status", { error });
17372
+ throw new NetworkError("Failed to get call mute status", { cause: error });
17373
+ }
17374
+ }
17185
17375
  };
17186
17376
 
17187
17377
  // src/conversation/Conversation.ts
17188
17378
  var Conversation = class extends EventEmitter {
17189
- constructor(id, options, authManager, apiUrl) {
17379
+ constructor(id, options, authManager, urls) {
17190
17380
  super();
17191
17381
  this.logger = getLogger(["angany", "sdk", "conversation"]);
17192
17382
  this.state = "idle";
@@ -17195,12 +17385,17 @@ var Conversation = class extends EventEmitter {
17195
17385
  this.id = id;
17196
17386
  this.options = options;
17197
17387
  this.authManager = authManager;
17198
- this.apiUrl = apiUrl;
17388
+ this.urls = urls;
17389
+ const sseUrl = urls.sseUrl || urls.apiUrl;
17199
17390
  this.sipManager = new SipManager();
17200
- this.transcriptionService = new TranscriptionService(apiUrl);
17201
- this.apiService = new ApiService(apiUrl);
17391
+ this.transcriptionService = new TranscriptionService(sseUrl);
17392
+ this.apiService = new ApiService(urls.apiUrl);
17202
17393
  this.logger = this.logger.with({ conversationId: id, resource: options.resource });
17203
- this.logger.debug("Conversation created");
17394
+ this.logger.debug("Conversation created", {
17395
+ apiUrl: urls.apiUrl,
17396
+ sseUrl,
17397
+ sipUrl: urls.sipUrl || "will be derived"
17398
+ });
17204
17399
  }
17205
17400
  /**
17206
17401
  * Initialize and start the conversation
@@ -17297,8 +17492,12 @@ var Conversation = class extends EventEmitter {
17297
17492
  if (this.ephemeralCredentials.sip.realm) {
17298
17493
  sipConfig.realm = this.ephemeralCredentials.sip.realm;
17299
17494
  }
17300
- if (this.ephemeralCredentials.sip.websocketUrl) {
17495
+ if (this.urls.sipUrl) {
17496
+ sipConfig.websocketUrl = this.urls.sipUrl;
17497
+ this.logger.debug("Using configured SIP URL", { url: sipConfig.websocketUrl });
17498
+ } else if (this.ephemeralCredentials.sip.websocketUrl) {
17301
17499
  sipConfig.websocketUrl = this.ephemeralCredentials.sip.websocketUrl;
17500
+ this.logger.debug("Using platform-provided WebSocket URL", { url: sipConfig.websocketUrl });
17302
17501
  } else if (this.ephemeralCredentials.sip.uris && this.ephemeralCredentials.sip.uris.length > 0) {
17303
17502
  const wssUris = this.ephemeralCredentials.sip.uris.filter(
17304
17503
  (uri) => uri.includes("transport=wss")
@@ -17309,7 +17508,7 @@ var Conversation = class extends EventEmitter {
17309
17508
  const wsUri = publicUri || wssUris[0];
17310
17509
  if (wsUri) {
17311
17510
  this.logger.debug("Selected SIP URI", { uri: wsUri, isPublic: !!publicUri });
17312
- const apiDomain = new URL(this.apiUrl).hostname;
17511
+ const apiDomain = new URL(this.urls.apiUrl).hostname;
17313
17512
  sipConfig.websocketUrl = `wss://${apiDomain}/api/webrtc/`;
17314
17513
  this.logger.debug("Derived WebSocket URL from API domain", {
17315
17514
  apiDomain,
@@ -17317,15 +17516,18 @@ var Conversation = class extends EventEmitter {
17317
17516
  });
17318
17517
  }
17319
17518
  }
17320
- if (sipConfig.websocketUrl) {
17519
+ if (sipConfig.websocketUrl && !this.urls.sipUrl) {
17321
17520
  this.logger.debug("Original WebSocket URL", { url: sipConfig.websocketUrl });
17322
17521
  const privateIpPattern = /wss?:\/\/(192\.168\.|10\.|172\.(1[6-9]|2[0-9]|3[01])\.)/;
17323
17522
  if (privateIpPattern.test(sipConfig.websocketUrl)) {
17324
17523
  this.logger.warn("Detected private IP in WebSocket URL, replacing with API domain");
17325
- const apiDomain = new URL(this.apiUrl).hostname;
17524
+ const apiDomain = new URL(this.urls.apiUrl).hostname;
17326
17525
  const originalUrl = sipConfig.websocketUrl;
17327
17526
  const correctedUrl = `wss://${apiDomain}/api/webrtc/`;
17328
- this.logger.debug("Extracted API domain", { domain: apiDomain, apiUrl: this.apiUrl });
17527
+ this.logger.debug("Extracted API domain", {
17528
+ domain: apiDomain,
17529
+ apiUrl: this.urls.apiUrl
17530
+ });
17329
17531
  sipConfig.websocketUrl = correctedUrl;
17330
17532
  this.logger.info("Corrected WebSocket URL", {
17331
17533
  original: originalUrl,
@@ -17397,11 +17599,11 @@ var Conversation = class extends EventEmitter {
17397
17599
  const sendVoiceOptions = {
17398
17600
  text,
17399
17601
  callId: this.callId,
17400
- muteAgent: this.agentMuted
17602
+ interruptsConversation: options?.interruptsConversation,
17603
+ queueWhenSpeaking: options?.queueWhenSpeaking,
17604
+ muteAgent: options?.muteAgent,
17605
+ voiceSettings: options?.voiceSettings
17401
17606
  };
17402
- if (options?.interrupt !== void 0) {
17403
- sendVoiceOptions.interrupt = options.interrupt;
17404
- }
17405
17607
  try {
17406
17608
  await this.apiService.sendVoice(sendVoiceOptions, this.accessToken);
17407
17609
  } catch (error) {
@@ -17488,6 +17690,118 @@ var Conversation = class extends EventEmitter {
17488
17690
  isAgentMuted() {
17489
17691
  return this.agentMuted;
17490
17692
  }
17693
+ /**
17694
+ * Get the current call ID (available after connection)
17695
+ */
17696
+ getCallId() {
17697
+ return this.callId;
17698
+ }
17699
+ /**
17700
+ * Mute the call via API (POST /calls/{call_id}/mute)
17701
+ */
17702
+ async muteCall() {
17703
+ this.logger.debug("Muting call via API");
17704
+ if (!this.callId) {
17705
+ throw new ConversationError("No call ID available - call may not be connected yet");
17706
+ }
17707
+ if (!this.accessToken) {
17708
+ throw new AuthenticationError("No access token available");
17709
+ }
17710
+ try {
17711
+ await this.apiService.muteCall(this.callId, this.accessToken);
17712
+ this.logger.info("Call muted via API", { callId: this.callId });
17713
+ } catch (error) {
17714
+ if (error instanceof AuthenticationError) {
17715
+ this.logger.debug("API call failed with auth error, attempting token refresh");
17716
+ try {
17717
+ const freshToken = await this.authManager.getAccessToken();
17718
+ if (freshToken && freshToken !== this.accessToken) {
17719
+ this.accessToken = freshToken;
17720
+ this.logger.debug("Token refreshed, retrying mute call operation");
17721
+ await this.apiService.muteCall(this.callId, this.accessToken);
17722
+ return;
17723
+ }
17724
+ throw error;
17725
+ } catch (refreshError) {
17726
+ this.logger.error("Failed to refresh token for mute call operation", {
17727
+ error: refreshError
17728
+ });
17729
+ throw error;
17730
+ }
17731
+ }
17732
+ throw error;
17733
+ }
17734
+ }
17735
+ /**
17736
+ * Unmute the call via API (DELETE /calls/{call_id}/mute)
17737
+ */
17738
+ async unmuteCall() {
17739
+ this.logger.debug("Unmuting call via API");
17740
+ if (!this.callId) {
17741
+ throw new ConversationError("No call ID available - call may not be connected yet");
17742
+ }
17743
+ if (!this.accessToken) {
17744
+ throw new AuthenticationError("No access token available");
17745
+ }
17746
+ try {
17747
+ await this.apiService.unmuteCall(this.callId, this.accessToken);
17748
+ this.logger.info("Call unmuted via API", { callId: this.callId });
17749
+ } catch (error) {
17750
+ if (error instanceof AuthenticationError) {
17751
+ this.logger.debug("API call failed with auth error, attempting token refresh");
17752
+ try {
17753
+ const freshToken = await this.authManager.getAccessToken();
17754
+ if (freshToken && freshToken !== this.accessToken) {
17755
+ this.accessToken = freshToken;
17756
+ this.logger.debug("Token refreshed, retrying unmute call operation");
17757
+ await this.apiService.unmuteCall(this.callId, this.accessToken);
17758
+ return;
17759
+ }
17760
+ throw error;
17761
+ } catch (refreshError) {
17762
+ this.logger.error("Failed to refresh token for unmute call operation", {
17763
+ error: refreshError
17764
+ });
17765
+ throw error;
17766
+ }
17767
+ }
17768
+ throw error;
17769
+ }
17770
+ }
17771
+ /**
17772
+ * Get call mute status via API (GET /calls/{call_id}/mute)
17773
+ */
17774
+ async getCallMuteStatus() {
17775
+ this.logger.debug("Getting call mute status via API");
17776
+ if (!this.callId) {
17777
+ throw new ConversationError("No call ID available - call may not be connected yet");
17778
+ }
17779
+ if (!this.accessToken) {
17780
+ throw new AuthenticationError("No access token available");
17781
+ }
17782
+ try {
17783
+ return await this.apiService.getCallMuteStatus(this.callId, this.accessToken);
17784
+ } catch (error) {
17785
+ if (error instanceof AuthenticationError) {
17786
+ this.logger.debug("API call failed with auth error, attempting token refresh");
17787
+ try {
17788
+ const freshToken = await this.authManager.getAccessToken();
17789
+ if (freshToken && freshToken !== this.accessToken) {
17790
+ this.accessToken = freshToken;
17791
+ this.logger.debug("Token refreshed, retrying get call mute status operation");
17792
+ return await this.apiService.getCallMuteStatus(this.callId, this.accessToken);
17793
+ }
17794
+ throw error;
17795
+ } catch (refreshError) {
17796
+ this.logger.error("Failed to refresh token for get call mute status operation", {
17797
+ error: refreshError
17798
+ });
17799
+ throw error;
17800
+ }
17801
+ }
17802
+ throw error;
17803
+ }
17804
+ }
17491
17805
  /**
17492
17806
  * Get conversation status
17493
17807
  */
@@ -17643,12 +17957,24 @@ var Conversation = class extends EventEmitter {
17643
17957
  speaker: event.speaker,
17644
17958
  text: event.text,
17645
17959
  timestamp: event.timestamp,
17646
- isFinal: event.isFinal
17960
+ isFinal: event.isFinal,
17961
+ humanTurnId: event.humanTurnId,
17962
+ agentTurnId: event.agentTurnId,
17963
+ speakerId: event.speakerId
17647
17964
  });
17648
17965
  });
17649
17966
  this.transcriptionService.on("callId", (callId) => {
17650
17967
  this.logger.info("Received call ID", { callId });
17651
17968
  this.callId = callId;
17969
+ this.emit("callId", callId);
17970
+ });
17971
+ this.transcriptionService.on("callStarted", (callId) => {
17972
+ this.logger.info("Call started", { callId });
17973
+ this.emit("callStarted", callId);
17974
+ });
17975
+ this.transcriptionService.on("callEnded", (callId) => {
17976
+ this.logger.info("Call ended", { callId });
17977
+ this.emit("callEnded", callId);
17652
17978
  });
17653
17979
  this.transcriptionService.on("error", (error) => {
17654
17980
  this.logger.error("Transcription error", { error });
@@ -17706,8 +18032,12 @@ var AnganyVoice = class extends EventEmitter {
17706
18032
  this.logger = getLogger(["angany", "sdk", "core"]);
17707
18033
  this.conversations = /* @__PURE__ */ new Map();
17708
18034
  this.config = config;
17709
- this.auth = new AuthManager(config.apiUrl, config.apiUrl);
17710
- this.logger.debug("AnganyVoice initialized", { apiUrl: config.apiUrl });
18035
+ this.auth = new AuthManager(config.apiUrl, config.issuer || config.apiUrl);
18036
+ this.logger.debug("AnganyVoice initialized", {
18037
+ apiUrl: config.apiUrl,
18038
+ sseUrl: config.sseUrl || config.apiUrl,
18039
+ sipUrl: config.sipUrl || "derived from apiUrl"
18040
+ });
17711
18041
  }
17712
18042
  /**
17713
18043
  * Get the current configuration
@@ -17742,7 +18072,11 @@ var AnganyVoice = class extends EventEmitter {
17742
18072
  ...options
17743
18073
  },
17744
18074
  this.auth,
17745
- this.config.apiUrl
18075
+ {
18076
+ apiUrl: this.config.apiUrl,
18077
+ sseUrl: this.config.sseUrl,
18078
+ sipUrl: this.config.sipUrl
18079
+ }
17746
18080
  );
17747
18081
  conversation.on("ended", () => {
17748
18082
  this.conversations.delete(conversationId);
@@ -17795,7 +18129,7 @@ var AnganyVoice = class extends EventEmitter {
17795
18129
  };
17796
18130
 
17797
18131
  // src/version.ts
17798
- var VERSION = "0.0.1";
18132
+ var VERSION = "0.0.2";
17799
18133
 
17800
18134
  export { AnganyError, AnganyVoice, AuthManager, AuthenticationError, ConfigurationError, Conversation, ConversationError, ErrorCodes, EventEmitter, MediaError, NetworkError, PermissionError, ResourceError, VERSION, ValidationError, hasErrorCode, isAnganyError };
17801
18135
  //# sourceMappingURL=index.js.map