@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.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");
|
|
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);
|
|
17060
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
|
|
@@ -17280,27 +17418,33 @@ var Conversation = class extends EventEmitter {
|
|
|
17280
17418
|
throw new AuthenticationError("Not authenticated");
|
|
17281
17419
|
}
|
|
17282
17420
|
this.logger.debug("\u2713 Authentication check passed");
|
|
17283
|
-
this.logger.debug("=== STEP 2: GETTING ACCESS TOKEN ===");
|
|
17421
|
+
this.logger.debug("=== STEP 2: GETTING ACCESS TOKEN (OPTIONAL) ===");
|
|
17284
17422
|
let accessToken = authStatus.tokens?.accessToken;
|
|
17285
17423
|
this.logger.debug("OAuth access token", { hasOAuthToken: !!accessToken });
|
|
17286
17424
|
if (!accessToken) {
|
|
17287
17425
|
this.logger.debug("No OAuth access token in auth status, trying to get from AuthManager");
|
|
17288
|
-
|
|
17426
|
+
try {
|
|
17427
|
+
accessToken = await this.authManager.getAccessToken();
|
|
17428
|
+
} catch {
|
|
17429
|
+
}
|
|
17289
17430
|
this.logger.debug("Access token from AuthManager", { hasToken: !!accessToken });
|
|
17290
17431
|
}
|
|
17291
17432
|
if (!accessToken) {
|
|
17292
|
-
this.logger.
|
|
17293
|
-
|
|
17294
|
-
|
|
17433
|
+
this.logger.debug("No OAuth access token, checking ephemeral credentials for API token");
|
|
17434
|
+
const cachedCreds = this.authManager.getCachedEphemeralCredentials();
|
|
17435
|
+
if (cachedCreds?.apiToken) {
|
|
17436
|
+
accessToken = cachedCreds.apiToken;
|
|
17437
|
+
this.logger.debug("Using API token from ephemeral credentials");
|
|
17438
|
+
}
|
|
17439
|
+
}
|
|
17440
|
+
if (!accessToken) {
|
|
17441
|
+
this.logger.info("No access token available - running in SIP-only mode (no transcription/API features)");
|
|
17442
|
+
} else {
|
|
17443
|
+
this.accessToken = accessToken;
|
|
17444
|
+
this.logger.debug("\u2713 Access token obtained for API calls", {
|
|
17445
|
+
tokenLength: accessToken.length
|
|
17295
17446
|
});
|
|
17296
|
-
throw new AuthenticationError(
|
|
17297
|
-
"No access token available for API calls. Ephemeral credentials are only for SIP/WebRTC connections."
|
|
17298
|
-
);
|
|
17299
17447
|
}
|
|
17300
|
-
this.accessToken = accessToken;
|
|
17301
|
-
this.logger.debug("\u2713 Access token obtained for API calls", {
|
|
17302
|
-
tokenLength: accessToken.length
|
|
17303
|
-
});
|
|
17304
17448
|
this.logger.debug("=== STEP 3: ENSURING EPHEMERAL CREDENTIALS ===");
|
|
17305
17449
|
if (!this.ephemeralCredentials) {
|
|
17306
17450
|
this.logger.debug("Getting ephemeral credentials");
|
|
@@ -17354,8 +17498,12 @@ var Conversation = class extends EventEmitter {
|
|
|
17354
17498
|
if (this.ephemeralCredentials.sip.realm) {
|
|
17355
17499
|
sipConfig.realm = this.ephemeralCredentials.sip.realm;
|
|
17356
17500
|
}
|
|
17357
|
-
if (this.
|
|
17501
|
+
if (this.urls.sipUrl) {
|
|
17502
|
+
sipConfig.websocketUrl = this.urls.sipUrl;
|
|
17503
|
+
this.logger.debug("Using configured SIP URL", { url: sipConfig.websocketUrl });
|
|
17504
|
+
} else if (this.ephemeralCredentials.sip.websocketUrl) {
|
|
17358
17505
|
sipConfig.websocketUrl = this.ephemeralCredentials.sip.websocketUrl;
|
|
17506
|
+
this.logger.debug("Using platform-provided WebSocket URL", { url: sipConfig.websocketUrl });
|
|
17359
17507
|
} else if (this.ephemeralCredentials.sip.uris && this.ephemeralCredentials.sip.uris.length > 0) {
|
|
17360
17508
|
const wssUris = this.ephemeralCredentials.sip.uris.filter(
|
|
17361
17509
|
(uri) => uri.includes("transport=wss")
|
|
@@ -17366,7 +17514,7 @@ var Conversation = class extends EventEmitter {
|
|
|
17366
17514
|
const wsUri = publicUri || wssUris[0];
|
|
17367
17515
|
if (wsUri) {
|
|
17368
17516
|
this.logger.debug("Selected SIP URI", { uri: wsUri, isPublic: !!publicUri });
|
|
17369
|
-
const apiDomain = new URL(this.apiUrl).hostname;
|
|
17517
|
+
const apiDomain = new URL(this.urls.apiUrl).hostname;
|
|
17370
17518
|
sipConfig.websocketUrl = `wss://${apiDomain}/api/webrtc/`;
|
|
17371
17519
|
this.logger.debug("Derived WebSocket URL from API domain", {
|
|
17372
17520
|
apiDomain,
|
|
@@ -17374,15 +17522,18 @@ var Conversation = class extends EventEmitter {
|
|
|
17374
17522
|
});
|
|
17375
17523
|
}
|
|
17376
17524
|
}
|
|
17377
|
-
if (sipConfig.websocketUrl) {
|
|
17525
|
+
if (sipConfig.websocketUrl && !this.urls.sipUrl) {
|
|
17378
17526
|
this.logger.debug("Original WebSocket URL", { url: sipConfig.websocketUrl });
|
|
17379
17527
|
const privateIpPattern = /wss?:\/\/(192\.168\.|10\.|172\.(1[6-9]|2[0-9]|3[01])\.)/;
|
|
17380
17528
|
if (privateIpPattern.test(sipConfig.websocketUrl)) {
|
|
17381
17529
|
this.logger.warn("Detected private IP in WebSocket URL, replacing with API domain");
|
|
17382
|
-
const apiDomain = new URL(this.apiUrl).hostname;
|
|
17530
|
+
const apiDomain = new URL(this.urls.apiUrl).hostname;
|
|
17383
17531
|
const originalUrl = sipConfig.websocketUrl;
|
|
17384
17532
|
const correctedUrl = `wss://${apiDomain}/api/webrtc/`;
|
|
17385
|
-
this.logger.debug("Extracted API domain", {
|
|
17533
|
+
this.logger.debug("Extracted API domain", {
|
|
17534
|
+
domain: apiDomain,
|
|
17535
|
+
apiUrl: this.urls.apiUrl
|
|
17536
|
+
});
|
|
17386
17537
|
sipConfig.websocketUrl = correctedUrl;
|
|
17387
17538
|
this.logger.info("Corrected WebSocket URL", {
|
|
17388
17539
|
original: originalUrl,
|
|
@@ -17402,19 +17553,23 @@ var Conversation = class extends EventEmitter {
|
|
|
17402
17553
|
this.setupSipHandlers();
|
|
17403
17554
|
this.logger.debug("Registering with SIP server");
|
|
17404
17555
|
await this.sipManager.register();
|
|
17405
|
-
this.
|
|
17406
|
-
this.
|
|
17407
|
-
|
|
17408
|
-
|
|
17409
|
-
|
|
17410
|
-
|
|
17411
|
-
|
|
17412
|
-
|
|
17413
|
-
|
|
17414
|
-
|
|
17415
|
-
|
|
17416
|
-
|
|
17417
|
-
|
|
17556
|
+
if (this.accessToken) {
|
|
17557
|
+
this.transcriptionService.setTokenRefreshCallback(async () => {
|
|
17558
|
+
this.logger.debug("TranscriptionService requesting fresh token");
|
|
17559
|
+
const freshToken = await this.authManager.getAccessToken();
|
|
17560
|
+
if (!freshToken) {
|
|
17561
|
+
throw new Error("Unable to get fresh access token");
|
|
17562
|
+
}
|
|
17563
|
+
this.accessToken = freshToken;
|
|
17564
|
+
this.logger.debug("Fresh token provided to TranscriptionService");
|
|
17565
|
+
return freshToken;
|
|
17566
|
+
});
|
|
17567
|
+
this.logger.debug("Starting transcription stream");
|
|
17568
|
+
await this.transcriptionService.start(this.accessToken);
|
|
17569
|
+
this.setupTranscriptionHandlers();
|
|
17570
|
+
} else {
|
|
17571
|
+
this.logger.info("Skipping transcription service - no access token available");
|
|
17572
|
+
}
|
|
17418
17573
|
this.logger.debug("Making call to resource", { resourceId: this.options.resource });
|
|
17419
17574
|
const callOptions = {
|
|
17420
17575
|
resourceId: this.options.resource,
|
|
@@ -17454,11 +17609,11 @@ var Conversation = class extends EventEmitter {
|
|
|
17454
17609
|
const sendVoiceOptions = {
|
|
17455
17610
|
text,
|
|
17456
17611
|
callId: this.callId,
|
|
17457
|
-
|
|
17612
|
+
interruptsConversation: options?.interruptsConversation,
|
|
17613
|
+
queueWhenSpeaking: options?.queueWhenSpeaking,
|
|
17614
|
+
muteAgent: options?.muteAgent,
|
|
17615
|
+
voiceSettings: options?.voiceSettings
|
|
17458
17616
|
};
|
|
17459
|
-
if (options?.interrupt !== void 0) {
|
|
17460
|
-
sendVoiceOptions.interrupt = options.interrupt;
|
|
17461
|
-
}
|
|
17462
17617
|
try {
|
|
17463
17618
|
await this.apiService.sendVoice(sendVoiceOptions, this.accessToken);
|
|
17464
17619
|
} catch (error) {
|
|
@@ -17545,6 +17700,118 @@ var Conversation = class extends EventEmitter {
|
|
|
17545
17700
|
isAgentMuted() {
|
|
17546
17701
|
return this.agentMuted;
|
|
17547
17702
|
}
|
|
17703
|
+
/**
|
|
17704
|
+
* Get the current call ID (available after connection)
|
|
17705
|
+
*/
|
|
17706
|
+
getCallId() {
|
|
17707
|
+
return this.callId;
|
|
17708
|
+
}
|
|
17709
|
+
/**
|
|
17710
|
+
* Mute the call via API (POST /calls/{call_id}/mute)
|
|
17711
|
+
*/
|
|
17712
|
+
async muteCall() {
|
|
17713
|
+
this.logger.debug("Muting call via API");
|
|
17714
|
+
if (!this.callId) {
|
|
17715
|
+
throw new ConversationError("No call ID available - call may not be connected yet");
|
|
17716
|
+
}
|
|
17717
|
+
if (!this.accessToken) {
|
|
17718
|
+
throw new AuthenticationError("No access token available");
|
|
17719
|
+
}
|
|
17720
|
+
try {
|
|
17721
|
+
await this.apiService.muteCall(this.callId, this.accessToken);
|
|
17722
|
+
this.logger.info("Call muted via API", { callId: this.callId });
|
|
17723
|
+
} catch (error) {
|
|
17724
|
+
if (error instanceof AuthenticationError) {
|
|
17725
|
+
this.logger.debug("API call failed with auth error, attempting token refresh");
|
|
17726
|
+
try {
|
|
17727
|
+
const freshToken = await this.authManager.getAccessToken();
|
|
17728
|
+
if (freshToken && freshToken !== this.accessToken) {
|
|
17729
|
+
this.accessToken = freshToken;
|
|
17730
|
+
this.logger.debug("Token refreshed, retrying mute call operation");
|
|
17731
|
+
await this.apiService.muteCall(this.callId, this.accessToken);
|
|
17732
|
+
return;
|
|
17733
|
+
}
|
|
17734
|
+
throw error;
|
|
17735
|
+
} catch (refreshError) {
|
|
17736
|
+
this.logger.error("Failed to refresh token for mute call operation", {
|
|
17737
|
+
error: refreshError
|
|
17738
|
+
});
|
|
17739
|
+
throw error;
|
|
17740
|
+
}
|
|
17741
|
+
}
|
|
17742
|
+
throw error;
|
|
17743
|
+
}
|
|
17744
|
+
}
|
|
17745
|
+
/**
|
|
17746
|
+
* Unmute the call via API (DELETE /calls/{call_id}/mute)
|
|
17747
|
+
*/
|
|
17748
|
+
async unmuteCall() {
|
|
17749
|
+
this.logger.debug("Unmuting call via API");
|
|
17750
|
+
if (!this.callId) {
|
|
17751
|
+
throw new ConversationError("No call ID available - call may not be connected yet");
|
|
17752
|
+
}
|
|
17753
|
+
if (!this.accessToken) {
|
|
17754
|
+
throw new AuthenticationError("No access token available");
|
|
17755
|
+
}
|
|
17756
|
+
try {
|
|
17757
|
+
await this.apiService.unmuteCall(this.callId, this.accessToken);
|
|
17758
|
+
this.logger.info("Call unmuted via API", { callId: this.callId });
|
|
17759
|
+
} catch (error) {
|
|
17760
|
+
if (error instanceof AuthenticationError) {
|
|
17761
|
+
this.logger.debug("API call failed with auth error, attempting token refresh");
|
|
17762
|
+
try {
|
|
17763
|
+
const freshToken = await this.authManager.getAccessToken();
|
|
17764
|
+
if (freshToken && freshToken !== this.accessToken) {
|
|
17765
|
+
this.accessToken = freshToken;
|
|
17766
|
+
this.logger.debug("Token refreshed, retrying unmute call operation");
|
|
17767
|
+
await this.apiService.unmuteCall(this.callId, this.accessToken);
|
|
17768
|
+
return;
|
|
17769
|
+
}
|
|
17770
|
+
throw error;
|
|
17771
|
+
} catch (refreshError) {
|
|
17772
|
+
this.logger.error("Failed to refresh token for unmute call operation", {
|
|
17773
|
+
error: refreshError
|
|
17774
|
+
});
|
|
17775
|
+
throw error;
|
|
17776
|
+
}
|
|
17777
|
+
}
|
|
17778
|
+
throw error;
|
|
17779
|
+
}
|
|
17780
|
+
}
|
|
17781
|
+
/**
|
|
17782
|
+
* Get call mute status via API (GET /calls/{call_id}/mute)
|
|
17783
|
+
*/
|
|
17784
|
+
async getCallMuteStatus() {
|
|
17785
|
+
this.logger.debug("Getting call mute status via API");
|
|
17786
|
+
if (!this.callId) {
|
|
17787
|
+
throw new ConversationError("No call ID available - call may not be connected yet");
|
|
17788
|
+
}
|
|
17789
|
+
if (!this.accessToken) {
|
|
17790
|
+
throw new AuthenticationError("No access token available");
|
|
17791
|
+
}
|
|
17792
|
+
try {
|
|
17793
|
+
return await this.apiService.getCallMuteStatus(this.callId, this.accessToken);
|
|
17794
|
+
} catch (error) {
|
|
17795
|
+
if (error instanceof AuthenticationError) {
|
|
17796
|
+
this.logger.debug("API call failed with auth error, attempting token refresh");
|
|
17797
|
+
try {
|
|
17798
|
+
const freshToken = await this.authManager.getAccessToken();
|
|
17799
|
+
if (freshToken && freshToken !== this.accessToken) {
|
|
17800
|
+
this.accessToken = freshToken;
|
|
17801
|
+
this.logger.debug("Token refreshed, retrying get call mute status operation");
|
|
17802
|
+
return await this.apiService.getCallMuteStatus(this.callId, this.accessToken);
|
|
17803
|
+
}
|
|
17804
|
+
throw error;
|
|
17805
|
+
} catch (refreshError) {
|
|
17806
|
+
this.logger.error("Failed to refresh token for get call mute status operation", {
|
|
17807
|
+
error: refreshError
|
|
17808
|
+
});
|
|
17809
|
+
throw error;
|
|
17810
|
+
}
|
|
17811
|
+
}
|
|
17812
|
+
throw error;
|
|
17813
|
+
}
|
|
17814
|
+
}
|
|
17548
17815
|
/**
|
|
17549
17816
|
* Get conversation status
|
|
17550
17817
|
*/
|
|
@@ -17700,12 +17967,24 @@ var Conversation = class extends EventEmitter {
|
|
|
17700
17967
|
speaker: event.speaker,
|
|
17701
17968
|
text: event.text,
|
|
17702
17969
|
timestamp: event.timestamp,
|
|
17703
|
-
isFinal: event.isFinal
|
|
17970
|
+
isFinal: event.isFinal,
|
|
17971
|
+
humanTurnId: event.humanTurnId,
|
|
17972
|
+
agentTurnId: event.agentTurnId,
|
|
17973
|
+
speakerId: event.speakerId
|
|
17704
17974
|
});
|
|
17705
17975
|
});
|
|
17706
17976
|
this.transcriptionService.on("callId", (callId) => {
|
|
17707
17977
|
this.logger.info("Received call ID", { callId });
|
|
17708
17978
|
this.callId = callId;
|
|
17979
|
+
this.emit("callId", callId);
|
|
17980
|
+
});
|
|
17981
|
+
this.transcriptionService.on("callStarted", (callId) => {
|
|
17982
|
+
this.logger.info("Call started", { callId });
|
|
17983
|
+
this.emit("callStarted", callId);
|
|
17984
|
+
});
|
|
17985
|
+
this.transcriptionService.on("callEnded", (callId) => {
|
|
17986
|
+
this.logger.info("Call ended", { callId });
|
|
17987
|
+
this.emit("callEnded", callId);
|
|
17709
17988
|
});
|
|
17710
17989
|
this.transcriptionService.on("error", (error) => {
|
|
17711
17990
|
this.logger.error("Transcription error", { error });
|
|
@@ -17763,8 +18042,12 @@ var AnganyVoice = class extends EventEmitter {
|
|
|
17763
18042
|
this.logger = getLogger(["angany", "sdk", "core"]);
|
|
17764
18043
|
this.conversations = /* @__PURE__ */ new Map();
|
|
17765
18044
|
this.config = config;
|
|
17766
|
-
this.auth = new AuthManager(config.apiUrl, config.apiUrl);
|
|
17767
|
-
this.logger.debug("AnganyVoice initialized", {
|
|
18045
|
+
this.auth = new AuthManager(config.apiUrl, config.issuer || config.apiUrl);
|
|
18046
|
+
this.logger.debug("AnganyVoice initialized", {
|
|
18047
|
+
apiUrl: config.apiUrl,
|
|
18048
|
+
sseUrl: config.sseUrl || config.apiUrl,
|
|
18049
|
+
sipUrl: config.sipUrl || "derived from apiUrl"
|
|
18050
|
+
});
|
|
17768
18051
|
}
|
|
17769
18052
|
/**
|
|
17770
18053
|
* Get the current configuration
|
|
@@ -17799,7 +18082,11 @@ var AnganyVoice = class extends EventEmitter {
|
|
|
17799
18082
|
...options
|
|
17800
18083
|
},
|
|
17801
18084
|
this.auth,
|
|
17802
|
-
|
|
18085
|
+
{
|
|
18086
|
+
apiUrl: this.config.apiUrl,
|
|
18087
|
+
sseUrl: this.config.sseUrl,
|
|
18088
|
+
sipUrl: this.config.sipUrl
|
|
18089
|
+
}
|
|
17803
18090
|
);
|
|
17804
18091
|
conversation.on("ended", () => {
|
|
17805
18092
|
this.conversations.delete(conversationId);
|