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