@sinch/functions-runtime 0.3.7 → 0.3.9
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/bin/sinch-runtime.js +11 -2
- package/dist/bin/sinch-runtime.js.map +1 -1
- package/dist/index.d.ts +1013 -63
- package/dist/index.js +946 -115
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -168,6 +168,21 @@ var IceSvamlBuilder = class extends BaseSvamlBuilder {
|
|
|
168
168
|
*
|
|
169
169
|
* @param number - E.164 formatted phone number (e.g., '+15551234567')
|
|
170
170
|
* @param options - Connection options
|
|
171
|
+
*
|
|
172
|
+
* @example
|
|
173
|
+
* ```typescript
|
|
174
|
+
* // Simple connect
|
|
175
|
+
* new IceSvamlBuilder().connectPstn('+15551234567').build();
|
|
176
|
+
*
|
|
177
|
+
* // With options: caller ID, timeout, and AMD
|
|
178
|
+
* new IceSvamlBuilder()
|
|
179
|
+
* .connectPstn('+15551234567', {
|
|
180
|
+
* cli: '+15559876543',
|
|
181
|
+
* timeout: 30,
|
|
182
|
+
* amd: { enabled: true },
|
|
183
|
+
* })
|
|
184
|
+
* .build();
|
|
185
|
+
* ```
|
|
171
186
|
*/
|
|
172
187
|
connectPstn(number, options) {
|
|
173
188
|
this.action = {
|
|
@@ -224,6 +239,22 @@ var IceSvamlBuilder = class extends BaseSvamlBuilder {
|
|
|
224
239
|
*
|
|
225
240
|
* @param menus - Array of menu definitions or MenuStructure from createMenu().build()
|
|
226
241
|
* @param options - Menu options
|
|
242
|
+
*
|
|
243
|
+
* @example
|
|
244
|
+
* ```typescript
|
|
245
|
+
* // Using MenuTemplates
|
|
246
|
+
* new IceSvamlBuilder()
|
|
247
|
+
* .runMenu(MenuTemplates.business('Acme Corp'))
|
|
248
|
+
* .build();
|
|
249
|
+
*
|
|
250
|
+
* // Using createMenu builder
|
|
251
|
+
* const menu = createMenu()
|
|
252
|
+
* .prompt('Press 1 for sales, 2 for support.')
|
|
253
|
+
* .option('1', 'return(sales)')
|
|
254
|
+
* .option('2', 'return(support)')
|
|
255
|
+
* .build();
|
|
256
|
+
* new IceSvamlBuilder().runMenu(menu).build();
|
|
257
|
+
* ```
|
|
227
258
|
*/
|
|
228
259
|
runMenu(menus, options) {
|
|
229
260
|
const menuArray = Array.isArray(menus) ? menus : menus.menus;
|
|
@@ -241,6 +272,14 @@ var IceSvamlBuilder = class extends BaseSvamlBuilder {
|
|
|
241
272
|
*
|
|
242
273
|
* @param holdPrompt - Prompt to play while on hold
|
|
243
274
|
* @param options - Park options
|
|
275
|
+
*
|
|
276
|
+
* @example
|
|
277
|
+
* ```typescript
|
|
278
|
+
* // Park with hold music prompt
|
|
279
|
+
* new IceSvamlBuilder()
|
|
280
|
+
* .park('Please hold while we connect you.', { maxDuration: 120 })
|
|
281
|
+
* .build();
|
|
282
|
+
* ```
|
|
244
283
|
*/
|
|
245
284
|
park(holdPrompt, options) {
|
|
246
285
|
this.action = {
|
|
@@ -869,7 +908,10 @@ function createLenientJsonParser(options = {}) {
|
|
|
869
908
|
}
|
|
870
909
|
try {
|
|
871
910
|
let parsed = parseJson(raw, opts.allowJson5);
|
|
872
|
-
|
|
911
|
+
const isWebhookPath = req.path.startsWith("/webhook");
|
|
912
|
+
if (!isWebhookPath) {
|
|
913
|
+
parsed = transformKeys(parsed, opts.camelCase);
|
|
914
|
+
}
|
|
873
915
|
req._keysTransformed = true;
|
|
874
916
|
req.body = parsed;
|
|
875
917
|
next();
|
|
@@ -988,6 +1030,12 @@ function extractFunctionName(path5, body) {
|
|
|
988
1030
|
if (segments.length === 1 && isVoiceCallback(segments[0])) {
|
|
989
1031
|
return segments[0];
|
|
990
1032
|
}
|
|
1033
|
+
if (segments.length >= 2 && segments[0] === "webhook") {
|
|
1034
|
+
return `${segments[1]}Webhook`;
|
|
1035
|
+
}
|
|
1036
|
+
if (segments.length === 1 && segments[0] === "webhook") {
|
|
1037
|
+
return "webhook";
|
|
1038
|
+
}
|
|
991
1039
|
return segments[segments.length - 1] || "default";
|
|
992
1040
|
}
|
|
993
1041
|
function generateRequestId() {
|
|
@@ -1403,145 +1451,895 @@ var ElevenLabsStateManager = class {
|
|
|
1403
1451
|
};
|
|
1404
1452
|
var ElevenLabsState = new ElevenLabsStateManager();
|
|
1405
1453
|
|
|
1406
|
-
// ../runtime-shared/dist/
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
var
|
|
1454
|
+
// ../runtime-shared/dist/ai/elevenlabs/client.js
|
|
1455
|
+
var ELEVENLABS_API_BASE = "https://api.elevenlabs.io/v1";
|
|
1456
|
+
var FETCH_TIMEOUT_MS = 1e4;
|
|
1457
|
+
var ElevenLabsClient = class {
|
|
1458
|
+
apiKey;
|
|
1459
|
+
constructor(apiKey) {
|
|
1460
|
+
this.apiKey = apiKey || process.env.ELEVENLABS_API_KEY || "";
|
|
1461
|
+
if (!this.apiKey) {
|
|
1462
|
+
console.warn("[ElevenLabs] No API key provided. Set ELEVENLABS_API_KEY environment variable.");
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
get authHeaders() {
|
|
1466
|
+
return { "xi-api-key": this.apiKey, "Content-Type": "application/json" };
|
|
1467
|
+
}
|
|
1410
1468
|
/**
|
|
1411
|
-
*
|
|
1412
|
-
* @param templatePath - Path to the HTML template file
|
|
1413
|
-
* @param variables - Object containing variables to replace in template
|
|
1414
|
-
* @returns Rendered HTML string
|
|
1469
|
+
* Initiate an outbound call to a phone number using an ElevenLabs agent
|
|
1415
1470
|
*/
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
console.error("Error rendering template:", error);
|
|
1428
|
-
throw new Error(`Failed to render template: ${templatePath}`);
|
|
1471
|
+
async makeCall(options) {
|
|
1472
|
+
const phoneNumberId = ElevenLabsState.getPhoneNumberId();
|
|
1473
|
+
const request = {
|
|
1474
|
+
agent_id: options.agentId,
|
|
1475
|
+
customer_phone_number: options.toNumber,
|
|
1476
|
+
agent_phone_number_id: phoneNumberId
|
|
1477
|
+
};
|
|
1478
|
+
if (options.dynamicVariables && Object.keys(options.dynamicVariables).length > 0) {
|
|
1479
|
+
request.custom_llm_extra_body = {
|
|
1480
|
+
dynamic_variables: options.dynamicVariables
|
|
1481
|
+
};
|
|
1429
1482
|
}
|
|
1483
|
+
const response = await fetch(`${ELEVENLABS_API_BASE}/convai/conversation/create_call`, {
|
|
1484
|
+
method: "POST",
|
|
1485
|
+
headers: this.authHeaders,
|
|
1486
|
+
body: JSON.stringify(request),
|
|
1487
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
1488
|
+
});
|
|
1489
|
+
if (!response.ok) {
|
|
1490
|
+
const error = await response.text();
|
|
1491
|
+
throw new Error(`ElevenLabs API error: ${response.status} - ${error}`);
|
|
1492
|
+
}
|
|
1493
|
+
return response.json();
|
|
1430
1494
|
}
|
|
1431
1495
|
/**
|
|
1432
|
-
* Get
|
|
1433
|
-
* @param templateName - Name of the template file (e.g., 'index.html')
|
|
1434
|
-
* @param baseDir - Base directory to search from (defaults to cwd)
|
|
1435
|
-
* @returns Absolute path to the template file
|
|
1496
|
+
* Get conversation details including transcript and analysis
|
|
1436
1497
|
*/
|
|
1437
|
-
|
|
1438
|
-
const
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1498
|
+
async getConversationDetails(conversationId) {
|
|
1499
|
+
const response = await fetch(`${ELEVENLABS_API_BASE}/convai/conversations/${encodeURIComponent(conversationId)}`, {
|
|
1500
|
+
headers: { "xi-api-key": this.apiKey },
|
|
1501
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
1502
|
+
});
|
|
1503
|
+
if (!response.ok) {
|
|
1504
|
+
const error = await response.text();
|
|
1505
|
+
throw new Error(`ElevenLabs API error: ${response.status} - ${error}`);
|
|
1442
1506
|
}
|
|
1443
|
-
const
|
|
1444
|
-
|
|
1445
|
-
|
|
1507
|
+
const data = await response.json();
|
|
1508
|
+
return this.mapConversationResponse(data);
|
|
1509
|
+
}
|
|
1510
|
+
/**
|
|
1511
|
+
* Auto-configure ElevenLabs with Sinch SIP trunk.
|
|
1512
|
+
*
|
|
1513
|
+
* Step 1: Get or import phone number with SIP trunk config
|
|
1514
|
+
* Step 2: Configure agent conversation-init webhook
|
|
1515
|
+
*
|
|
1516
|
+
* Called by tryAutoConfigureAsync (the orchestrator that reads env vars).
|
|
1517
|
+
*/
|
|
1518
|
+
async autoConfigureAsync(options) {
|
|
1519
|
+
console.log(`[ElevenLabs] Starting auto-configuration for agent ${options.agentId}`);
|
|
1520
|
+
const phoneResult = await this.getOrImportPhoneNumber(options);
|
|
1521
|
+
if (options.webhookBaseUrl) {
|
|
1522
|
+
await this.configureAgentWebhooks(options.agentId, options.webhookBaseUrl);
|
|
1523
|
+
}
|
|
1524
|
+
console.log(`[ElevenLabs] Auto-configuration completed: PhoneNumberId=${phoneResult.phoneNumberId}`);
|
|
1525
|
+
return {
|
|
1526
|
+
success: true,
|
|
1527
|
+
phoneNumberId: phoneResult.phoneNumberId,
|
|
1528
|
+
phoneNumber: options.phoneNumber,
|
|
1529
|
+
sipAddress: options.sipAddress
|
|
1530
|
+
};
|
|
1531
|
+
}
|
|
1532
|
+
/**
|
|
1533
|
+
* Check if phone number already exists in ElevenLabs; create or update it.
|
|
1534
|
+
* Matches C# GetOrImportPhoneNumberAsync.
|
|
1535
|
+
*/
|
|
1536
|
+
async getOrImportPhoneNumber(config) {
|
|
1537
|
+
const listResponse = await fetch(`${ELEVENLABS_API_BASE}/convai/phone-numbers`, {
|
|
1538
|
+
headers: this.authHeaders,
|
|
1539
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
1540
|
+
});
|
|
1541
|
+
if (!listResponse.ok) {
|
|
1542
|
+
throw new Error(`Failed to list phone numbers: ${listResponse.status}`);
|
|
1543
|
+
}
|
|
1544
|
+
const phoneNumbers = await listResponse.json();
|
|
1545
|
+
const sipConfig = {
|
|
1546
|
+
address: config.sipAddress,
|
|
1547
|
+
transport: "tcp",
|
|
1548
|
+
media_encryption: "disabled",
|
|
1549
|
+
credentials: { username: config.sipUsername, password: config.sipPassword }
|
|
1550
|
+
};
|
|
1551
|
+
const existing = phoneNumbers.find((p) => p.phone_number === config.phoneNumber);
|
|
1552
|
+
if (existing) {
|
|
1553
|
+
console.log(`[ElevenLabs] Phone number already exists (${existing.phone_number_id}), updating config`);
|
|
1554
|
+
await this.updatePhoneNumberConfig(existing.phone_number_id, config.agentId, sipConfig);
|
|
1555
|
+
return { phoneNumberId: existing.phone_number_id };
|
|
1556
|
+
}
|
|
1557
|
+
const label = `Sinch Functions - ${config.functionName || "Sinch Function"}`;
|
|
1558
|
+
console.log("[ElevenLabs] Creating SIP trunk phone number");
|
|
1559
|
+
const createResponse2 = await fetch(`${ELEVENLABS_API_BASE}/convai/phone-numbers`, {
|
|
1560
|
+
method: "POST",
|
|
1561
|
+
headers: this.authHeaders,
|
|
1562
|
+
body: JSON.stringify({
|
|
1563
|
+
phone_number: config.phoneNumber,
|
|
1564
|
+
label,
|
|
1565
|
+
provider: "sip_trunk",
|
|
1566
|
+
supports_inbound: true,
|
|
1567
|
+
supports_outbound: true,
|
|
1568
|
+
inbound_trunk_config: { media_encryption: "disabled" },
|
|
1569
|
+
outbound_trunk_config: sipConfig
|
|
1570
|
+
}),
|
|
1571
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
1572
|
+
});
|
|
1573
|
+
if (!createResponse2.ok) {
|
|
1574
|
+
const err = await createResponse2.text();
|
|
1575
|
+
throw new Error(`Failed to create phone number: ${createResponse2.status} - ${err}`);
|
|
1576
|
+
}
|
|
1577
|
+
const created = await createResponse2.json();
|
|
1578
|
+
console.log("[ElevenLabs] Phone number created successfully");
|
|
1579
|
+
await this.updatePhoneNumberConfig(created.phone_number_id, config.agentId, sipConfig);
|
|
1580
|
+
return { phoneNumberId: created.phone_number_id };
|
|
1581
|
+
}
|
|
1582
|
+
/**
|
|
1583
|
+
* Update phone number with agent association and SIP trunk config.
|
|
1584
|
+
* Matches C# UpdatePhoneNumberConfigAsync.
|
|
1585
|
+
*/
|
|
1586
|
+
async updatePhoneNumberConfig(phoneNumberId, agentId, sipConfig) {
|
|
1587
|
+
const response = await fetch(`${ELEVENLABS_API_BASE}/convai/phone-numbers/${encodeURIComponent(phoneNumberId)}`, {
|
|
1588
|
+
method: "PATCH",
|
|
1589
|
+
headers: this.authHeaders,
|
|
1590
|
+
body: JSON.stringify({
|
|
1591
|
+
agent_id: agentId,
|
|
1592
|
+
outbound_trunk_config: sipConfig
|
|
1593
|
+
}),
|
|
1594
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
1595
|
+
});
|
|
1596
|
+
if (!response.ok) {
|
|
1597
|
+
await response.text();
|
|
1598
|
+
console.warn(`[ElevenLabs] Failed to update phone number config: ${response.status}`);
|
|
1599
|
+
} else {
|
|
1600
|
+
console.log("[ElevenLabs] Phone number configuration updated");
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
/**
|
|
1604
|
+
* Configure agent webhook for conversation-init events.
|
|
1605
|
+
* Matches C# ConfigureAgentWebhooksAsync.
|
|
1606
|
+
*/
|
|
1607
|
+
async configureAgentWebhooks(agentId, webhookBaseUrl) {
|
|
1608
|
+
const webhookUrl = `${webhookBaseUrl}/webhook/elevenlabs/conversation-init`;
|
|
1609
|
+
console.log(`[ElevenLabs] Configuring agent webhook`);
|
|
1610
|
+
const response = await fetch(`${ELEVENLABS_API_BASE}/convai/agents/${encodeURIComponent(agentId)}`, {
|
|
1611
|
+
method: "PATCH",
|
|
1612
|
+
headers: this.authHeaders,
|
|
1613
|
+
body: JSON.stringify({
|
|
1614
|
+
platform_settings: {
|
|
1615
|
+
workspace_overrides: {
|
|
1616
|
+
conversation_initiation_client_data_webhook: {
|
|
1617
|
+
url: webhookUrl,
|
|
1618
|
+
request_headers: {}
|
|
1619
|
+
}
|
|
1620
|
+
},
|
|
1621
|
+
overrides: {
|
|
1622
|
+
enable_conversation_initiation_client_data_from_webhook: true
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
}),
|
|
1626
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
1627
|
+
});
|
|
1628
|
+
if (!response.ok) {
|
|
1629
|
+
await response.text();
|
|
1630
|
+
console.error(`[ElevenLabs] Failed to configure agent webhooks: ${response.status}`);
|
|
1631
|
+
} else {
|
|
1632
|
+
console.log("[ElevenLabs] Agent webhooks configured successfully");
|
|
1446
1633
|
}
|
|
1447
|
-
|
|
1634
|
+
}
|
|
1635
|
+
/**
|
|
1636
|
+
* Map API response to our interface
|
|
1637
|
+
*/
|
|
1638
|
+
mapConversationResponse(data) {
|
|
1639
|
+
return {
|
|
1640
|
+
conversationId: data.conversation_id,
|
|
1641
|
+
agentId: data.agent_id,
|
|
1642
|
+
status: data.status,
|
|
1643
|
+
metadata: data.metadata ? {
|
|
1644
|
+
callDurationSeconds: data.metadata.call_duration_secs,
|
|
1645
|
+
startTime: data.metadata.start_time_unix_secs ? new Date(data.metadata.start_time_unix_secs * 1e3) : void 0,
|
|
1646
|
+
endTime: data.metadata.end_time_unix_secs ? new Date(data.metadata.end_time_unix_secs * 1e3) : void 0
|
|
1647
|
+
} : void 0,
|
|
1648
|
+
transcript: data.transcript?.map((t) => ({
|
|
1649
|
+
role: t.role,
|
|
1650
|
+
message: t.message,
|
|
1651
|
+
timeInCallSeconds: t.time_in_call_secs
|
|
1652
|
+
})),
|
|
1653
|
+
analysis: data.analysis ? {
|
|
1654
|
+
summary: data.analysis.transcript_summary,
|
|
1655
|
+
customData: data.analysis.custom_data
|
|
1656
|
+
} : void 0
|
|
1657
|
+
};
|
|
1448
1658
|
}
|
|
1449
1659
|
};
|
|
1660
|
+
function createElevenLabsClient(apiKey) {
|
|
1661
|
+
return new ElevenLabsClient(apiKey);
|
|
1662
|
+
}
|
|
1450
1663
|
|
|
1451
|
-
// ../runtime-shared/dist/
|
|
1452
|
-
|
|
1453
|
-
import path2 from "path";
|
|
1454
|
-
var VersionExtractor = class {
|
|
1664
|
+
// ../runtime-shared/dist/ai/elevenlabs/controller.js
|
|
1665
|
+
var ElevenLabsController = class {
|
|
1455
1666
|
/**
|
|
1456
|
-
*
|
|
1457
|
-
*
|
|
1458
|
-
*
|
|
1459
|
-
* @
|
|
1667
|
+
* Handle conversation initialization webhook
|
|
1668
|
+
* Called when an inbound call is received - allows customizing the conversation
|
|
1669
|
+
*
|
|
1670
|
+
* @param request - The conversation init request with caller info
|
|
1671
|
+
* @returns Response with dynamic variables and/or config overrides
|
|
1460
1672
|
*/
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1673
|
+
async handleConversationInit(request) {
|
|
1674
|
+
return {};
|
|
1675
|
+
}
|
|
1676
|
+
/**
|
|
1677
|
+
* Handle post-call webhook with transcript and analysis
|
|
1678
|
+
*
|
|
1679
|
+
* @param webhook - The post-call webhook payload
|
|
1680
|
+
*/
|
|
1681
|
+
async handlePostCall(webhook) {
|
|
1682
|
+
console.log("[ElevenLabs] Post-call webhook received:", {
|
|
1683
|
+
conversationId: webhook.data.conversation_id,
|
|
1684
|
+
agentId: webhook.data.agent_id,
|
|
1685
|
+
status: webhook.data.status,
|
|
1686
|
+
duration: webhook.data.metadata.call_duration_secs
|
|
1687
|
+
});
|
|
1688
|
+
}
|
|
1689
|
+
/**
|
|
1690
|
+
* Handle post-call audio webhook with recording
|
|
1691
|
+
*
|
|
1692
|
+
* @param webhook - The audio webhook with base64-encoded MP3
|
|
1693
|
+
*/
|
|
1694
|
+
async handlePostCallAudio(webhook) {
|
|
1695
|
+
console.log("[ElevenLabs] Post-call audio received:", {
|
|
1696
|
+
conversationId: webhook.data.conversation_id,
|
|
1697
|
+
agentId: webhook.data.agent_id
|
|
1698
|
+
});
|
|
1699
|
+
}
|
|
1700
|
+
/**
|
|
1701
|
+
* Handle call initiation failure webhook
|
|
1702
|
+
*
|
|
1703
|
+
* @param webhook - The failure webhook with error details
|
|
1704
|
+
*/
|
|
1705
|
+
async handleCallInitiationFailure(webhook) {
|
|
1706
|
+
console.error("[ElevenLabs] Call initiation failed:", {
|
|
1707
|
+
agentId: webhook.data.agent_id,
|
|
1708
|
+
customerNumber: webhook.data.customer_phone_number,
|
|
1709
|
+
reason: webhook.data.failure_reason
|
|
1710
|
+
});
|
|
1711
|
+
}
|
|
1712
|
+
/**
|
|
1713
|
+
* Express route handler for conversation init endpoint
|
|
1714
|
+
* Mount at: POST /elevenlabs/conversation-init
|
|
1715
|
+
*/
|
|
1716
|
+
getConversationInitHandler() {
|
|
1717
|
+
return async (req, res) => {
|
|
1718
|
+
try {
|
|
1719
|
+
const request = req.body;
|
|
1720
|
+
const response = await this.handleConversationInit(request);
|
|
1721
|
+
res.json(response);
|
|
1722
|
+
} catch (error) {
|
|
1723
|
+
console.error("[ElevenLabs] Error in conversation init:", error);
|
|
1724
|
+
res.status(500).json({ error: "Internal server error" });
|
|
1467
1725
|
}
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1726
|
+
};
|
|
1727
|
+
}
|
|
1728
|
+
/**
|
|
1729
|
+
* Express route handler for webhook endpoint
|
|
1730
|
+
* Mount at: POST /elevenlabs/webhook
|
|
1731
|
+
* Handles all webhook types based on the 'type' field
|
|
1732
|
+
*/
|
|
1733
|
+
getWebhookHandler() {
|
|
1734
|
+
return async (req, res) => {
|
|
1735
|
+
try {
|
|
1736
|
+
const webhook = req.body;
|
|
1737
|
+
switch (webhook.type) {
|
|
1738
|
+
case "post_call_transcription":
|
|
1739
|
+
await this.handlePostCall(webhook);
|
|
1740
|
+
break;
|
|
1741
|
+
case "post_call_audio":
|
|
1742
|
+
await this.handlePostCallAudio(webhook);
|
|
1743
|
+
break;
|
|
1744
|
+
case "call_initiation_failure":
|
|
1745
|
+
await this.handleCallInitiationFailure(webhook);
|
|
1746
|
+
break;
|
|
1747
|
+
default:
|
|
1748
|
+
console.warn("[ElevenLabs] Unknown webhook type:", webhook.type);
|
|
1475
1749
|
}
|
|
1750
|
+
res.status(200).send("OK");
|
|
1751
|
+
} catch (error) {
|
|
1752
|
+
console.error("[ElevenLabs] Error processing webhook:", error);
|
|
1753
|
+
res.status(500).json({ error: "Internal server error" });
|
|
1476
1754
|
}
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1755
|
+
};
|
|
1756
|
+
}
|
|
1757
|
+
};
|
|
1758
|
+
|
|
1759
|
+
// ../runtime-shared/dist/ai/elevenlabs/autoconfigurator.js
|
|
1760
|
+
var FETCH_TIMEOUT_MS2 = 1e4;
|
|
1761
|
+
function maskPhone(phone) {
|
|
1762
|
+
if (phone.length <= 6)
|
|
1763
|
+
return "***";
|
|
1764
|
+
return phone.slice(0, 4) + "***" + phone.slice(-4);
|
|
1765
|
+
}
|
|
1766
|
+
async function tryAutoConfigureAsync(webhookUrl, functionName, sinchPhoneNumber) {
|
|
1767
|
+
const autoConfig = process.env.ELEVENLABS_AUTO_CONFIGURE;
|
|
1768
|
+
if (!autoConfig || autoConfig.toLowerCase() !== "true") {
|
|
1769
|
+
return null;
|
|
1770
|
+
}
|
|
1771
|
+
console.log("[ElevenLabs] Auto-configuration is enabled");
|
|
1772
|
+
const agentId = process.env.ELEVENLABS_AGENT_ID;
|
|
1773
|
+
const apiKey = process.env.ELEVENLABS_API_KEY;
|
|
1774
|
+
if (!agentId) {
|
|
1775
|
+
console.warn("[ElevenLabs] Auto-configuration skipped: ELEVENLABS_AGENT_ID not set. Please configure your agent ID in environment variables.");
|
|
1776
|
+
return null;
|
|
1777
|
+
}
|
|
1778
|
+
if (!apiKey) {
|
|
1779
|
+
console.warn("[ElevenLabs] Auto-configuration skipped: ELEVENLABS_API_KEY not set. Please store your API key using: sinch secrets add ELEVENLABS_API_KEY sk_...");
|
|
1780
|
+
return null;
|
|
1781
|
+
}
|
|
1782
|
+
let phoneNumber = sinchPhoneNumber ?? process.env.SINCH_PHONE_NUMBER;
|
|
1783
|
+
if (!phoneNumber) {
|
|
1784
|
+
console.log("[ElevenLabs] SINCH_PHONE_NUMBER not configured, attempting auto-discovery from Voice API...");
|
|
1785
|
+
phoneNumber = await autoDiscoverPhoneNumber();
|
|
1786
|
+
}
|
|
1787
|
+
if (!phoneNumber) {
|
|
1788
|
+
console.warn("[ElevenLabs] Auto-configuration skipped: Sinch phone number not provided and auto-discovery failed. Please set SINCH_PHONE_NUMBER or ensure Voice API credentials are configured.");
|
|
1789
|
+
return null;
|
|
1790
|
+
}
|
|
1791
|
+
if (!webhookUrl) {
|
|
1792
|
+
console.warn("[ElevenLabs] Auto-configuration skipped: Webhook URL not provided.");
|
|
1793
|
+
return null;
|
|
1794
|
+
}
|
|
1795
|
+
try {
|
|
1796
|
+
new URL(webhookUrl);
|
|
1797
|
+
} catch {
|
|
1798
|
+
console.warn("[ElevenLabs] Auto-configuration skipped: Webhook URL is not a valid URL.");
|
|
1799
|
+
return null;
|
|
1800
|
+
}
|
|
1801
|
+
const voiceAppKey = process.env.VOICE_APPLICATION_KEY || "";
|
|
1802
|
+
const voiceAppSecret = process.env.VOICE_APPLICATION_SECRET || "";
|
|
1803
|
+
const safeName = functionName ? functionName.slice(0, 100) : "Sinch Function";
|
|
1804
|
+
try {
|
|
1805
|
+
console.log(`[ElevenLabs] Starting auto-configuration: Agent=${agentId}, Number=${maskPhone(phoneNumber)}`);
|
|
1806
|
+
const client = new ElevenLabsClient(apiKey);
|
|
1807
|
+
const result = await client.autoConfigureAsync({
|
|
1808
|
+
agentId,
|
|
1809
|
+
phoneNumber,
|
|
1810
|
+
sipAddress: "use1.vp.sip.sinch.com",
|
|
1811
|
+
sipUsername: voiceAppKey,
|
|
1812
|
+
sipPassword: voiceAppSecret,
|
|
1813
|
+
webhookBaseUrl: webhookUrl,
|
|
1814
|
+
functionName: safeName
|
|
1815
|
+
});
|
|
1816
|
+
if (result.success) {
|
|
1817
|
+
ElevenLabsState.setConfigured({
|
|
1818
|
+
phoneNumberId: result.phoneNumberId,
|
|
1819
|
+
phoneNumber: result.phoneNumber,
|
|
1820
|
+
agentId,
|
|
1821
|
+
sipAddress: result.sipAddress
|
|
1822
|
+
});
|
|
1823
|
+
console.log(`[ElevenLabs] Auto-configuration completed! Phone: ${maskPhone(result.phoneNumber ?? "")}, SIP: ${result.sipAddress}`);
|
|
1482
1824
|
}
|
|
1825
|
+
return result;
|
|
1826
|
+
} catch (error) {
|
|
1827
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1828
|
+
console.error(`[ElevenLabs] Auto-configuration failed: ${message}. The function will start but ElevenLabs integration may not work.`);
|
|
1829
|
+
return null;
|
|
1483
1830
|
}
|
|
1831
|
+
}
|
|
1832
|
+
async function autoDiscoverPhoneNumber() {
|
|
1833
|
+
const appKey = process.env.VOICE_APPLICATION_KEY || "";
|
|
1834
|
+
const appSecret = process.env.VOICE_APPLICATION_SECRET || "";
|
|
1835
|
+
if (!appKey || !appSecret) {
|
|
1836
|
+
console.warn("[ElevenLabs] Voice client not available for phone number auto-discovery. Ensure VOICE_APPLICATION_KEY and VOICE_APPLICATION_SECRET are configured.");
|
|
1837
|
+
return void 0;
|
|
1838
|
+
}
|
|
1839
|
+
try {
|
|
1840
|
+
const auth = Buffer.from(`${appKey}:${appSecret}`).toString("base64");
|
|
1841
|
+
const configResponse = await fetch(`https://callingapi.sinch.com/v1/configuration/numbers/`, {
|
|
1842
|
+
headers: { Authorization: `Basic ${auth}` },
|
|
1843
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS2)
|
|
1844
|
+
});
|
|
1845
|
+
if (!configResponse.ok) {
|
|
1846
|
+
console.warn(`[ElevenLabs] Failed to query Voice API for phone numbers: ${configResponse.status}`);
|
|
1847
|
+
return void 0;
|
|
1848
|
+
}
|
|
1849
|
+
const data = await configResponse.json();
|
|
1850
|
+
const numbers = (data.numbers || []).filter((n) => n.applicationkey === appKey).map((n) => n.number).filter(Boolean);
|
|
1851
|
+
if (numbers.length > 0) {
|
|
1852
|
+
console.log(`[ElevenLabs] Auto-discovered ${numbers.length} phone number(s), using first`);
|
|
1853
|
+
return numbers[0];
|
|
1854
|
+
}
|
|
1855
|
+
console.warn("[ElevenLabs] No phone numbers found for the configured Voice application");
|
|
1856
|
+
return void 0;
|
|
1857
|
+
} catch (error) {
|
|
1858
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1859
|
+
console.error(`[ElevenLabs] Failed to auto-discover phone number: ${message}`);
|
|
1860
|
+
return void 0;
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
var ElevenLabsAutoConfigurator = class {
|
|
1864
|
+
static tryAutoConfigureAsync = tryAutoConfigureAsync;
|
|
1484
1865
|
};
|
|
1485
1866
|
|
|
1486
|
-
// ../runtime-shared/dist/
|
|
1487
|
-
var
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1867
|
+
// ../runtime-shared/dist/conversation/message-builder.js
|
|
1868
|
+
var ConversationMessageBuilder = class {
|
|
1869
|
+
config;
|
|
1870
|
+
appId;
|
|
1871
|
+
channel;
|
|
1872
|
+
identity;
|
|
1873
|
+
conversationId;
|
|
1874
|
+
messageText;
|
|
1875
|
+
/**
|
|
1876
|
+
* Create a new builder with configuration
|
|
1877
|
+
*/
|
|
1878
|
+
constructor(config = {}) {
|
|
1879
|
+
this.config = config;
|
|
1880
|
+
}
|
|
1881
|
+
/**
|
|
1882
|
+
* Pre-fill builder from an inbound message (for replies)
|
|
1883
|
+
*/
|
|
1884
|
+
fromInbound(inbound) {
|
|
1885
|
+
this.appId = inbound.app_id;
|
|
1886
|
+
this.channel = inbound.message?.channel_identity?.channel;
|
|
1887
|
+
this.identity = inbound.message?.channel_identity?.identity;
|
|
1888
|
+
this.conversationId = inbound.message?.conversation_id;
|
|
1889
|
+
return this;
|
|
1890
|
+
}
|
|
1891
|
+
/**
|
|
1892
|
+
* Set the app ID (defaults to CONVERSATION_APP_ID from config)
|
|
1893
|
+
*/
|
|
1894
|
+
setAppId(appId) {
|
|
1895
|
+
this.appId = appId;
|
|
1896
|
+
return this;
|
|
1897
|
+
}
|
|
1898
|
+
/**
|
|
1899
|
+
* Set the recipient channel and identity
|
|
1900
|
+
*
|
|
1901
|
+
* @param channel - Channel name (SMS, WHATSAPP, MESSENGER, etc.)
|
|
1902
|
+
* @param identity - Channel-specific identity (phone number, PSID, etc.)
|
|
1903
|
+
*/
|
|
1904
|
+
to(channel, identity) {
|
|
1905
|
+
this.channel = channel;
|
|
1906
|
+
this.identity = identity;
|
|
1907
|
+
return this;
|
|
1908
|
+
}
|
|
1909
|
+
/**
|
|
1910
|
+
* Set the conversation ID (for non-DISPATCH mode apps)
|
|
1911
|
+
*/
|
|
1912
|
+
setConversationId(conversationId) {
|
|
1913
|
+
this.conversationId = conversationId;
|
|
1914
|
+
return this;
|
|
1915
|
+
}
|
|
1916
|
+
/**
|
|
1917
|
+
* Set a text message
|
|
1918
|
+
*/
|
|
1919
|
+
text(text) {
|
|
1920
|
+
this.messageText = text;
|
|
1921
|
+
return this;
|
|
1922
|
+
}
|
|
1923
|
+
/**
|
|
1924
|
+
* Build the SendMessageRequest.
|
|
1925
|
+
* Automatically sets SMS_SENDER from config if sending to SMS channel.
|
|
1926
|
+
*
|
|
1927
|
+
* @returns A SendMessageRequest ready to send via context.conversation.messages.send()
|
|
1928
|
+
*/
|
|
1929
|
+
build() {
|
|
1930
|
+
const appId = this.appId || this.config.CONVERSATION_APP_ID;
|
|
1931
|
+
if (!appId) {
|
|
1932
|
+
throw new Error("AppId required - use setAppId() or set CONVERSATION_APP_ID in config");
|
|
1933
|
+
}
|
|
1934
|
+
if (!this.channel || !this.identity) {
|
|
1935
|
+
throw new Error("Recipient required - use to() or fromInbound()");
|
|
1936
|
+
}
|
|
1937
|
+
if (!this.messageText) {
|
|
1938
|
+
throw new Error("Message required - use text()");
|
|
1939
|
+
}
|
|
1940
|
+
const request = {
|
|
1941
|
+
app_id: appId,
|
|
1942
|
+
message: {
|
|
1943
|
+
text_message: {
|
|
1944
|
+
text: this.messageText
|
|
1945
|
+
}
|
|
1946
|
+
},
|
|
1947
|
+
recipient: {
|
|
1948
|
+
identified_by: {
|
|
1949
|
+
channel_identities: [
|
|
1950
|
+
{
|
|
1951
|
+
channel: this.channel,
|
|
1952
|
+
identity: this.identity
|
|
1953
|
+
}
|
|
1954
|
+
]
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
};
|
|
1958
|
+
if (this.channel.toUpperCase() === "SMS") {
|
|
1959
|
+
const smsSender = this.config.FROM_NUMBER;
|
|
1960
|
+
if (smsSender) {
|
|
1961
|
+
request.channel_properties = {
|
|
1962
|
+
SMS_SENDER: smsSender
|
|
1519
1963
|
};
|
|
1520
|
-
} catch (error) {
|
|
1521
|
-
console.warn("HTML template not found, returning JSON");
|
|
1522
1964
|
}
|
|
1523
1965
|
}
|
|
1524
|
-
return
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1966
|
+
return request;
|
|
1967
|
+
}
|
|
1968
|
+
};
|
|
1969
|
+
function createMessageBuilder(config = {}) {
|
|
1970
|
+
return new ConversationMessageBuilder(config);
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
// ../runtime-shared/dist/conversation/message-helpers.js
|
|
1974
|
+
function textReply(inbound, text, smsSender) {
|
|
1975
|
+
const channelIdentity = inbound.message?.channel_identity;
|
|
1976
|
+
if (!channelIdentity) {
|
|
1977
|
+
throw new Error("Inbound message has no channel identity");
|
|
1978
|
+
}
|
|
1979
|
+
const channel = channelIdentity.channel;
|
|
1980
|
+
const identity = channelIdentity.identity;
|
|
1981
|
+
if (!channel || !identity) {
|
|
1982
|
+
throw new Error("Inbound message has no channel or identity");
|
|
1983
|
+
}
|
|
1984
|
+
return createChannelMessage(inbound.app_id, channel, identity, text, smsSender);
|
|
1985
|
+
}
|
|
1986
|
+
function createSms(appId, phoneNumber, text, smsSender) {
|
|
1987
|
+
return createChannelMessage(appId, "SMS", phoneNumber, text, smsSender);
|
|
1988
|
+
}
|
|
1989
|
+
function createWhatsApp(appId, phoneNumber, text) {
|
|
1990
|
+
return createChannelMessage(appId, "WHATSAPP", phoneNumber, text);
|
|
1991
|
+
}
|
|
1992
|
+
function createMessenger(appId, psid, text) {
|
|
1993
|
+
return createChannelMessage(appId, "MESSENGER", psid, text);
|
|
1994
|
+
}
|
|
1995
|
+
function createChannelMessage(appId, channel, identity, text, smsSender) {
|
|
1996
|
+
if (!appId)
|
|
1997
|
+
throw new Error("appId is required");
|
|
1998
|
+
if (!channel)
|
|
1999
|
+
throw new Error("channel is required");
|
|
2000
|
+
if (!identity)
|
|
2001
|
+
throw new Error("identity is required");
|
|
2002
|
+
if (!text)
|
|
2003
|
+
throw new Error("text is required");
|
|
2004
|
+
const request = {
|
|
2005
|
+
app_id: appId,
|
|
2006
|
+
message: {
|
|
2007
|
+
text_message: {
|
|
2008
|
+
text
|
|
2009
|
+
}
|
|
2010
|
+
},
|
|
2011
|
+
recipient: {
|
|
2012
|
+
identified_by: {
|
|
2013
|
+
channel_identities: [
|
|
2014
|
+
{
|
|
2015
|
+
channel,
|
|
2016
|
+
identity
|
|
2017
|
+
}
|
|
2018
|
+
]
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
};
|
|
2022
|
+
if (channel.toUpperCase() === "SMS" && smsSender) {
|
|
2023
|
+
request.channel_properties = {
|
|
2024
|
+
SMS_SENDER: smsSender
|
|
2025
|
+
};
|
|
2026
|
+
}
|
|
2027
|
+
return request;
|
|
2028
|
+
}
|
|
2029
|
+
var ConversationMessage = {
|
|
2030
|
+
textReply,
|
|
2031
|
+
createSms,
|
|
2032
|
+
createWhatsApp,
|
|
2033
|
+
createMessenger,
|
|
2034
|
+
createChannelMessage
|
|
2035
|
+
};
|
|
2036
|
+
|
|
2037
|
+
// ../runtime-shared/dist/conversation/controller.js
|
|
2038
|
+
var ConversationController = class {
|
|
2039
|
+
conversation;
|
|
2040
|
+
config;
|
|
2041
|
+
constructor(conversation, config = {}) {
|
|
2042
|
+
this.conversation = conversation;
|
|
2043
|
+
this.config = config;
|
|
2044
|
+
}
|
|
2045
|
+
/**
|
|
2046
|
+
* MESSAGE_INBOUND - Called when a user sends a message.
|
|
2047
|
+
* Override this to handle incoming messages from users.
|
|
2048
|
+
*/
|
|
2049
|
+
async handleMessageInbound(event) {
|
|
2050
|
+
}
|
|
2051
|
+
/**
|
|
2052
|
+
* MESSAGE_DELIVERY - Called when message delivery status updates.
|
|
2053
|
+
* Override this to track delivery receipts.
|
|
2054
|
+
*/
|
|
2055
|
+
async handleMessageDelivery(event) {
|
|
2056
|
+
}
|
|
2057
|
+
/**
|
|
2058
|
+
* EVENT_INBOUND - Called for events like composing indicators.
|
|
2059
|
+
* Override this to handle typing indicators, read receipts, etc.
|
|
2060
|
+
*/
|
|
2061
|
+
async handleEventInbound(event) {
|
|
2062
|
+
}
|
|
2063
|
+
/**
|
|
2064
|
+
* CONVERSATION_START - Called when a conversation begins.
|
|
2065
|
+
* Override this to handle conversation initialization.
|
|
2066
|
+
*/
|
|
2067
|
+
async handleConversationStart(event) {
|
|
2068
|
+
}
|
|
2069
|
+
/**
|
|
2070
|
+
* CONVERSATION_STOP - Called when a conversation ends.
|
|
2071
|
+
* Override this to handle conversation cleanup.
|
|
2072
|
+
*/
|
|
2073
|
+
async handleConversationStop(event) {
|
|
2074
|
+
}
|
|
2075
|
+
/**
|
|
2076
|
+
* Gets the sender number from configuration (FROM_NUMBER).
|
|
2077
|
+
* Used for setting SMS_SENDER channel property when sending to SMS channel.
|
|
2078
|
+
*/
|
|
2079
|
+
get fromNumber() {
|
|
2080
|
+
return this.config.FROM_NUMBER;
|
|
2081
|
+
}
|
|
2082
|
+
/**
|
|
2083
|
+
* Create a text reply to an inbound message.
|
|
2084
|
+
* Automatically sets SMS_SENDER from FROM_NUMBER config.
|
|
2085
|
+
*/
|
|
2086
|
+
reply(inbound, text) {
|
|
2087
|
+
return textReply(inbound, text, this.fromNumber);
|
|
2088
|
+
}
|
|
2089
|
+
/**
|
|
2090
|
+
* Create a ConversationMessageBuilder for building messages with full control.
|
|
2091
|
+
*/
|
|
2092
|
+
createMessage() {
|
|
2093
|
+
return new ConversationMessageBuilder(this.config);
|
|
2094
|
+
}
|
|
2095
|
+
/**
|
|
2096
|
+
* Create a ConversationMessageBuilder pre-filled from an inbound message.
|
|
2097
|
+
*/
|
|
2098
|
+
createReply(inbound) {
|
|
2099
|
+
return new ConversationMessageBuilder(this.config).fromInbound(inbound);
|
|
2100
|
+
}
|
|
2101
|
+
/**
|
|
2102
|
+
* Express route handler for the webhook endpoint.
|
|
2103
|
+
* Mount at: POST /conversation
|
|
2104
|
+
*
|
|
2105
|
+
* Routes to appropriate handler based on event type.
|
|
2106
|
+
* Uses manual JSON parsing as workaround for SDK issues with extra fields.
|
|
2107
|
+
*/
|
|
2108
|
+
getWebhookHandler() {
|
|
2109
|
+
return async (req, res) => {
|
|
2110
|
+
try {
|
|
2111
|
+
const body = req.body;
|
|
2112
|
+
const verbose = this.config.VERBOSE === "true";
|
|
2113
|
+
if (!body) {
|
|
2114
|
+
res.status(400).json({ error: "Empty request body" });
|
|
2115
|
+
return;
|
|
2116
|
+
}
|
|
2117
|
+
if (!this.conversation) {
|
|
2118
|
+
console.error("[Conversation] Client not configured");
|
|
2119
|
+
res.status(500).json({ error: "Conversation API not configured" });
|
|
2120
|
+
return;
|
|
1539
2121
|
}
|
|
2122
|
+
if (body.message?.direction === "TO_APP" && body.message?.contact_message) {
|
|
2123
|
+
if (verbose) {
|
|
2124
|
+
console.log("[Conversation] MESSAGE_INBOUND event detected");
|
|
2125
|
+
}
|
|
2126
|
+
await this.handleMessageInbound(body);
|
|
2127
|
+
res.status(200).send("OK");
|
|
2128
|
+
return;
|
|
2129
|
+
}
|
|
2130
|
+
if (body.message_delivery_report) {
|
|
2131
|
+
if (verbose) {
|
|
2132
|
+
console.log("[Conversation] MESSAGE_DELIVERY event detected");
|
|
2133
|
+
}
|
|
2134
|
+
await this.handleMessageDelivery(body);
|
|
2135
|
+
res.status(200).send("OK");
|
|
2136
|
+
return;
|
|
2137
|
+
}
|
|
2138
|
+
if (body.event?.contact_event) {
|
|
2139
|
+
if (verbose) {
|
|
2140
|
+
console.log("[Conversation] EVENT_INBOUND event detected");
|
|
2141
|
+
}
|
|
2142
|
+
await this.handleEventInbound(body);
|
|
2143
|
+
res.status(200).send("OK");
|
|
2144
|
+
return;
|
|
2145
|
+
}
|
|
2146
|
+
if (body.conversation_event) {
|
|
2147
|
+
const eventType = body.conversation_event.type;
|
|
2148
|
+
if (eventType === "CONVERSATION_START") {
|
|
2149
|
+
if (verbose) {
|
|
2150
|
+
console.log("[Conversation] CONVERSATION_START event detected");
|
|
2151
|
+
}
|
|
2152
|
+
await this.handleConversationStart(body);
|
|
2153
|
+
} else if (eventType === "CONVERSATION_STOP") {
|
|
2154
|
+
if (verbose) {
|
|
2155
|
+
console.log("[Conversation] CONVERSATION_STOP event detected");
|
|
2156
|
+
}
|
|
2157
|
+
await this.handleConversationStop(body);
|
|
2158
|
+
}
|
|
2159
|
+
res.status(200).send("OK");
|
|
2160
|
+
return;
|
|
2161
|
+
}
|
|
2162
|
+
if (verbose) {
|
|
2163
|
+
console.log("[Conversation] Unknown event type, ignoring");
|
|
2164
|
+
}
|
|
2165
|
+
res.status(200).json({ status: "ignored", reason: "unknown_event_type" });
|
|
2166
|
+
} catch (error) {
|
|
2167
|
+
console.error("[Conversation] Error processing webhook:", error);
|
|
2168
|
+
res.status(500).json({ error: "Internal server error" });
|
|
1540
2169
|
}
|
|
1541
2170
|
};
|
|
1542
2171
|
}
|
|
1543
2172
|
};
|
|
1544
2173
|
|
|
2174
|
+
// ../runtime-shared/dist/conversation/extensions.js
|
|
2175
|
+
function getText(event) {
|
|
2176
|
+
return event.message?.contact_message?.text_message?.text;
|
|
2177
|
+
}
|
|
2178
|
+
function getMedia(event) {
|
|
2179
|
+
return event.message?.contact_message?.media_message;
|
|
2180
|
+
}
|
|
2181
|
+
function getPostbackData(event) {
|
|
2182
|
+
return event.message?.contact_message?.choice_response_message?.postback_data;
|
|
2183
|
+
}
|
|
2184
|
+
function getContactId(event) {
|
|
2185
|
+
return event.message?.contact_id;
|
|
2186
|
+
}
|
|
2187
|
+
function getConversationId(event) {
|
|
2188
|
+
return event.message?.conversation_id;
|
|
2189
|
+
}
|
|
2190
|
+
function getChannel(event) {
|
|
2191
|
+
return event.message?.channel_identity?.channel;
|
|
2192
|
+
}
|
|
2193
|
+
function getIdentity(event) {
|
|
2194
|
+
return event.message?.channel_identity?.identity;
|
|
2195
|
+
}
|
|
2196
|
+
function getTo(event) {
|
|
2197
|
+
return event.message?.sender_id;
|
|
2198
|
+
}
|
|
2199
|
+
function getLocation(event) {
|
|
2200
|
+
return event.message?.contact_message?.location_message;
|
|
2201
|
+
}
|
|
2202
|
+
function isTextMessage(event) {
|
|
2203
|
+
return event.message?.contact_message?.text_message !== void 0;
|
|
2204
|
+
}
|
|
2205
|
+
function isMediaMessage(event) {
|
|
2206
|
+
return event.message?.contact_message?.media_message !== void 0;
|
|
2207
|
+
}
|
|
2208
|
+
function isPostback(event) {
|
|
2209
|
+
return event.message?.contact_message?.choice_response_message !== void 0;
|
|
2210
|
+
}
|
|
2211
|
+
function createMessageHelper(event) {
|
|
2212
|
+
return {
|
|
2213
|
+
getText: () => getText(event),
|
|
2214
|
+
getMedia: () => getMedia(event),
|
|
2215
|
+
getPostbackData: () => getPostbackData(event),
|
|
2216
|
+
getContactId: () => getContactId(event),
|
|
2217
|
+
getConversationId: () => getConversationId(event),
|
|
2218
|
+
getChannel: () => getChannel(event),
|
|
2219
|
+
getIdentity: () => getIdentity(event),
|
|
2220
|
+
getTo: () => getTo(event),
|
|
2221
|
+
getLocation: () => getLocation(event),
|
|
2222
|
+
isTextMessage: () => isTextMessage(event),
|
|
2223
|
+
isMediaMessage: () => isMediaMessage(event),
|
|
2224
|
+
isPostback: () => isPostback(event)
|
|
2225
|
+
};
|
|
2226
|
+
}
|
|
2227
|
+
|
|
2228
|
+
// ../runtime-shared/dist/voice/helpers.js
|
|
2229
|
+
var COUNTRY_CODES = {
|
|
2230
|
+
"+1": { code: "US", format: (n) => `(${n.slice(0, 3)}) ${n.slice(3, 6)}-${n.slice(6)}` },
|
|
2231
|
+
"+44": { code: "GB", format: (n) => `0${n.slice(0, 4)} ${n.slice(4)}` },
|
|
2232
|
+
"+46": {
|
|
2233
|
+
code: "SE",
|
|
2234
|
+
format: (n) => `0${n.slice(0, 2)}-${n.slice(2, 5)} ${n.slice(5, 7)} ${n.slice(7)}`
|
|
2235
|
+
},
|
|
2236
|
+
"+49": { code: "DE", format: (n) => `0${n.slice(0, 3)} ${n.slice(3)}` },
|
|
2237
|
+
"+33": {
|
|
2238
|
+
code: "FR",
|
|
2239
|
+
format: (n) => `0${n.slice(0, 1)} ${n.slice(1, 3)} ${n.slice(3, 5)} ${n.slice(5, 7)} ${n.slice(7)}`
|
|
2240
|
+
},
|
|
2241
|
+
"+61": { code: "AU", format: (n) => `0${n.slice(0, 1)} ${n.slice(1, 5)} ${n.slice(5)}` }
|
|
2242
|
+
};
|
|
2243
|
+
function toLocalPhoneNumber(phoneNumber, defaultRegion = "US") {
|
|
2244
|
+
if (!phoneNumber || !phoneNumber.startsWith("+")) {
|
|
2245
|
+
return phoneNumber;
|
|
2246
|
+
}
|
|
2247
|
+
const sortedCodes = Object.keys(COUNTRY_CODES).sort((a, b) => b.length - a.length);
|
|
2248
|
+
for (const prefix of sortedCodes) {
|
|
2249
|
+
if (phoneNumber.startsWith(prefix)) {
|
|
2250
|
+
const national = phoneNumber.slice(prefix.length);
|
|
2251
|
+
try {
|
|
2252
|
+
return COUNTRY_CODES[prefix].format(national);
|
|
2253
|
+
} catch {
|
|
2254
|
+
return phoneNumber;
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
return phoneNumber.slice(1);
|
|
2259
|
+
}
|
|
2260
|
+
function formatPhoneNumber(phoneNumber) {
|
|
2261
|
+
const cleaned = phoneNumber.replace(/[^\d+]/g, "");
|
|
2262
|
+
if (cleaned.startsWith("+")) {
|
|
2263
|
+
return toLocalPhoneNumber(cleaned);
|
|
2264
|
+
}
|
|
2265
|
+
if (cleaned.length === 10) {
|
|
2266
|
+
return `(${cleaned.slice(0, 3)}) ${cleaned.slice(3, 6)}-${cleaned.slice(6)}`;
|
|
2267
|
+
}
|
|
2268
|
+
return phoneNumber;
|
|
2269
|
+
}
|
|
2270
|
+
var VoiceErrorHelper = {
|
|
2271
|
+
/**
|
|
2272
|
+
* Create a standard error response that plays a message and hangs up
|
|
2273
|
+
*
|
|
2274
|
+
* @param message - Error message to speak
|
|
2275
|
+
* @param locale - Language locale (default: "en-US")
|
|
2276
|
+
* @returns SVAML response object
|
|
2277
|
+
*/
|
|
2278
|
+
createErrorResponse(message, locale = "en-US") {
|
|
2279
|
+
return new IceSvamlBuilder().say(message, locale).hangup().build();
|
|
2280
|
+
},
|
|
2281
|
+
/**
|
|
2282
|
+
* Create a service unavailable response
|
|
2283
|
+
*
|
|
2284
|
+
* @param locale - Language locale (default: "en-US")
|
|
2285
|
+
* @returns SVAML response object
|
|
2286
|
+
*/
|
|
2287
|
+
serviceUnavailable(locale = "en-US") {
|
|
2288
|
+
return this.createErrorResponse("We're sorry, our service is temporarily unavailable. Please try again later.", locale);
|
|
2289
|
+
},
|
|
2290
|
+
/**
|
|
2291
|
+
* Create an invalid input response for PIE handlers
|
|
2292
|
+
*
|
|
2293
|
+
* @param locale - Language locale (default: "en-US")
|
|
2294
|
+
* @returns SVAML response object
|
|
2295
|
+
*/
|
|
2296
|
+
invalidInput(locale = "en-US") {
|
|
2297
|
+
return new PieSvamlBuilder().say("Invalid selection. Please try again.", locale).continue().build();
|
|
2298
|
+
},
|
|
2299
|
+
/**
|
|
2300
|
+
* Create a timeout response for PIE handlers
|
|
2301
|
+
*
|
|
2302
|
+
* @param locale - Language locale (default: "en-US")
|
|
2303
|
+
* @returns SVAML response object
|
|
2304
|
+
*/
|
|
2305
|
+
timeout(locale = "en-US") {
|
|
2306
|
+
return new PieSvamlBuilder().say("We did not receive a response. Please try again.", locale).continue().build();
|
|
2307
|
+
},
|
|
2308
|
+
/**
|
|
2309
|
+
* Create a goodbye response that thanks the caller and hangs up
|
|
2310
|
+
*
|
|
2311
|
+
* @param locale - Language locale (default: "en-US")
|
|
2312
|
+
* @returns SVAML response object
|
|
2313
|
+
*/
|
|
2314
|
+
goodbye(locale = "en-US") {
|
|
2315
|
+
return new IceSvamlBuilder().say("Thank you for calling. Goodbye!", locale).hangup().build();
|
|
2316
|
+
}
|
|
2317
|
+
};
|
|
2318
|
+
function formatDuration(seconds) {
|
|
2319
|
+
if (seconds < 60) {
|
|
2320
|
+
return `${Math.round(seconds)}s`;
|
|
2321
|
+
}
|
|
2322
|
+
const minutes = Math.floor(seconds / 60);
|
|
2323
|
+
const remainingSeconds = Math.round(seconds % 60);
|
|
2324
|
+
if (remainingSeconds === 0) {
|
|
2325
|
+
return `${minutes}m`;
|
|
2326
|
+
}
|
|
2327
|
+
return `${minutes}m ${remainingSeconds}s`;
|
|
2328
|
+
}
|
|
2329
|
+
function extractCallerNumber(cli) {
|
|
2330
|
+
if (!cli)
|
|
2331
|
+
return void 0;
|
|
2332
|
+
return cli.replace(/[^\d+]/g, "");
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
// ../runtime-shared/dist/utils/templateRender.js
|
|
2336
|
+
import fs from "fs";
|
|
2337
|
+
import path from "path";
|
|
2338
|
+
|
|
2339
|
+
// ../runtime-shared/dist/utils/versionExtractor.js
|
|
2340
|
+
import fs2 from "fs";
|
|
2341
|
+
import path2 from "path";
|
|
2342
|
+
|
|
1545
2343
|
// ../runtime-shared/dist/utils/functionLoader.js
|
|
1546
2344
|
import path3 from "path";
|
|
1547
2345
|
|
|
@@ -1658,7 +2456,7 @@ async function configureConversationWebhooks(tunnelUrl, config) {
|
|
|
1658
2456
|
console.log("\u{1F4A1} Conversation API not fully configured - skipping webhook setup");
|
|
1659
2457
|
return;
|
|
1660
2458
|
}
|
|
1661
|
-
const webhookUrl = `${tunnelUrl}/webhook`;
|
|
2459
|
+
const webhookUrl = `${tunnelUrl}/webhook/conversation`;
|
|
1662
2460
|
console.log(`\u{1F4AC} Conversation webhook URL: ${webhookUrl}`);
|
|
1663
2461
|
const sinchClient = new SinchClient2({
|
|
1664
2462
|
projectId,
|
|
@@ -2204,7 +3002,14 @@ var SecretsLoader = class {
|
|
|
2204
3002
|
var secretsLoader = new SecretsLoader();
|
|
2205
3003
|
export {
|
|
2206
3004
|
AceSvamlBuilder,
|
|
2207
|
-
|
|
3005
|
+
AgentProvider,
|
|
3006
|
+
ConversationController,
|
|
3007
|
+
ConversationMessage,
|
|
3008
|
+
ConversationMessageBuilder,
|
|
3009
|
+
ElevenLabsAutoConfigurator,
|
|
3010
|
+
ElevenLabsClient,
|
|
3011
|
+
ElevenLabsController,
|
|
3012
|
+
ElevenLabsState,
|
|
2208
3013
|
IceSvamlBuilder,
|
|
2209
3014
|
LocalCache,
|
|
2210
3015
|
MenuBuilder,
|
|
@@ -2212,36 +3017,59 @@ export {
|
|
|
2212
3017
|
NOTIFICATION_EVENTS,
|
|
2213
3018
|
PieSvamlBuilder,
|
|
2214
3019
|
SecretsLoader,
|
|
2215
|
-
TemplateRender,
|
|
2216
3020
|
TunnelClient,
|
|
2217
3021
|
UniversalConfig,
|
|
2218
3022
|
VOICE_CALLBACKS,
|
|
2219
|
-
|
|
3023
|
+
VoiceErrorHelper,
|
|
2220
3024
|
buildBaseContext,
|
|
2221
3025
|
toCamelCase as convertToCamelCase,
|
|
2222
3026
|
createAceBuilder,
|
|
2223
3027
|
createApp,
|
|
2224
3028
|
createCacheClient,
|
|
3029
|
+
createChannelMessage,
|
|
2225
3030
|
createConfig,
|
|
3031
|
+
createElevenLabsClient,
|
|
2226
3032
|
createErrorResponse,
|
|
2227
3033
|
createIceBuilder,
|
|
2228
3034
|
createLenientJsonParser as createJsonParser,
|
|
2229
3035
|
createJsonResponse,
|
|
2230
3036
|
createLenientJsonParser,
|
|
2231
3037
|
createMenu,
|
|
3038
|
+
createMessageBuilder,
|
|
3039
|
+
createMessageHelper,
|
|
3040
|
+
createMessenger,
|
|
2232
3041
|
createPieBuilder,
|
|
2233
3042
|
createResponse,
|
|
2234
3043
|
createSimpleMenu,
|
|
3044
|
+
createSms,
|
|
2235
3045
|
createUniversalConfig,
|
|
3046
|
+
createWhatsApp,
|
|
3047
|
+
extractCallerNumber,
|
|
2236
3048
|
extractFunctionName,
|
|
2237
3049
|
formatCustomResponse,
|
|
3050
|
+
formatDuration,
|
|
3051
|
+
formatPhoneNumber,
|
|
2238
3052
|
formatSvamlResponse,
|
|
2239
3053
|
generateRequestId,
|
|
3054
|
+
getChannel,
|
|
3055
|
+
getContactId,
|
|
3056
|
+
getConversationId,
|
|
3057
|
+
getFaviconSvg,
|
|
3058
|
+
getIdentity,
|
|
3059
|
+
getLandingPageHtml,
|
|
3060
|
+
getLocation,
|
|
3061
|
+
getMedia,
|
|
3062
|
+
getPostbackData,
|
|
2240
3063
|
getSinchClients,
|
|
3064
|
+
getText,
|
|
3065
|
+
getTo,
|
|
2241
3066
|
getTunnelClient,
|
|
2242
3067
|
handleCustomEndpoint,
|
|
2243
3068
|
handleVoiceCallback,
|
|
3069
|
+
isMediaMessage,
|
|
2244
3070
|
isNotificationEvent,
|
|
3071
|
+
isPostback,
|
|
3072
|
+
isTextMessage,
|
|
2245
3073
|
isVoiceCallback,
|
|
2246
3074
|
parseJson,
|
|
2247
3075
|
parseJson as parseJsonLenient,
|
|
@@ -2250,9 +3078,12 @@ export {
|
|
|
2250
3078
|
setupJsonParsing as setupJsonMiddleware,
|
|
2251
3079
|
setupJsonParsing,
|
|
2252
3080
|
setupRequestHandler,
|
|
3081
|
+
textReply,
|
|
2253
3082
|
toCamelCase,
|
|
3083
|
+
toLocalPhoneNumber,
|
|
2254
3084
|
transformKeys,
|
|
2255
3085
|
transformKeys as transformKeysToCamelCase,
|
|
3086
|
+
tryAutoConfigureAsync,
|
|
2256
3087
|
validateVoiceRequest
|
|
2257
3088
|
};
|
|
2258
3089
|
//# sourceMappingURL=index.js.map
|