@anganyai/voice-sdk 0.0.2 → 0.0.5

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");
@@ -16601,7 +16611,9 @@ var SipManager = class extends EventEmitter {
16601
16611
  this.remoteStream = event.streams[0];
16602
16612
  this.logger.info("Remote audio stream received via ontrack fallback");
16603
16613
  if (isReactNative()) {
16604
- this.logger.info("React Native: Remote audio from ontrack will be played automatically");
16614
+ this.logger.info(
16615
+ "React Native: Remote audio from ontrack will be played automatically"
16616
+ );
16605
16617
  } else {
16606
16618
  this.createAudioElement();
16607
16619
  if (this.audioElement && this.remoteStream) {
@@ -16646,7 +16658,9 @@ var SipManager = class extends EventEmitter {
16646
16658
  */
16647
16659
  createAudioElement() {
16648
16660
  if (isReactNative()) {
16649
- this.logger.debug("React Native detected - skipping audio element creation (handled by WebRTC)");
16661
+ this.logger.debug(
16662
+ "React Native detected - skipping audio element creation (handled by WebRTC)"
16663
+ );
16650
16664
  return;
16651
16665
  }
16652
16666
  if (!isBrowser()) {
@@ -16923,7 +16937,7 @@ var TranscriptionService = class extends EventEmitter {
16923
16937
  * Connect to SSE endpoint with token refresh support
16924
16938
  */
16925
16939
  async connectToSSE(accessToken, isRetry = false) {
16926
- const sseUrl = `${this.apiUrl}/api/v1/events?event_types=transcription`;
16940
+ const sseUrl = `${this.apiUrl}/api/v1/events?event_types=transcription,call_event`;
16927
16941
  this.logger.debug("Connecting to SSE endpoint", { url: sseUrl, isRetry });
16928
16942
  const response = await fetch(sseUrl, {
16929
16943
  method: "GET",
@@ -17053,13 +17067,36 @@ var TranscriptionService = class extends EventEmitter {
17053
17067
  }
17054
17068
  }
17055
17069
  handleMessage(data) {
17056
- this.logger.debug("Handling SSE message", { data });
17057
- if (data.type === "transcription" && data.text) {
17058
- if (data.call_id && !this.currentCallId) {
17059
- this.currentCallId = data.call_id;
17060
- this.emit("callId", data.call_id);
17061
- 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);
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);
17062
17098
  }
17099
+ } else if (data.type === "transcription" && data.text) {
17063
17100
  const event = {
17064
17101
  speaker: data.direction === "agent" ? "agent" : "user",
17065
17102
  text: data.text.trim(),
@@ -17067,6 +17104,9 @@ var TranscriptionService = class extends EventEmitter {
17067
17104
  isFinal: true,
17068
17105
  // Assume final for now
17069
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,
17070
17110
  metadata: data
17071
17111
  };
17072
17112
  this.logger.debug("Emitting transcription event", { event });
@@ -17138,7 +17178,8 @@ var ApiService = class {
17138
17178
  this.logger.debug("Sending voice text", {
17139
17179
  callId: options.callId,
17140
17180
  textLength: options.text.length,
17141
- interrupt: options.interrupt,
17181
+ interruptsConversation: options.interruptsConversation,
17182
+ queueWhenSpeaking: options.queueWhenSpeaking,
17142
17183
  muteAgent: options.muteAgent
17143
17184
  });
17144
17185
  try {
@@ -17152,10 +17193,10 @@ var ApiService = class {
17152
17193
  },
17153
17194
  body: JSON.stringify({
17154
17195
  text: options.text,
17155
- interrupts_conversation: options.interrupt !== false,
17156
- // Default true
17157
- queue_when_speaking: false,
17158
- 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
17159
17200
  })
17160
17201
  }
17161
17202
  );
@@ -17241,11 +17282,103 @@ var ApiService = class {
17241
17282
  throw new NetworkError("Failed to set agent mute status", { cause: error });
17242
17283
  }
17243
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
+ }
17244
17377
  };
17245
17378
 
17246
17379
  // src/conversation/Conversation.ts
17247
17380
  var Conversation = class extends EventEmitter {
17248
- constructor(id, options, authManager, apiUrl) {
17381
+ constructor(id, options, authManager, urls) {
17249
17382
  super();
17250
17383
  this.logger = getLogger(["angany", "sdk", "conversation"]);
17251
17384
  this.state = "idle";
@@ -17254,12 +17387,17 @@ var Conversation = class extends EventEmitter {
17254
17387
  this.id = id;
17255
17388
  this.options = options;
17256
17389
  this.authManager = authManager;
17257
- this.apiUrl = apiUrl;
17390
+ this.urls = urls;
17391
+ const sseUrl = urls.sseUrl || urls.apiUrl;
17258
17392
  this.sipManager = new SipManager();
17259
- this.transcriptionService = new TranscriptionService(apiUrl);
17260
- this.apiService = new ApiService(apiUrl);
17393
+ this.transcriptionService = new TranscriptionService(sseUrl);
17394
+ this.apiService = new ApiService(urls.apiUrl);
17261
17395
  this.logger = this.logger.with({ conversationId: id, resource: options.resource });
17262
- 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
+ });
17263
17401
  }
17264
17402
  /**
17265
17403
  * Initialize and start the conversation
@@ -17282,27 +17420,33 @@ var Conversation = class extends EventEmitter {
17282
17420
  throw new AuthenticationError("Not authenticated");
17283
17421
  }
17284
17422
  this.logger.debug("\u2713 Authentication check passed");
17285
- this.logger.debug("=== STEP 2: GETTING ACCESS TOKEN ===");
17423
+ this.logger.debug("=== STEP 2: GETTING ACCESS TOKEN (OPTIONAL) ===");
17286
17424
  let accessToken = authStatus.tokens?.accessToken;
17287
17425
  this.logger.debug("OAuth access token", { hasOAuthToken: !!accessToken });
17288
17426
  if (!accessToken) {
17289
17427
  this.logger.debug("No OAuth access token in auth status, trying to get from AuthManager");
17290
- accessToken = await this.authManager.getAccessToken();
17428
+ try {
17429
+ accessToken = await this.authManager.getAccessToken();
17430
+ } catch {
17431
+ }
17291
17432
  this.logger.debug("Access token from AuthManager", { hasToken: !!accessToken });
17292
17433
  }
17293
17434
  if (!accessToken) {
17294
- this.logger.error("No access token available for API calls", {
17295
- hasOAuthToken: !!authStatus.tokens?.accessToken,
17296
- hasEphemeralCredentials: this.authManager.hasValidEphemeralCredentials()
17435
+ this.logger.debug("No OAuth access token, checking ephemeral credentials for API token");
17436
+ const cachedCreds = this.authManager.getCachedEphemeralCredentials();
17437
+ if (cachedCreds?.apiToken) {
17438
+ accessToken = cachedCreds.apiToken;
17439
+ this.logger.debug("Using API token from ephemeral credentials");
17440
+ }
17441
+ }
17442
+ if (!accessToken) {
17443
+ this.logger.info("No access token available - running in SIP-only mode (no transcription/API features)");
17444
+ } else {
17445
+ this.accessToken = accessToken;
17446
+ this.logger.debug("\u2713 Access token obtained for API calls", {
17447
+ tokenLength: accessToken.length
17297
17448
  });
17298
- throw new AuthenticationError(
17299
- "No access token available for API calls. Ephemeral credentials are only for SIP/WebRTC connections."
17300
- );
17301
17449
  }
17302
- this.accessToken = accessToken;
17303
- this.logger.debug("\u2713 Access token obtained for API calls", {
17304
- tokenLength: accessToken.length
17305
- });
17306
17450
  this.logger.debug("=== STEP 3: ENSURING EPHEMERAL CREDENTIALS ===");
17307
17451
  if (!this.ephemeralCredentials) {
17308
17452
  this.logger.debug("Getting ephemeral credentials");
@@ -17356,8 +17500,12 @@ var Conversation = class extends EventEmitter {
17356
17500
  if (this.ephemeralCredentials.sip.realm) {
17357
17501
  sipConfig.realm = this.ephemeralCredentials.sip.realm;
17358
17502
  }
17359
- if (this.ephemeralCredentials.sip.websocketUrl) {
17503
+ if (this.urls.sipUrl) {
17504
+ sipConfig.websocketUrl = this.urls.sipUrl;
17505
+ this.logger.debug("Using configured SIP URL", { url: sipConfig.websocketUrl });
17506
+ } else if (this.ephemeralCredentials.sip.websocketUrl) {
17360
17507
  sipConfig.websocketUrl = this.ephemeralCredentials.sip.websocketUrl;
17508
+ this.logger.debug("Using platform-provided WebSocket URL", { url: sipConfig.websocketUrl });
17361
17509
  } else if (this.ephemeralCredentials.sip.uris && this.ephemeralCredentials.sip.uris.length > 0) {
17362
17510
  const wssUris = this.ephemeralCredentials.sip.uris.filter(
17363
17511
  (uri) => uri.includes("transport=wss")
@@ -17368,7 +17516,7 @@ var Conversation = class extends EventEmitter {
17368
17516
  const wsUri = publicUri || wssUris[0];
17369
17517
  if (wsUri) {
17370
17518
  this.logger.debug("Selected SIP URI", { uri: wsUri, isPublic: !!publicUri });
17371
- const apiDomain = new URL(this.apiUrl).hostname;
17519
+ const apiDomain = new URL(this.urls.apiUrl).hostname;
17372
17520
  sipConfig.websocketUrl = `wss://${apiDomain}/api/webrtc/`;
17373
17521
  this.logger.debug("Derived WebSocket URL from API domain", {
17374
17522
  apiDomain,
@@ -17376,15 +17524,18 @@ var Conversation = class extends EventEmitter {
17376
17524
  });
17377
17525
  }
17378
17526
  }
17379
- if (sipConfig.websocketUrl) {
17527
+ if (sipConfig.websocketUrl && !this.urls.sipUrl) {
17380
17528
  this.logger.debug("Original WebSocket URL", { url: sipConfig.websocketUrl });
17381
17529
  const privateIpPattern = /wss?:\/\/(192\.168\.|10\.|172\.(1[6-9]|2[0-9]|3[01])\.)/;
17382
17530
  if (privateIpPattern.test(sipConfig.websocketUrl)) {
17383
17531
  this.logger.warn("Detected private IP in WebSocket URL, replacing with API domain");
17384
- const apiDomain = new URL(this.apiUrl).hostname;
17532
+ const apiDomain = new URL(this.urls.apiUrl).hostname;
17385
17533
  const originalUrl = sipConfig.websocketUrl;
17386
17534
  const correctedUrl = `wss://${apiDomain}/api/webrtc/`;
17387
- this.logger.debug("Extracted API domain", { domain: apiDomain, apiUrl: this.apiUrl });
17535
+ this.logger.debug("Extracted API domain", {
17536
+ domain: apiDomain,
17537
+ apiUrl: this.urls.apiUrl
17538
+ });
17388
17539
  sipConfig.websocketUrl = correctedUrl;
17389
17540
  this.logger.info("Corrected WebSocket URL", {
17390
17541
  original: originalUrl,
@@ -17404,19 +17555,23 @@ var Conversation = class extends EventEmitter {
17404
17555
  this.setupSipHandlers();
17405
17556
  this.logger.debug("Registering with SIP server");
17406
17557
  await this.sipManager.register();
17407
- this.transcriptionService.setTokenRefreshCallback(async () => {
17408
- this.logger.debug("TranscriptionService requesting fresh token");
17409
- const freshToken = await this.authManager.getAccessToken();
17410
- if (!freshToken) {
17411
- throw new Error("Unable to get fresh access token");
17412
- }
17413
- this.accessToken = freshToken;
17414
- this.logger.debug("Fresh token provided to TranscriptionService");
17415
- return freshToken;
17416
- });
17417
- this.logger.debug("Starting transcription stream");
17418
- await this.transcriptionService.start(this.accessToken);
17419
- this.setupTranscriptionHandlers();
17558
+ if (this.accessToken) {
17559
+ this.transcriptionService.setTokenRefreshCallback(async () => {
17560
+ this.logger.debug("TranscriptionService requesting fresh token");
17561
+ const freshToken = await this.authManager.getAccessToken();
17562
+ if (!freshToken) {
17563
+ throw new Error("Unable to get fresh access token");
17564
+ }
17565
+ this.accessToken = freshToken;
17566
+ this.logger.debug("Fresh token provided to TranscriptionService");
17567
+ return freshToken;
17568
+ });
17569
+ this.logger.debug("Starting transcription stream");
17570
+ await this.transcriptionService.start(this.accessToken);
17571
+ this.setupTranscriptionHandlers();
17572
+ } else {
17573
+ this.logger.info("Skipping transcription service - no access token available");
17574
+ }
17420
17575
  this.logger.debug("Making call to resource", { resourceId: this.options.resource });
17421
17576
  const callOptions = {
17422
17577
  resourceId: this.options.resource,
@@ -17456,11 +17611,11 @@ var Conversation = class extends EventEmitter {
17456
17611
  const sendVoiceOptions = {
17457
17612
  text,
17458
17613
  callId: this.callId,
17459
- muteAgent: this.agentMuted
17614
+ interruptsConversation: options?.interruptsConversation,
17615
+ queueWhenSpeaking: options?.queueWhenSpeaking,
17616
+ muteAgent: options?.muteAgent,
17617
+ voiceSettings: options?.voiceSettings
17460
17618
  };
17461
- if (options?.interrupt !== void 0) {
17462
- sendVoiceOptions.interrupt = options.interrupt;
17463
- }
17464
17619
  try {
17465
17620
  await this.apiService.sendVoice(sendVoiceOptions, this.accessToken);
17466
17621
  } catch (error) {
@@ -17547,6 +17702,118 @@ var Conversation = class extends EventEmitter {
17547
17702
  isAgentMuted() {
17548
17703
  return this.agentMuted;
17549
17704
  }
17705
+ /**
17706
+ * Get the current call ID (available after connection)
17707
+ */
17708
+ getCallId() {
17709
+ return this.callId;
17710
+ }
17711
+ /**
17712
+ * Mute the call via API (POST /calls/{call_id}/mute)
17713
+ */
17714
+ async muteCall() {
17715
+ this.logger.debug("Muting call via API");
17716
+ if (!this.callId) {
17717
+ throw new ConversationError("No call ID available - call may not be connected yet");
17718
+ }
17719
+ if (!this.accessToken) {
17720
+ throw new AuthenticationError("No access token available");
17721
+ }
17722
+ try {
17723
+ await this.apiService.muteCall(this.callId, this.accessToken);
17724
+ this.logger.info("Call muted via API", { callId: this.callId });
17725
+ } catch (error) {
17726
+ if (error instanceof AuthenticationError) {
17727
+ this.logger.debug("API call failed with auth error, attempting token refresh");
17728
+ try {
17729
+ const freshToken = await this.authManager.getAccessToken();
17730
+ if (freshToken && freshToken !== this.accessToken) {
17731
+ this.accessToken = freshToken;
17732
+ this.logger.debug("Token refreshed, retrying mute call operation");
17733
+ await this.apiService.muteCall(this.callId, this.accessToken);
17734
+ return;
17735
+ }
17736
+ throw error;
17737
+ } catch (refreshError) {
17738
+ this.logger.error("Failed to refresh token for mute call operation", {
17739
+ error: refreshError
17740
+ });
17741
+ throw error;
17742
+ }
17743
+ }
17744
+ throw error;
17745
+ }
17746
+ }
17747
+ /**
17748
+ * Unmute the call via API (DELETE /calls/{call_id}/mute)
17749
+ */
17750
+ async unmuteCall() {
17751
+ this.logger.debug("Unmuting call via API");
17752
+ if (!this.callId) {
17753
+ throw new ConversationError("No call ID available - call may not be connected yet");
17754
+ }
17755
+ if (!this.accessToken) {
17756
+ throw new AuthenticationError("No access token available");
17757
+ }
17758
+ try {
17759
+ await this.apiService.unmuteCall(this.callId, this.accessToken);
17760
+ this.logger.info("Call unmuted via API", { callId: this.callId });
17761
+ } catch (error) {
17762
+ if (error instanceof AuthenticationError) {
17763
+ this.logger.debug("API call failed with auth error, attempting token refresh");
17764
+ try {
17765
+ const freshToken = await this.authManager.getAccessToken();
17766
+ if (freshToken && freshToken !== this.accessToken) {
17767
+ this.accessToken = freshToken;
17768
+ this.logger.debug("Token refreshed, retrying unmute call operation");
17769
+ await this.apiService.unmuteCall(this.callId, this.accessToken);
17770
+ return;
17771
+ }
17772
+ throw error;
17773
+ } catch (refreshError) {
17774
+ this.logger.error("Failed to refresh token for unmute call operation", {
17775
+ error: refreshError
17776
+ });
17777
+ throw error;
17778
+ }
17779
+ }
17780
+ throw error;
17781
+ }
17782
+ }
17783
+ /**
17784
+ * Get call mute status via API (GET /calls/{call_id}/mute)
17785
+ */
17786
+ async getCallMuteStatus() {
17787
+ this.logger.debug("Getting call mute status via API");
17788
+ if (!this.callId) {
17789
+ throw new ConversationError("No call ID available - call may not be connected yet");
17790
+ }
17791
+ if (!this.accessToken) {
17792
+ throw new AuthenticationError("No access token available");
17793
+ }
17794
+ try {
17795
+ return await this.apiService.getCallMuteStatus(this.callId, this.accessToken);
17796
+ } catch (error) {
17797
+ if (error instanceof AuthenticationError) {
17798
+ this.logger.debug("API call failed with auth error, attempting token refresh");
17799
+ try {
17800
+ const freshToken = await this.authManager.getAccessToken();
17801
+ if (freshToken && freshToken !== this.accessToken) {
17802
+ this.accessToken = freshToken;
17803
+ this.logger.debug("Token refreshed, retrying get call mute status operation");
17804
+ return await this.apiService.getCallMuteStatus(this.callId, this.accessToken);
17805
+ }
17806
+ throw error;
17807
+ } catch (refreshError) {
17808
+ this.logger.error("Failed to refresh token for get call mute status operation", {
17809
+ error: refreshError
17810
+ });
17811
+ throw error;
17812
+ }
17813
+ }
17814
+ throw error;
17815
+ }
17816
+ }
17550
17817
  /**
17551
17818
  * Get conversation status
17552
17819
  */
@@ -17702,12 +17969,24 @@ var Conversation = class extends EventEmitter {
17702
17969
  speaker: event.speaker,
17703
17970
  text: event.text,
17704
17971
  timestamp: event.timestamp,
17705
- isFinal: event.isFinal
17972
+ isFinal: event.isFinal,
17973
+ humanTurnId: event.humanTurnId,
17974
+ agentTurnId: event.agentTurnId,
17975
+ speakerId: event.speakerId
17706
17976
  });
17707
17977
  });
17708
17978
  this.transcriptionService.on("callId", (callId) => {
17709
17979
  this.logger.info("Received call ID", { callId });
17710
17980
  this.callId = callId;
17981
+ this.emit("callId", callId);
17982
+ });
17983
+ this.transcriptionService.on("callStarted", (callId) => {
17984
+ this.logger.info("Call started", { callId });
17985
+ this.emit("callStarted", callId);
17986
+ });
17987
+ this.transcriptionService.on("callEnded", (callId) => {
17988
+ this.logger.info("Call ended", { callId });
17989
+ this.emit("callEnded", callId);
17711
17990
  });
17712
17991
  this.transcriptionService.on("error", (error) => {
17713
17992
  this.logger.error("Transcription error", { error });
@@ -17765,8 +18044,12 @@ var AnganyVoice = class extends EventEmitter {
17765
18044
  this.logger = getLogger(["angany", "sdk", "core"]);
17766
18045
  this.conversations = /* @__PURE__ */ new Map();
17767
18046
  this.config = config;
17768
- this.auth = new AuthManager(config.apiUrl, config.apiUrl);
17769
- this.logger.debug("AnganyVoice initialized", { apiUrl: config.apiUrl });
18047
+ this.auth = new AuthManager(config.apiUrl, config.issuer || config.apiUrl);
18048
+ this.logger.debug("AnganyVoice initialized", {
18049
+ apiUrl: config.apiUrl,
18050
+ sseUrl: config.sseUrl || config.apiUrl,
18051
+ sipUrl: config.sipUrl || "derived from apiUrl"
18052
+ });
17770
18053
  }
17771
18054
  /**
17772
18055
  * Get the current configuration
@@ -17801,7 +18084,11 @@ var AnganyVoice = class extends EventEmitter {
17801
18084
  ...options
17802
18085
  },
17803
18086
  this.auth,
17804
- this.config.apiUrl
18087
+ {
18088
+ apiUrl: this.config.apiUrl,
18089
+ sseUrl: this.config.sseUrl,
18090
+ sipUrl: this.config.sipUrl
18091
+ }
17805
18092
  );
17806
18093
  conversation.on("ended", () => {
17807
18094
  this.conversations.delete(conversationId);