@sinch/functions-runtime 0.1.0-beta.28 → 0.2.2-beta

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.js CHANGED
@@ -1,3 +1,52 @@
1
+ // ../runtime-shared/dist/ai/connect-agent.js
2
+ var AgentProvider;
3
+ (function(AgentProvider2) {
4
+ AgentProvider2["ElevenLabs"] = "elevenlabs";
5
+ })(AgentProvider || (AgentProvider = {}));
6
+ var AgentSipHelper = class {
7
+ /**
8
+ * Build a SIP URI for connecting to an ElevenLabs agent
9
+ */
10
+ static buildElevenLabsSipUri(agentId, options) {
11
+ const baseUri = `sip:${agentId}@sip.elevenlabs.io`;
12
+ if (options?.customHeaders && Object.keys(options.customHeaders).length > 0) {
13
+ const headers = Object.entries(options.customHeaders).map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join("&");
14
+ return `${baseUri};${headers}`;
15
+ }
16
+ return baseUri;
17
+ }
18
+ /**
19
+ * Build a SIP URI for the given provider and agent
20
+ */
21
+ static buildSipUri(provider, agentId, options) {
22
+ switch (provider) {
23
+ case AgentProvider.ElevenLabs:
24
+ return this.buildElevenLabsSipUri(agentId, options);
25
+ default:
26
+ throw new Error(`Unsupported agent provider: ${provider}`);
27
+ }
28
+ }
29
+ };
30
+ function createConnectAgentAction(provider, agentId, options) {
31
+ const sipUri = AgentSipHelper.buildSipUri(provider, agentId, options);
32
+ const action = {
33
+ name: "connectSip",
34
+ destination: {
35
+ endpoint: sipUri
36
+ }
37
+ };
38
+ if (options?.cli) {
39
+ action.cli = options.cli;
40
+ }
41
+ if (options?.maxDuration !== void 0) {
42
+ action.maxDuration = options.maxDuration;
43
+ }
44
+ if (options?.suppressCallbacks !== void 0) {
45
+ action.suppressCallbacks = options.suppressCallbacks;
46
+ }
47
+ return action;
48
+ }
49
+
1
50
  // ../runtime-shared/dist/builders/svaml.js
2
51
  var BaseSvamlBuilder = class {
3
52
  instructions = [];
@@ -201,6 +250,25 @@ var IceSvamlBuilder = class extends BaseSvamlBuilder {
201
250
  };
202
251
  return this;
203
252
  }
253
+ /**
254
+ * Connect to an AI voice agent
255
+ *
256
+ * @param provider - The AI agent provider (e.g., AgentProvider.ElevenLabs)
257
+ * @param agentId - The agent ID to connect to
258
+ * @param options - Connection options
259
+ *
260
+ * @example
261
+ * ```typescript
262
+ * return new IceSvamlBuilder()
263
+ * .say('Connecting you to our AI assistant.')
264
+ * .connectAgent(AgentProvider.ElevenLabs, 'agent_123')
265
+ * .build();
266
+ * ```
267
+ */
268
+ connectAgent(provider, agentId, options) {
269
+ this.action = createConnectAgentAction(provider, agentId, options);
270
+ return this;
271
+ }
204
272
  };
205
273
  var AceSvamlBuilder = class extends BaseSvamlBuilder {
206
274
  };
@@ -250,6 +318,28 @@ var PieSvamlBuilder = class extends BaseSvamlBuilder {
250
318
  };
251
319
  return this;
252
320
  }
321
+ /**
322
+ * Connect to an AI voice agent
323
+ *
324
+ * @param provider - The AI agent provider (e.g., AgentProvider.ElevenLabs)
325
+ * @param agentId - The agent ID to connect to
326
+ * @param options - Connection options
327
+ *
328
+ * @example
329
+ * ```typescript
330
+ * // Transfer to AI agent based on menu selection
331
+ * if (event.menuResult?.value === 'ai') {
332
+ * return new PieSvamlBuilder()
333
+ * .say('Connecting you to our AI assistant.')
334
+ * .connectAgent(AgentProvider.ElevenLabs, 'agent_123')
335
+ * .build();
336
+ * }
337
+ * ```
338
+ */
339
+ connectAgent(provider, agentId, options) {
340
+ this.action = createConnectAgentAction(provider, agentId, options);
341
+ return this;
342
+ }
253
343
  };
