@anganyai/voice-sdk 0.0.2 → 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 +309 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +75 -6
- package/dist/index.d.ts +75 -6
- package/dist/index.js +309 -32
- package/dist/index.js.map +1 -1
- package/package.json +9 -1
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");
|
|
@@ -16599,7 +16609,9 @@ var SipManager = class extends EventEmitter {
|
|
|
16599
16609
|
this.remoteStream = event.streams[0];
|
|
16600
16610
|
this.logger.info("Remote audio stream received via ontrack fallback");
|
|
16601
16611
|
if (isReactNative()) {
|
|
16602
|
-
this.logger.info(
|
|
16612
|
+
this.logger.info(
|
|
16613
|
+
"React Native: Remote audio from ontrack will be played automatically"
|
|
16614
|
+
);
|
|
16603
16615
|
} else {
|
|
16604
16616
|
this.createAudioElement();
|
|
16605
16617
|
if (this.audioElement && this.remoteStream) {
|
|
@@ -16644,7 +16656,9 @@ var SipManager = class extends EventEmitter {
|
|
|
16644
16656
|
*/
|
|
16645
16657
|
createAudioElement() {
|
|
16646
16658
|
if (isReactNative()) {
|
|
16647
|
-
this.logger.debug(
|
|
16659
|
+
this.logger.debug(
|
|
16660
|
+
"React Native detected - skipping audio element creation (handled by WebRTC)"
|
|
16661
|
+
);
|
|
16648
16662
|
return;
|
|
16649
16663
|
}
|
|
16650
16664
|
if (!isBrowser()) {
|
|
@@ -16921,7 +16935,7 @@ var TranscriptionService = class extends EventEmitter {
|
|
|
16921
16935
|
* Connect to SSE endpoint with token refresh support
|
|
16922
16936
|
*/
|
|
16923
16937
|
async connectToSSE(accessToken, isRetry = false) {
|
|
16924
|
-
const sseUrl = `${this.apiUrl}/api/v1/events?event_types=transcription`;
|
|
16938
|
+
const sseUrl = `${this.apiUrl}/api/v1/events?event_types=transcription,call_event`;
|
|
16925
16939
|
this.logger.debug("Connecting to SSE endpoint", { url: sseUrl, isRetry });
|
|
16926
16940
|
const response = await fetch(sseUrl, {
|
|
16927
16941
|
method: "GET",
|
|
@@ -17051,13 +17065,36 @@ var TranscriptionService = class extends EventEmitter {
|
|
|
17051
17065
|
}
|
|
17052
17066
|
}
|
|
17053
17067
|
handleMessage(data) {
|
|
17054
|
-
this.logger.debug("Handling SSE message", { data });
|
|
17055
|
-
if (data.
|
|
17056
|
-
|
|
17057
|
-
|
|
17058
|
-
|
|
17059
|
-
|
|
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");
|
|
17060
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);
|
|
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) {
|
|
17061
17098
|
const event = {
|
|
17062
17099
|
speaker: data.direction === "agent" ? "agent" : "user",
|
|
17063
17100
|
text: data.text.trim(),
|
|
@@ -17065,6 +17102,9 @@ var TranscriptionService = class extends EventEmitter {
|
|
|
17065
17102
|
isFinal: true,
|
|
17066
17103
|
// Assume final for now
|
|
17067
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,
|
|
17068
17108
|
metadata: data
|
|
17069
17109
|
};
|
|
17070
17110
|
this.logger.debug("Emitting transcription event", { event });
|
|
@@ -17136,7 +17176,8 @@ var ApiService = class {
|
|
|
17136
17176
|
this.logger.debug("Sending voice text", {
|
|
17137
17177
|
callId: options.callId,
|
|
17138
17178
|
textLength: options.text.length,
|
|
17139
|
-
|
|
17179
|
+
interruptsConversation: options.interruptsConversation,
|
|
17180
|
+
queueWhenSpeaking: options.queueWhenSpeaking,
|
|
17140
17181
|
muteAgent: options.muteAgent
|
|
17141
17182
|
});
|
|
17142
17183
|
try {
|
|
@@ -17150,10 +17191,10 @@ var ApiService = class {
|
|
|
17150
17191
|
},
|
|
17151
17192
|
body: JSON.stringify({
|
|
17152
17193
|
text: options.text,
|
|
17153
|
-
interrupts_conversation: options.
|
|
17154
|
-
|
|
17155
|
-
|
|
17156
|
-
|
|
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
|
|
17157
17198
|
})
|
|
17158
17199
|
}
|
|
17159
17200
|
);
|
|
@@ -17239,11 +17280,103 @@ var ApiService = class {
|
|
|
17239
17280
|
throw new NetworkError("Failed to set agent mute status", { cause: error });
|
|
17240
17281
|
}
|
|
17241
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
|
+
}
|
|
17242
17375
|
};
|
|
17243
17376
|
|
|
17244
17377
|
// src/conversation/Conversation.ts
|
|
17245
17378
|
var Conversation = class extends EventEmitter {
|
|
17246
|
-
constructor(id, options, authManager,
|
|
17379
|
+
constructor(id, options, authManager, urls) {
|
|
17247
17380
|
super();
|
|
17248
17381
|
this.logger = getLogger(["angany", "sdk", "conversation"]);
|
|
17249
17382
|
this.state = "idle";
|
|
@@ -17252,12 +17385,17 @@ var Conversation = class extends EventEmitter {
|
|
|
17252
17385
|
this.id = id;
|
|
17253
17386
|
this.options = options;
|
|
17254
17387
|
this.authManager = authManager;
|
|
17255
|
-
this.
|
|
17388
|
+
this.urls = urls;
|
|
17389
|
+
const sseUrl = urls.sseUrl || urls.apiUrl;
|
|
17256
17390
|
this.sipManager = new SipManager();
|
|
17257
|
-
this.transcriptionService = new TranscriptionService(
|
|
17258
|
-
this.apiService = new ApiService(apiUrl);
|
|
17391
|
+
this.transcriptionService = new TranscriptionService(sseUrl);
|
|
17392
|
+
this.apiService = new ApiService(urls.apiUrl);
|
|
17259
17393
|
this.logger = this.logger.with({ conversationId: id, resource: options.resource });
|
|
17260
|
-
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
|
+
});
|
|
17261
17399
|
}
|
|
17262
17400
|
/**
|
|
17263
17401
|
* Initialize and start the conversation
|
|
@@ -17354,8 +17492,12 @@ var Conversation = class extends EventEmitter {
|
|
|
17354
17492
|
if (this.ephemeralCredentials.sip.realm) {
|
|
17355
17493
|
sipConfig.realm = this.ephemeralCredentials.sip.realm;
|
|
17356
17494
|
}
|
|
17357
|
-
if (this.
|
|
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) {
|
|
17358
17499
|
sipConfig.websocketUrl = this.ephemeralCredentials.sip.websocketUrl;
|
|
17500
|
+
this.logger.debug("Using platform-provided WebSocket URL", { url: sipConfig.websocketUrl });
|
|
17359
17501
|
} else if (this.ephemeralCredentials.sip.uris && this.ephemeralCredentials.sip.uris.length > 0) {
|
|
17360
17502
|
const wssUris = this.ephemeralCredentials.sip.uris.filter(
|
|
17361
17503
|
(uri) => uri.includes("transport=wss")
|
|
@@ -17366,7 +17508,7 @@ var Conversation = class extends EventEmitter {
|
|
|
17366
17508
|
const wsUri = publicUri || wssUris[0];
|
|
17367
17509
|
if (wsUri) {
|
|
17368
17510
|
this.logger.debug("Selected SIP URI", { uri: wsUri, isPublic: !!publicUri });
|
|
17369
|
-
const apiDomain = new URL(this.apiUrl).hostname;
|
|
17511
|
+
const apiDomain = new URL(this.urls.apiUrl).hostname;
|
|
17370
17512
|
sipConfig.websocketUrl = `wss://${apiDomain}/api/webrtc/`;
|
|
17371
17513
|
this.logger.debug("Derived WebSocket URL from API domain", {
|
|
17372
17514
|
apiDomain,
|
|
@@ -17374,15 +17516,18 @@ var Conversation = class extends EventEmitter {
|
|
|
17374
17516
|
});
|
|
17375
17517
|
}
|
|
17376
17518
|
}
|
|
17377
|
-
if (sipConfig.websocketUrl) {
|
|
17519
|
+
if (sipConfig.websocketUrl && !this.urls.sipUrl) {
|
|
17378
17520
|
this.logger.debug("Original WebSocket URL", { url: sipConfig.websocketUrl });
|
|
17379
17521
|
const privateIpPattern = /wss?:\/\/(192\.168\.|10\.|172\.(1[6-9]|2[0-9]|3[01])\.)/;
|
|
17380
17522
|
if (privateIpPattern.test(sipConfig.websocketUrl)) {
|
|
17381
17523
|
this.logger.warn("Detected private IP in WebSocket URL, replacing with API domain");
|
|
17382
|
-
const apiDomain = new URL(this.apiUrl).hostname;
|
|
17524
|
+
const apiDomain = new URL(this.urls.apiUrl).hostname;
|
|
17383
17525
|
const originalUrl = sipConfig.websocketUrl;
|
|
17384
17526
|
const correctedUrl = `wss://${apiDomain}/api/webrtc/`;
|
|
17385
|
-
this.logger.debug("Extracted API domain", {
|
|
17527
|
+
this.logger.debug("Extracted API domain", {
|
|
17528
|
+
domain: apiDomain,
|
|
17529
|
+
apiUrl: this.urls.apiUrl
|
|
17530
|
+
});
|
|
17386
17531
|
sipConfig.websocketUrl = correctedUrl;
|
|
17387
17532
|
this.logger.info("Corrected WebSocket URL", {
|
|
17388
17533
|
original: originalUrl,
|
|
@@ -17454,11 +17599,11 @@ var Conversation = class extends EventEmitter {
|
|
|
17454
17599
|
const sendVoiceOptions = {
|
|
17455
17600
|
text,
|
|
17456
17601
|
callId: this.callId,
|
|
17457
|
-
|
|
17602
|
+
interruptsConversation: options?.interruptsConversation,
|
|
17603
|
+
queueWhenSpeaking: options?.queueWhenSpeaking,
|
|
17604
|
+
muteAgent: options?.muteAgent,
|
|
17605
|
+
voiceSettings: options?.voiceSettings
|
|
17458
17606
|
};
|
|
17459
|
-
if (options?.interrupt !== void 0) {
|
|
17460
|
-
sendVoiceOptions.interrupt = options.interrupt;
|
|
17461
|
-
}
|
|
17462
17607
|
try {
|
|
17463
17608
|
await this.apiService.sendVoice(sendVoiceOptions, this.accessToken);
|
|
17464
17609
|
} catch (error) {
|
|
@@ -17545,6 +17690,118 @@ var Conversation = class extends EventEmitter {
|
|
|
17545
17690
|
isAgentMuted() {
|
|
17546
17691
|
return this.agentMuted;
|
|
17547
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
|
+
}
|
|
17548
17805
|
/**
|
|
17549
17806
|
* Get conversation status
|
|
17550
17807
|
*/
|
|
@@ -17700,12 +17957,24 @@ var Conversation = class extends EventEmitter {
|
|
|
17700
17957
|
speaker: event.speaker,
|
|
17701
17958
|
text: event.text,
|
|
17702
17959
|
timestamp: event.timestamp,
|
|
17703
|
-
isFinal: event.isFinal
|
|
17960
|
+
isFinal: event.isFinal,
|
|
17961
|
+
humanTurnId: event.humanTurnId,
|
|
17962
|
+
agentTurnId: event.agentTurnId,
|
|
17963
|
+
speakerId: event.speakerId
|
|
17704
17964
|
});
|
|
17705
17965
|
});
|
|
17706
17966
|
this.transcriptionService.on("callId", (callId) => {
|
|
17707
17967
|
this.logger.info("Received call ID", { callId });
|
|
17708
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);
|
|
17709
17978
|
});
|
|
17710
17979
|
this.transcriptionService.on("error", (error) => {
|
|
17711
17980
|
this.logger.error("Transcription error", { error });
|
|
@@ -17763,8 +18032,12 @@ var AnganyVoice = class extends EventEmitter {
|
|
|
17763
18032
|
this.logger = getLogger(["angany", "sdk", "core"]);
|
|
17764
18033
|
this.conversations = /* @__PURE__ */ new Map();
|
|
17765
18034
|
this.config = config;
|
|
17766
|
-
this.auth = new AuthManager(config.apiUrl, config.apiUrl);
|
|
17767
|
-
this.logger.debug("AnganyVoice initialized", {
|
|
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
|
+
});
|
|
17768
18041
|
}
|
|
17769
18042
|
/**
|
|
17770
18043
|
* Get the current configuration
|
|
@@ -17799,7 +18072,11 @@ var AnganyVoice = class extends EventEmitter {
|
|
|
17799
18072
|
...options
|
|
17800
18073
|
},
|
|
17801
18074
|
this.auth,
|
|
17802
|
-
|
|
18075
|
+
{
|
|
18076
|
+
apiUrl: this.config.apiUrl,
|
|
18077
|
+
sseUrl: this.config.sseUrl,
|
|
18078
|
+
sipUrl: this.config.sipUrl
|
|
18079
|
+
}
|
|
17803
18080
|
);
|
|
17804
18081
|
conversation.on("ended", () => {
|
|
17805
18082
|
this.conversations.delete(conversationId);
|