@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 +344 -57
- 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 +344 -57
- package/dist/index.js.map +1 -1
- package/package.json +9 -1
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(
|
|
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(
|
|
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.
|
|
17058
|
-
|
|
17059
|
-
|
|
17060
|
-
|
|
17061
|
-
|
|
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
|
-
|
|
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.
|
|
17156
|
-
|
|
17157
|
-
|
|
17158
|
-
|
|
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,
|
|
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.
|
|
17390
|
+
this.urls = urls;
|
|
17391
|
+
const sseUrl = urls.sseUrl || urls.apiUrl;
|
|
17258
17392
|
this.sipManager = new SipManager();
|
|
17259
|
-
this.transcriptionService = new TranscriptionService(
|
|
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
|
-
|
|
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.
|
|
17295
|
-
|
|
17296
|
-
|
|
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.
|
|
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", {
|
|
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.
|
|
17408
|
-
this.
|
|
17409
|
-
|
|
17410
|
-
|
|
17411
|
-
|
|
17412
|
-
|
|
17413
|
-
|
|
17414
|
-
|
|
17415
|
-
|
|
17416
|
-
|
|
17417
|
-
|
|
17418
|
-
|
|
17419
|
-
|
|
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
|
-
|
|
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", {
|
|
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
|
-
|
|
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);
|