254
344
  function createIceBuilder() {
255
345
  return new IceSvamlBuilder();
@@ -1161,30 +1251,9 @@ function setupRequestHandler(app, options = {}) {
1161
1251
  }
1162
1252
 
1163
1253
  // ../runtime-shared/dist/sinch/index.js
1164
- var SinchClient = null;
1165
- var validateAuthenticationHeader = null;
1166
- async function loadSinchSdk() {
1167
- if (SinchClient !== null) {
1168
- return SinchClient !== false;
1169
- }
1170
- try {
1171
- const sdk = await import("@sinch/sdk-core");
1172
- SinchClient = sdk.SinchClient;
1173
- validateAuthenticationHeader = sdk.validateAuthenticationHeader;
1174
- return true;
1175
- } catch {
1176
- SinchClient = false;
1177
- validateAuthenticationHeader = false;
1178
- console.debug("[SINCH] SDK not available - Sinch API features disabled");
1179
- return false;
1180
- }
1181
- }
1182
- async function createSinchClients() {
1254
+ import { SinchClient, validateAuthenticationHeader } from "@sinch/sdk-core";
1255
+ function createSinchClients() {
1183
1256
  const clients = {};
1184
- const sdkAvailable = await loadSinchSdk();
1185
- if (!sdkAvailable) {
1186
- return clients;
1187
- }
1188
1257
  const hasCredentials = process.env.PROJECT_ID && process.env.PROJECT_ID_API_KEY && process.env.PROJECT_ID_API_SECRET;
1189
1258
  if (!hasCredentials) {
1190
1259
  return clients;
@@ -1193,8 +1262,7 @@ async function createSinchClients() {
1193
1262
  const sinchClient = new SinchClient({
1194
1263
  projectId: process.env.PROJECT_ID,
1195
1264
  keyId: process.env.PROJECT_ID_API_KEY,
1196
- keySecret: process.env.PROJECT_ID_API_SECRET,
1197
- region: process.env.SINCH_REGION || "us"
1265
+ keySecret: process.env.PROJECT_ID_API_SECRET
1198
1266
  });
1199
1267
  if (process.env.CONVERSATION_APP_ID) {
1200
1268
  clients.conversation = sinchClient.conversation;
@@ -1206,8 +1274,7 @@ async function createSinchClients() {
1206
1274
  keyId: process.env.PROJECT_ID_API_KEY,
1207
1275
  keySecret: process.env.PROJECT_ID_API_SECRET,
1208
1276
  applicationKey: process.env.VOICE_APPLICATION_KEY,
1209
- applicationSecret: process.env.VOICE_APPLICATION_SECRET,
1210
- region: process.env.SINCH_REGION || "us"
1277
+ applicationSecret: process.env.VOICE_APPLICATION_SECRET
1211
1278
  });
1212
1279
  clients.voice = voiceClient.voice;
1213
1280
  console.log("[SINCH] Voice API initialized with application credentials");
@@ -1224,7 +1291,7 @@ async function createSinchClients() {
1224
1291
  console.error("[SINCH] Failed to initialize Sinch clients:", error.message);
1225
1292
  return {};
1226
1293
  }
1227
- if (process.env.VOICE_APPLICATION_KEY && process.env.VOICE_APPLICATION_SECRET && validateAuthenticationHeader) {
1294
+ if (process.env.VOICE_APPLICATION_KEY && process.env.VOICE_APPLICATION_SECRET) {
1228
1295
  clients.validateWebhookSignature = (requestData) => {
1229
1296
  console.log("[SINCH] Validating Voice webhook signature");
1230
1297
  try {
@@ -1240,24 +1307,72 @@ async function createSinchClients() {
1240
1307
  return clients;
1241
1308
  }
1242
1309
  var cachedClients = null;
1243
- var initPromise = null;
1244
1310
  function getSinchClients() {
1245
- if (cachedClients) {
1246
- return cachedClients;
1311
+ if (!cachedClients) {
1312
+ cachedClients = createSinchClients();
1247
1313
  }
1248
- if (!initPromise) {
1249
- initPromise = createSinchClients().then((clients) => {
1250
- cachedClients = clients;
1251
- return clients;
1252
- });
1253
- }
1254
- return {};
1314
+ return cachedClients;
1255
1315
  }
1256
1316
  function resetSinchClients() {
1257
1317
  cachedClients = null;
1258
- initPromise = null;
1259
1318
  }
1260
1319
 
1320
+ // ../runtime-shared/dist/ai/elevenlabs/state.js
1321
+ var ElevenLabsStateManager = class {
1322
+ state = {
1323
+ isConfigured: false
1324
+ };
1325
+ /**
1326
+ * Get the current state
1327
+ */
1328
+ getState() {
1329
+ return { ...this.state };
1330
+ }
1331
+ /**
1332
+ * Check if ElevenLabs is configured
1333
+ */
1334
+ isConfigured() {
1335
+ return this.state.isConfigured;
1336
+ }
1337
+ /**
1338
+ * Update state with auto-configuration results
1339
+ */
1340
+ setConfigured(data) {
1341
+ this.state = {
1342
+ ...data,
1343
+ isConfigured: true,
1344
+ configuredAt: /* @__PURE__ */ new Date()
1345
+ };
1346
+ }
1347
+ /**
1348
+ * Clear the configuration state
1349
+ */
1350
+ clear() {
1351
+ this.state = {
1352
+ isConfigured: false
1353
+ };
1354
+ }
1355
+ /**
1356
+ * Get the phone number ID for making calls
1357
+ */
1358
+ getPhoneNumberId() {
1359
+ return this.state.phoneNumberId;
1360
+ }
1361
+ /**
1362
+ * Get the SIP address for connecting to the agent
1363
+ */
1364
+ getSipAddress() {
1365
+ return this.state.sipAddress;
1366
+ }
1367
+ /**
1368
+ * Get the configured agent ID
1369
+ */
1370
+ getAgentId() {
1371
+ return this.state.agentId;
1372
+ }
1373
+ };
1374
+ var ElevenLabsState = new ElevenLabsStateManager();
1375
+
1261
1376
  // ../runtime-shared/dist/utils/templateRender.js
1262
1377
  import fs from "fs";
1263
1378
  import path from "path";
@@ -1500,53 +1615,158 @@ function createCacheClient(_projectId, _functionName) {
1500
1615
  // src/tunnel/index.ts
1501
1616
  import WebSocket from "ws";
1502
1617
  import axios from "axios";
1618
+
1619
+ // src/tunnel/webhook-config.ts
1620
+ import { SinchClient as SinchClient2 } from "@sinch/sdk-core";
1621
+ async function configureConversationWebhooks(tunnelUrl, config) {
1622
+ try {
1623
+ const conversationAppId = process.env.CONVERSATION_APP_ID;
1624
+ const projectId = process.env.PROJECT_ID;
1625
+ const keyId = process.env.KEY_ID;
1626
+ const keySecret = process.env.KEY_SECRET;
1627
+ if (!conversationAppId || !projectId || !keyId || !keySecret) {
1628
+ console.log("\u{1F4A1} Conversation API not fully configured - skipping webhook setup");
1629
+ return;
1630
+ }
1631
+ const webhookUrl = `${tunnelUrl}/conversation`;
1632
+ console.log(`\u{1F4AC} Conversation webhook URL: ${webhookUrl}`);
1633
+ const sinchClient = new SinchClient2({
1634
+ projectId,
1635
+ keyId,
1636
+ keySecret
1637
+ });
1638
+ const webhooksResult = await sinchClient.conversation.webhooks.list({ app_id: conversationAppId });
1639
+ const existingWebhooks = webhooksResult.webhooks || [];
1640
+ const tunnelWebhooks = existingWebhooks.filter(
1641
+ (w) => w.target?.includes("/api/ingress/")
1642
+ );
1643
+ for (const staleWebhook of tunnelWebhooks) {
1644
+ try {
1645
+ await sinchClient.conversation.webhooks.delete({ webhook_id: staleWebhook.id });
1646
+ console.log(`\u{1F9F9} Cleaned up stale tunnel webhook: ${staleWebhook.id}`);
1647
+ } catch (err) {
1648
+ }
1649
+ }
1650
+ const createResult = await sinchClient.conversation.webhooks.create({
1651
+ webhookCreateRequestBody: {
1652
+ app_id: conversationAppId,
1653
+ target: webhookUrl,
1654
+ target_type: "HTTP",
1655
+ triggers: ["MESSAGE_INBOUND"]
1656
+ }
1657
+ });
1658
+ config.conversationWebhookId = createResult.id;
1659
+ console.log(`\u2705 Created Conversation webhook: ${webhookUrl}`);
1660
+ console.log("\u{1F4AC} Send a message to your Conversation app to test!");
1661
+ } catch (error) {
1662
+ console.log("\u26A0\uFE0F Could not configure Conversation webhooks:", error.message);
1663
+ }
1664
+ }
1665
+ async function cleanupConversationWebhook(config) {
1666
+ if (!config.conversationWebhookId) return;
1667
+ try {
1668
+ const conversationAppId = process.env.CONVERSATION_APP_ID;
1669
+ const projectId = process.env.PROJECT_ID;
1670
+ const keyId = process.env.KEY_ID;
1671
+ const keySecret = process.env.KEY_SECRET;
1672
+ if (!conversationAppId || !projectId || !keyId || !keySecret) return;
1673
+ const sinchClient = new SinchClient2({
1674
+ projectId,
1675
+ keyId,
1676
+ keySecret
1677
+ });
1678
+ await sinchClient.conversation.webhooks.delete({ webhook_id: config.conversationWebhookId });
1679
+ console.log("\u{1F9F9} Cleaned up tunnel webhook");
1680
+ config.conversationWebhookId = void 0;
1681
+ } catch (error) {
1682
+ }
1683
+ }
1684
+ async function configureElevenLabs() {
1685
+ try {
1686
+ const agentId = process.env.ELEVENLABS_AGENT_ID;
1687
+ const apiKey = process.env.ELEVENLABS_API_KEY;
1688
+ if (!agentId || !apiKey) {
1689
+ console.log("\u{1F4A1} ElevenLabs not fully configured - skipping auto-configuration");
1690
+ return;
1691
+ }
1692
+ void apiKey;
1693
+ console.log("\u{1F916} ElevenLabs auto-configuration enabled");
1694
+ console.log(` Agent ID: ${agentId}`);
1695
+ } catch (error) {
1696
+ console.log("\u26A0\uFE0F Could not configure ElevenLabs:", error.message);
1697
+ }
1698
+ }
1699
+
1700
+ // src/tunnel/index.ts
1701
+ var TUNNEL_GATEWAY_DEFAULT = "https://tunnel.fn.sinch.com";
1503
1702
  var TunnelClient = class {
1504
1703
  ws = null;
1505
1704
  tunnelUrl = null;
1705
+ tunnelId = null;
1506
1706
  isConnected = false;
1507
1707
  reconnectAttempts = 0;
1508
1708
  maxReconnectAttempts = 10;
1509
1709
  reconnectDelay = 5e3;
1510
1710
  heartbeatInterval = null;
1511
1711
  localPort;
1712
+ webhookConfig = {};
1713
+ welcomeResolver = null;
1512
1714
  constructor(localPort = 3e3) {
1513
1715
  this.localPort = localPort;
1514
1716
  }
1717
+ getTunnelGatewayUrl() {
1718
+ const explicitUrl = process.env.TUNNEL_GATEWAY_URL;
1719
+ if (explicitUrl) {
1720
+ return explicitUrl;
1721
+ }
1722
+ return TUNNEL_GATEWAY_DEFAULT;
1723
+ }
1724
+ generateTunnelId() {
1725
+ const ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
1726
+ const timestamp = Date.now();
1727
+ let timestampPart = "";
1728
+ let t = timestamp;
1729
+ for (let i = 0; i < 10; i++) {
1730
+ timestampPart = ENCODING[t % 32] + timestampPart;
1731
+ t = Math.floor(t / 32);
1732
+ }
1733
+ const randomBytes = new Uint8Array(10);
1734
+ crypto.getRandomValues(randomBytes);
1735
+ let randomPart = "";
1736
+ for (let i = 0; i < 10; i++) {
1737
+ const byte = randomBytes[i];
1738
+ randomPart += ENCODING[byte >> 3];
1739
+ if (randomPart.length < 16) {
1740
+ randomPart += ENCODING[(byte & 7) << 2 | (i + 1 < 10 ? randomBytes[i + 1] >> 6 : 0)];
1741
+ }
1742
+ }
1743
+ randomPart = randomPart.substring(0, 16);
1744
+ return timestampPart + randomPart;
1745
+ }
1515
1746
  async connect() {
1516
1747
  if (process.env.SINCH_TUNNEL !== "true") {
1517
1748
  console.log("Tunnel is disabled (set SINCH_TUNNEL=true to enable)");
1518
1749
  return;
1519
1750
  }
1520
- const projectId = process.env.PROJECT_ID;
1521
- const apiKey = process.env.PROJECT_ID_API_KEY;
1522
- if (!projectId || !apiKey) {
1523
- console.warn("PROJECT_ID or PROJECT_ID_API_KEY not configured, tunnel cannot connect");
1524
- return;
1525
- }
1526
- const sessionId = `${projectId}-local-dev`;
1527
- const apiUrl = process.env.API_URL || "https://functions.api.sinch.com";
1528
- const baseUrl = new URL(apiUrl);
1529
- const wsUrl = new URL(baseUrl.toString());
1530
- wsUrl.protocol = baseUrl.protocol === "https:" ? "wss:" : "ws:";
1531
- wsUrl.pathname = `/api/tunnel/${projectId}/${sessionId}`;
1751
+ const gatewayUrl = this.getTunnelGatewayUrl();
1752
+ this.tunnelId = this.generateTunnelId();
1753
+ const gatewayUri = new URL(gatewayUrl);
1754
+ const wsUrl = new URL(gatewayUrl);
1755
+ wsUrl.protocol = gatewayUri.protocol === "https:" ? "wss:" : "ws:";
1756
+ wsUrl.pathname = "/ws";
1757
+ wsUrl.searchParams.set("tunnel", this.tunnelId);
1532
1758
  const tunnelEndpoint = wsUrl.toString();
1533
- const tunnelUrlObj = new URL(baseUrl.toString());
1534
- tunnelUrlObj.pathname = `/api/ingress/${sessionId}`;
1535
- this.tunnelUrl = tunnelUrlObj.toString();
1536
- console.log("Connecting to tunnel server...");
1759
+ console.log(`Connecting to tunnel gateway at ${tunnelEndpoint}...`);
1537
1760
  try {
1538
- this.ws = new WebSocket(tunnelEndpoint, {
1539
- headers: {
1540
- "Authorization": `Bearer ${apiKey}`
1541
- }
1761
+ this.ws = new WebSocket(tunnelEndpoint);
1762
+ const welcomePromise = new Promise((resolve, reject) => {
1763
+ this.welcomeResolver = resolve;
1764
+ setTimeout(() => reject(new Error("Timed out waiting for welcome message")), 1e4);
1542
1765
  });
1543
- this.ws.on("open", async () => {
1766
+ this.ws.on("open", () => {
1544
1767
  this.isConnected = true;
1545
1768
  this.reconnectAttempts = 0;
1546
- console.log("\u2705 Tunnel connected successfully!");
1547
- console.log(`\u{1F4CD} Tunnel URL: ${this.tunnelUrl}`);
1548
- console.log(`\u{1F4DE} Voice webhook URL: ${this.tunnelUrl}/`);
1549
- await this.displayTestPhoneNumbers();
1769
+ console.log("WebSocket connected, waiting for welcome message...");
1550
1770
  });
1551
1771
  this.ws.on("message", async (data) => {
1552
1772
  try {
@@ -1556,7 +1776,7 @@ var TunnelClient = class {
1556
1776
  console.error("Error processing tunnel message:", error);
1557
1777
  }
1558
1778
  });
1559
- this.ws.on("close", () => {
1779
+ this.ws.on("close", async () => {
1560
1780
  this.isConnected = false;
1561
1781
  console.log("Tunnel connection closed");
1562
1782
  this.stopHeartbeat();
@@ -1565,14 +1785,23 @@ var TunnelClient = class {
1565
1785
  this.ws.on("error", (error) => {
1566
1786
  console.error("Tunnel connection error:", error.message);
1567
1787
  });
1788
+ await welcomePromise;
1789
+ if (!this.tunnelUrl) {
1790
+ throw new Error("Did not receive tunnel URL from gateway");
1791
+ }
1792
+ console.log("Tunnel connected successfully!");
1568
1793
  this.startHeartbeat();
1794
+ await this.configureWebhooks();
1569
1795
  } catch (error) {
1570
- console.error("Failed to establish tunnel connection:", error);
1796
+ console.error("Failed to establish tunnel connection:", error.message);
1571
1797
  this.scheduleReconnect();
1572
1798
  }
1573
1799
  }
1574
1800
  async handleMessage(message) {
1575
1801
  switch (message.type) {
1802
+ case "welcome":
1803
+ this.handleWelcomeMessage(message);
1804
+ break;
1576
1805
  case "request":
1577
1806
  await this.handleRequest(message);
1578
1807
  break;
@@ -1581,6 +1810,15 @@ var TunnelClient = class {
1581
1810
  break;
1582
1811
  }
1583
1812
  }
1813
+ handleWelcomeMessage(message) {
1814
+ this.tunnelId = message.tunnelId || null;
1815
+ this.tunnelUrl = message.publicUrl || null;
1816
+ console.log(`Received welcome: tunnelId=${this.tunnelId}`);
1817
+ if (this.welcomeResolver) {
1818
+ this.welcomeResolver(true);
1819
+ this.welcomeResolver = null;
1820
+ }
1821
+ }
1584
1822
  async handleRequest(message) {
1585
1823
  console.log(`Forwarding ${message.method} request to ${message.path}`);
1586
1824
  try {
@@ -1597,12 +1835,23 @@ var TunnelClient = class {
1597
1835
  axiosConfig.data = message.body;
1598
1836
  }
1599
1837
  const response = await axios(axiosConfig);
1838
+ const headers = {};
1839
+ for (const [key, value] of Object.entries(response.headers)) {
1840
+ if (value) {
1841
+ const normalizedKey = key.toLowerCase() === "content-type" ? "Content-Type" : key.toLowerCase() === "content-length" ? "Content-Length" : key;
1842
+ headers[normalizedKey] = String(value);
1843
+ }
1844
+ }
1845
+ if (!headers["Content-Type"] && response.data) {
1846
+ headers["Content-Type"] = "application/json";
1847
+ }
1848
+ const body = typeof response.data === "string" ? response.data : JSON.stringify(response.data);
1600
1849
  const responseMessage = {
1601
1850
  type: "response",
1602
1851
  id: message.id,
1603
1852
  statusCode: response.status,
1604
- headers: response.headers,
1605
- body: typeof response.data === "string" ? response.data : JSON.stringify(response.data)
1853
+ headers,
1854
+ body
1606
1855
  };
1607
1856
  this.ws?.send(JSON.stringify(responseMessage));
1608
1857
  } catch (error) {
@@ -1637,7 +1886,29 @@ var TunnelClient = class {
1637
1886
  this.heartbeatInterval = null;
1638
1887
  }
1639
1888
  }
1640
- async displayTestPhoneNumbers() {
1889
+ /**
1890
+ * Configure all webhooks (Voice, Conversation, ElevenLabs)
1891
+ */
1892
+ async configureWebhooks() {
1893
+ const autoConfigVoice = process.env.AUTO_CONFIGURE_VOICE !== "false";
1894
+ const autoConfigConversation = process.env.AUTO_CONFIGURE_CONVERSATION !== "false";
1895
+ if (autoConfigVoice && process.env.VOICE_APPLICATION_KEY) {
1896
+ await this.configureVoiceWebhooks();
1897
+ }
1898
+ if (autoConfigConversation && process.env.CONVERSATION_APP_ID) {
1899
+ await configureConversationWebhooks(this.tunnelUrl, this.webhookConfig);
1900
+ }
1901
+ if (process.env.ELEVENLABS_AUTO_CONFIGURE === "true") {
1902
+ await configureElevenLabs();
1903
+ }
1904
+ }
1905
+ /**
1906
+ * Cleanup webhooks on disconnect
1907
+ */
1908
+ async cleanupWebhooks() {
1909
+ await cleanupConversationWebhook(this.webhookConfig);
1910
+ }
1911
+ async configureVoiceWebhooks() {
1641
1912
  try {
1642
1913
  const appKey = process.env.VOICE_APPLICATION_KEY;
1643
1914
  const appSecret = process.env.VOICE_APPLICATION_SECRET;
@@ -1659,7 +1930,7 @@ var TunnelClient = class {
1659
1930
  "Content-Type": "application/json"
1660
1931
  }
1661
1932
  });
1662
- console.log(`\u2705 Updated voice webhook URL to: ${this.tunnelUrl}`);
1933
+ console.log("\u2705 Updated voice webhook URL");
1663
1934
  } catch (error) {
1664
1935
  console.log("\u26A0\uFE0F Could not update webhook URL:", error.message);
1665
1936
  }
@@ -1700,8 +1971,9 @@ var TunnelClient = class {
1700
1971
  console.log(`Attempting to reconnect in ${delay / 1e3} seconds...`);
1701
1972
  setTimeout(() => this.connect(), delay);
1702
1973
  }
1703
- disconnect() {
1974
+ async disconnect() {
1704
1975
  this.stopHeartbeat();
1976
+ await this.cleanupWebhooks();
1705
1977
  if (this.ws) {
1706
1978
  this.ws.close();
1707
1979
  this.ws = null;