voice-router-dev 0.1.7 → 0.1.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/index.js CHANGED
@@ -43,9 +43,16 @@ __export(src_exports, {
43
43
  GladiaAdapter: () => GladiaAdapter,
44
44
  GladiaTypes: () => schema_exports,
45
45
  GladiaWebhookHandler: () => GladiaWebhookHandler,
46
+ ListenV1EncodingParameter: () => ListenV1EncodingParameter,
46
47
  OpenAIWhisperAdapter: () => OpenAIWhisperAdapter,
48
+ SpeakV1ContainerParameter: () => SpeakV1ContainerParameter,
49
+ SpeakV1EncodingParameter: () => SpeakV1EncodingParameter,
50
+ SpeakV1SampleRateParameter: () => SpeakV1SampleRateParameter,
47
51
  SpeechmaticsAdapter: () => SpeechmaticsAdapter,
48
52
  SpeechmaticsWebhookHandler: () => SpeechmaticsWebhookHandler,
53
+ StreamingSupportedBitDepthEnum: () => StreamingSupportedBitDepthEnum,
54
+ StreamingSupportedEncodingEnum: () => StreamingSupportedEncodingEnum,
55
+ StreamingSupportedSampleRateEnum: () => StreamingSupportedSampleRateEnum,
49
56
  VoiceRouter: () => VoiceRouter,
50
57
  WebhookRouter: () => WebhookRouter,
51
58
  createAssemblyAIAdapter: () => createAssemblyAIAdapter,
@@ -268,24 +275,165 @@ function createVoiceRouter(config, adapters) {
268
275
  return router;
269
276
  }
270
277
 
278
+ // src/generated/deepgram/schema/listenV1EncodingParameter.ts
279
+ var ListenV1EncodingParameter = {
280
+ linear16: "linear16",
281
+ flac: "flac",
282
+ mulaw: "mulaw",
283
+ opus: "opus",
284
+ speex: "speex",
285
+ g729: "g729"
286
+ };
287
+
288
+ // src/generated/deepgram/schema/speakV1EncodingParameter.ts
289
+ var SpeakV1EncodingParameter = {
290
+ linear16: "linear16",
291
+ aac: "aac",
292
+ opus: "opus",
293
+ mp3: "mp3",
294
+ flac: "flac",
295
+ mulaw: "mulaw",
296
+ alaw: "alaw"
297
+ };
298
+
299
+ // src/generated/deepgram/schema/speakV1ContainerParameter.ts
300
+ var SpeakV1ContainerParameter = {
301
+ none: "none",
302
+ wav: "wav",
303
+ ogg: "ogg"
304
+ };
305
+
306
+ // src/generated/deepgram/schema/speakV1SampleRateParameter.ts
307
+ var SpeakV1SampleRateParameter = {
308
+ NUMBER_16000: 16e3,
309
+ NUMBER_24000: 24e3,
310
+ NUMBER_32000: 32e3,
311
+ NUMBER_48000: 48e3,
312
+ null: null,
313
+ NUMBER_8000: 8e3,
314
+ NUMBER_22050: 22050
315
+ };
316
+
317
+ // src/generated/gladia/schema/streamingSupportedEncodingEnum.ts
318
+ var StreamingSupportedEncodingEnum = {
319
+ "wav/pcm": "wav/pcm",
320
+ "wav/alaw": "wav/alaw",
321
+ "wav/ulaw": "wav/ulaw"
322
+ };
323
+
324
+ // src/generated/gladia/schema/streamingSupportedSampleRateEnum.ts
325
+ var StreamingSupportedSampleRateEnum = {
326
+ NUMBER_8000: 8e3,
327
+ NUMBER_16000: 16e3,
328
+ NUMBER_32000: 32e3,
329
+ NUMBER_44100: 44100,
330
+ NUMBER_48000: 48e3
331
+ };
332
+
333
+ // src/generated/gladia/schema/streamingSupportedBitDepthEnum.ts
334
+ var StreamingSupportedBitDepthEnum = {
335
+ NUMBER_8: 8,
336
+ NUMBER_16: 16,
337
+ NUMBER_24: 24,
338
+ NUMBER_32: 32
339
+ };
340
+
341
+ // src/constants/defaults.ts
342
+ var DEFAULT_TIMEOUTS = {
343
+ /** Standard HTTP request timeout for API calls (60 seconds) */
344
+ HTTP_REQUEST: 6e4,
345
+ /** Audio processing timeout for long audio files (120 seconds) */
346
+ AUDIO_PROCESSING: 12e4,
347
+ /** WebSocket connection establishment timeout (10 seconds) */
348
+ WS_CONNECTION: 1e4,
349
+ /** WebSocket graceful close timeout (5 seconds) */
350
+ WS_CLOSE: 5e3
351
+ };
352
+ var DEFAULT_POLLING = {
353
+ /** Maximum number of polling attempts before timing out */
354
+ MAX_ATTEMPTS: 60,
355
+ /** Standard interval between polling attempts (2 seconds) */
356
+ INTERVAL_MS: 2e3,
357
+ /** Slower interval for long-running jobs (3 seconds) */
358
+ SLOW_INTERVAL_MS: 3e3
359
+ };
360
+
361
+ // src/utils/errors.ts
362
+ var ERROR_CODES = {
363
+ /** Failed to parse API response or WebSocket message */
364
+ PARSE_ERROR: "PARSE_ERROR",
365
+ /** WebSocket connection error */
366
+ WEBSOCKET_ERROR: "WEBSOCKET_ERROR",
367
+ /** Async transcription job did not complete within timeout */
368
+ POLLING_TIMEOUT: "POLLING_TIMEOUT",
369
+ /** Transcription processing failed on provider side */
370
+ TRANSCRIPTION_ERROR: "TRANSCRIPTION_ERROR",
371
+ /** Connection attempt timed out */
372
+ CONNECTION_TIMEOUT: "CONNECTION_TIMEOUT",
373
+ /** Invalid input provided to API */
374
+ INVALID_INPUT: "INVALID_INPUT",
375
+ /** Requested operation not supported by provider */
376
+ NOT_SUPPORTED: "NOT_SUPPORTED",
377
+ /** No transcription results available */
378
+ NO_RESULTS: "NO_RESULTS",
379
+ /** Unspecified or unknown error */
380
+ UNKNOWN_ERROR: "UNKNOWN_ERROR"
381
+ };
382
+ var ERROR_MESSAGES = {
383
+ PARSE_ERROR: "Failed to parse response data",
384
+ WEBSOCKET_ERROR: "WebSocket connection error",
385
+ POLLING_TIMEOUT: "Transcription did not complete within timeout period",
386
+ TRANSCRIPTION_ERROR: "Transcription processing failed",
387
+ CONNECTION_TIMEOUT: "Connection attempt timed out",
388
+ INVALID_INPUT: "Invalid input provided",
389
+ NOT_SUPPORTED: "Operation not supported by this provider",
390
+ NO_RESULTS: "No transcription results available",
391
+ UNKNOWN_ERROR: "An unknown error occurred"
392
+ };
393
+ function createError(code, customMessage, details) {
394
+ return {
395
+ code,
396
+ message: customMessage || ERROR_MESSAGES[code],
397
+ details
398
+ };
399
+ }
400
+
271
401
  // src/adapters/base-adapter.ts
272
402
  var BaseAdapter = class {
273
403
  initialize(config) {
274
404
  this.config = config;
275
405
  }
276
406
  /**
277
- * Helper method to create error responses
407
+ * Helper method to create error responses with stack traces
408
+ *
409
+ * @param error - Error object or unknown error
410
+ * @param statusCode - Optional HTTP status code
411
+ * @param code - Optional error code (defaults to extracted or UNKNOWN_ERROR)
278
412
  */
279
- createErrorResponse(error, statusCode) {
413
+ createErrorResponse(error, statusCode, code) {
280
414
  const err = error;
415
+ const httpStatus = statusCode || err.statusCode || err.response?.status;
416
+ const httpStatusText = err.response?.statusText;
417
+ const responseData = err.response?.data;
281
418
  return {
282
419
  success: false,
283
420
  provider: this.name,
284
421
  error: {
285
- code: err.code || "UNKNOWN_ERROR",
422
+ code: code || err.code || ERROR_CODES.UNKNOWN_ERROR,
286
423
  message: err.message || "An unknown error occurred",
287
- statusCode: statusCode || err.statusCode,
288
- details: error
424
+ statusCode: httpStatus,
425
+ details: {
426
+ // Include full error object
427
+ error,
428
+ // Include stack trace if available
429
+ stack: err.stack,
430
+ // Include HTTP response details
431
+ httpStatus,
432
+ httpStatusText,
433
+ responseData,
434
+ // Include provider name for debugging
435
+ provider: this.name
436
+ }
289
437
  }
290
438
  };
291
439
  }
@@ -300,6 +448,64 @@ var BaseAdapter = class {
300
448
  throw new Error(`API key is required for ${this.name} provider`);
301
449
  }
302
450
  }
451
+ /**
452
+ * Build axios config for generated API client functions
453
+ *
454
+ * @param authHeaderName - Header name for API key (e.g., "Authorization", "x-gladia-key")
455
+ * @param authHeaderValue - Optional function to format auth header value (defaults to raw API key)
456
+ * @returns Axios config object
457
+ */
458
+ getAxiosConfig(authHeaderName = "Authorization", authHeaderValue) {
459
+ this.validateConfig();
460
+ const authValue = authHeaderValue ? authHeaderValue(this.config.apiKey) : this.config.apiKey;
461
+ return {
462
+ baseURL: this.config.baseUrl || this.baseUrl,
463
+ timeout: this.config.timeout || DEFAULT_TIMEOUTS.HTTP_REQUEST,
464
+ headers: {
465
+ [authHeaderName]: authValue,
466
+ "Content-Type": "application/json",
467
+ ...this.config.headers
468
+ }
469
+ };
470
+ }
471
+ /**
472
+ * Generic polling helper for async transcription jobs
473
+ *
474
+ * Polls getTranscript() until job completes or times out.
475
+ *
476
+ * @param transcriptId - Job/transcript ID to poll
477
+ * @param options - Polling configuration
478
+ * @returns Final transcription result
479
+ */
480
+ async pollForCompletion(transcriptId, options) {
481
+ const { maxAttempts = DEFAULT_POLLING.MAX_ATTEMPTS, intervalMs = DEFAULT_POLLING.INTERVAL_MS } = options || {};
482
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
483
+ const result = await this.getTranscript(transcriptId);
484
+ if (!result.success) {
485
+ return result;
486
+ }
487
+ const status = result.data?.status;
488
+ if (status === "completed") {
489
+ return result;
490
+ }
491
+ if (status === "error") {
492
+ return this.createErrorResponse(
493
+ new Error("Transcription failed"),
494
+ void 0,
495
+ ERROR_CODES.TRANSCRIPTION_ERROR
496
+ );
497
+ }
498
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
499
+ }
500
+ return {
501
+ success: false,
502
+ provider: this.name,
503
+ error: {
504
+ code: ERROR_CODES.POLLING_TIMEOUT,
505
+ message: `Transcription did not complete after ${maxAttempts} attempts`
506
+ }
507
+ };
508
+ }
303
509
  };
304
510
 
305
511
  // src/adapters/gladia-adapter.ts
@@ -346,6 +552,143 @@ function mapEncodingToProvider(unifiedEncoding, provider) {
346
552
  return providerEncoding;
347
553
  }
348
554
 
555
+ // src/utils/websocket-helpers.ts
556
+ function waitForWebSocketOpen(ws, timeoutMs = DEFAULT_TIMEOUTS.WS_CONNECTION) {
557
+ return new Promise((resolve, reject) => {
558
+ const timeout = setTimeout(() => {
559
+ reject(new Error("WebSocket connection timeout"));
560
+ }, timeoutMs);
561
+ ws.once("open", () => {
562
+ clearTimeout(timeout);
563
+ resolve();
564
+ });
565
+ ws.once("error", (error) => {
566
+ clearTimeout(timeout);
567
+ reject(error);
568
+ });
569
+ });
570
+ }
571
+ function closeWebSocket(ws, timeoutMs = DEFAULT_TIMEOUTS.WS_CLOSE) {
572
+ return new Promise((resolve) => {
573
+ const timeout = setTimeout(() => {
574
+ ws.terminate();
575
+ resolve();
576
+ }, timeoutMs);
577
+ ws.close();
578
+ ws.once("close", () => {
579
+ clearTimeout(timeout);
580
+ resolve();
581
+ });
582
+ });
583
+ }
584
+ function setupWebSocketHandlers(ws, callbacks, setSessionStatus) {
585
+ ws.on("open", () => {
586
+ setSessionStatus("open");
587
+ callbacks?.onOpen?.();
588
+ });
589
+ ws.on("error", (error) => {
590
+ callbacks?.onError?.(createError(ERROR_CODES.WEBSOCKET_ERROR, error.message, error));
591
+ });
592
+ ws.on("close", (code, reason) => {
593
+ setSessionStatus("closed");
594
+ callbacks?.onClose?.(code, reason.toString());
595
+ });
596
+ }
597
+ function validateSessionForAudio(sessionStatus, wsReadyState, WebSocketOpen) {
598
+ if (sessionStatus !== "open") {
599
+ throw new Error(`Cannot send audio: session is ${sessionStatus}`);
600
+ }
601
+ if (wsReadyState !== WebSocketOpen) {
602
+ throw new Error("WebSocket is not open");
603
+ }
604
+ }
605
+
606
+ // src/utils/validation.ts
607
+ function validateEnumValue(value, enumType, fieldName, provider) {
608
+ const validValues = Object.values(enumType);
609
+ const isValid = validValues.some((v) => v === value);
610
+ if (!isValid) {
611
+ throw new Error(
612
+ `${provider} does not support ${fieldName} '${value}'. Supported values (from OpenAPI spec): ${validValues.join(", ")}`
613
+ );
614
+ }
615
+ return value;
616
+ }
617
+
618
+ // src/utils/transcription-helpers.ts
619
+ function extractSpeakersFromUtterances(utterances, getSpeakerId, formatLabel) {
620
+ if (!utterances || utterances.length === 0) {
621
+ return void 0;
622
+ }
623
+ const speakerSet = /* @__PURE__ */ new Set();
624
+ utterances.forEach((utterance) => {
625
+ const speakerId = getSpeakerId(utterance);
626
+ if (speakerId !== void 0) {
627
+ speakerSet.add(String(speakerId));
628
+ }
629
+ });
630
+ if (speakerSet.size === 0) {
631
+ return void 0;
632
+ }
633
+ return Array.from(speakerSet).map((speakerId) => ({
634
+ id: speakerId,
635
+ label: formatLabel ? formatLabel(speakerId) : `Speaker ${speakerId}`
636
+ }));
637
+ }
638
+ function extractWords(words, mapper) {
639
+ if (!words || words.length === 0) {
640
+ return void 0;
641
+ }
642
+ const normalizedWords = words.map(mapper);
643
+ return normalizedWords.length > 0 ? normalizedWords : void 0;
644
+ }
645
+ var STATUS_MAPPINGS = {
646
+ gladia: {
647
+ queued: "queued",
648
+ processing: "processing",
649
+ done: "completed",
650
+ error: "error"
651
+ },
652
+ assemblyai: {
653
+ queued: "queued",
654
+ processing: "processing",
655
+ completed: "completed",
656
+ error: "error"
657
+ },
658
+ deepgram: {
659
+ queued: "queued",
660
+ processing: "processing",
661
+ completed: "completed",
662
+ error: "error"
663
+ },
664
+ azure: {
665
+ succeeded: "completed",
666
+ running: "processing",
667
+ notstarted: "queued",
668
+ failed: "error"
669
+ },
670
+ speechmatics: {
671
+ running: "processing",
672
+ done: "completed",
673
+ rejected: "error",
674
+ expired: "error"
675
+ }
676
+ };
677
+ function normalizeStatus(providerStatus, provider, defaultStatus = "queued") {
678
+ if (!providerStatus) return defaultStatus;
679
+ const mapping = STATUS_MAPPINGS[provider];
680
+ const statusKey = providerStatus.toString().toLowerCase();
681
+ if (statusKey in mapping) {
682
+ return mapping[statusKey];
683
+ }
684
+ for (const [key, value] of Object.entries(mapping)) {
685
+ if (statusKey.includes(key)) {
686
+ return value;
687
+ }
688
+ }
689
+ return defaultStatus;
690
+ }
691
+
349
692
  // src/generated/gladia/api/gladiaControlAPI.ts
350
693
  var import_axios = __toESM(require("axios"));
351
694
 
@@ -882,21 +1225,6 @@ var StreamingResponseStatus = {
882
1225
  error: "error"
883
1226
  };
884
1227
 
885
- // src/generated/gladia/schema/streamingSupportedBitDepthEnum.ts
886
- var StreamingSupportedBitDepthEnum = {
887
- NUMBER_8: 8,
888
- NUMBER_16: 16,
889
- NUMBER_24: 24,
890
- NUMBER_32: 32
891
- };
892
-
893
- // src/generated/gladia/schema/streamingSupportedEncodingEnum.ts
894
- var StreamingSupportedEncodingEnum = {
895
- "wav/pcm": "wav/pcm",
896
- "wav/alaw": "wav/alaw",
897
- "wav/ulaw": "wav/ulaw"
898
- };
899
-
900
1228
  // src/generated/gladia/schema/streamingSupportedModels.ts
901
1229
  var StreamingSupportedModels = {
902
1230
  "solaria-1": "solaria-1"
@@ -908,15 +1236,6 @@ var StreamingSupportedRegions = {
908
1236
  "eu-west": "eu-west"
909
1237
  };
910
1238
 
911
- // src/generated/gladia/schema/streamingSupportedSampleRateEnum.ts
912
- var StreamingSupportedSampleRateEnum = {
913
- NUMBER_8000: 8e3,
914
- NUMBER_16000: 16e3,
915
- NUMBER_32000: 32e3,
916
- NUMBER_44100: 44100,
917
- NUMBER_48000: 48e3
918
- };
919
-
920
1239
  // src/generated/gladia/schema/subtitlesFormatEnum.ts
921
1240
  var SubtitlesFormatEnum = {
922
1241
  srt: "srt",
@@ -1466,21 +1785,10 @@ var GladiaAdapter = class extends BaseAdapter {
1466
1785
  }
1467
1786
  /**
1468
1787
  * Get axios config for generated API client functions
1469
- * Configures headers and base URL
1788
+ * Configures headers and base URL using Gladia's x-gladia-key header
1470
1789
  */
1471
1790
  getAxiosConfig() {
1472
- if (!this.config) {
1473
- throw new Error("Adapter not initialized. Call initialize() first.");
1474
- }
1475
- return {
1476
- baseURL: this.config.baseUrl || this.baseUrl,
1477
- timeout: this.config.timeout || 6e4,
1478
- headers: {
1479
- "x-gladia-key": this.config.apiKey,
1480
- "Content-Type": "application/json",
1481
- ...this.config.headers
1482
- }
1483
- };
1791
+ return super.getAxiosConfig("x-gladia-key");
1484
1792
  }
1485
1793
  /**
1486
1794
  * Submit audio for transcription
@@ -1647,29 +1955,13 @@ var GladiaAdapter = class extends BaseAdapter {
1647
1955
  * Normalize Gladia response to unified format
1648
1956
  */
1649
1957
  normalizeResponse(response) {
1650
- let status;
1651
- switch (response.status) {
1652
- case "queued":
1653
- status = "queued";
1654
- break;
1655
- case "processing":
1656
- status = "processing";
1657
- break;
1658
- case "done":
1659
- status = "completed";
1660
- break;
1661
- case "error":
1662
- status = "error";
1663
- break;
1664
- default:
1665
- status = "queued";
1666
- }
1958
+ const status = normalizeStatus(response.status, "gladia");
1667
1959
  if (response.status === "error") {
1668
1960
  return {
1669
1961
  success: false,
1670
1962
  provider: this.name,
1671
1963
  error: {
1672
- code: response.error_code?.toString() || "TRANSCRIPTION_ERROR",
1964
+ code: response.error_code?.toString() || ERROR_CODES.TRANSCRIPTION_ERROR,
1673
1965
  message: "Transcription failed",
1674
1966
  statusCode: response.error_code || void 0
1675
1967
  },
@@ -1709,22 +2001,11 @@ var GladiaAdapter = class extends BaseAdapter {
1709
2001
  * Extract speaker information from Gladia response
1710
2002
  */
1711
2003
  extractSpeakers(transcription) {
1712
- if (!transcription?.utterances) {
1713
- return void 0;
1714
- }
1715
- const speakerSet = /* @__PURE__ */ new Set();
1716
- transcription.utterances.forEach((utterance) => {
1717
- if (utterance.speaker !== void 0) {
1718
- speakerSet.add(utterance.speaker);
1719
- }
1720
- });
1721
- if (speakerSet.size === 0) {
1722
- return void 0;
1723
- }
1724
- return Array.from(speakerSet).map((speakerId) => ({
1725
- id: speakerId.toString(),
1726
- label: `Speaker ${speakerId}`
1727
- }));
2004
+ return extractSpeakersFromUtterances(
2005
+ transcription?.utterances,
2006
+ (utterance) => utterance.speaker,
2007
+ (id) => `Speaker ${id}`
2008
+ );
1728
2009
  }
1729
2010
  /**
1730
2011
  * Extract word timestamps from Gladia response
@@ -1735,14 +2016,17 @@ var GladiaAdapter = class extends BaseAdapter {
1735
2016
  }
1736
2017
  const allWords = transcription.utterances.flatMap(
1737
2018
  (utterance) => utterance.words.map((word) => ({
1738
- text: word.word,
1739
- start: word.start,
1740
- end: word.end,
1741
- confidence: word.confidence,
1742
- speaker: utterance.speaker?.toString()
2019
+ word,
2020
+ speaker: utterance.speaker
1743
2021
  }))
1744
2022
  );
1745
- return allWords.length > 0 ? allWords : void 0;
2023
+ return extractWords(allWords, (item) => ({
2024
+ text: item.word.word,
2025
+ start: item.word.start,
2026
+ end: item.word.end,
2027
+ confidence: item.word.confidence,
2028
+ speaker: item.speaker?.toString()
2029
+ }));
1746
2030
  }
1747
2031
  /**
1748
2032
  * Extract utterances from Gladia response
@@ -1768,38 +2052,6 @@ var GladiaAdapter = class extends BaseAdapter {
1768
2052
  /**
1769
2053
  * Poll for transcription completion
1770
2054
  */
1771
- async pollForCompletion(jobId, maxAttempts = 60, intervalMs = 2e3) {
1772
- for (let attempt = 0; attempt < maxAttempts; attempt++) {
1773
- const result = await this.getTranscript(jobId);
1774
- if (!result.success) {
1775
- return result;
1776
- }
1777
- const status = result.data?.status;
1778
- if (status === "completed") {
1779
- return result;
1780
- }
1781
- if (status === "error") {
1782
- return {
1783
- success: false,
1784
- provider: this.name,
1785
- error: {
1786
- code: "TRANSCRIPTION_ERROR",
1787
- message: "Transcription failed"
1788
- },
1789
- raw: result.raw
1790
- };
1791
- }
1792
- await new Promise((resolve) => setTimeout(resolve, intervalMs));
1793
- }
1794
- return {
1795
- success: false,
1796
- provider: this.name,
1797
- error: {
1798
- code: "POLLING_TIMEOUT",
1799
- message: `Transcription did not complete after ${maxAttempts} attempts`
1800
- }
1801
- };
1802
- }
1803
2055
  /**
1804
2056
  * Stream audio for real-time transcription
1805
2057
  *
@@ -1843,14 +2095,12 @@ var GladiaAdapter = class extends BaseAdapter {
1843
2095
  this.validateConfig();
1844
2096
  let validatedSampleRate;
1845
2097
  if (options?.sampleRate) {
1846
- const validRates = Object.values(StreamingSupportedSampleRateEnum);
1847
- const isValidRate = validRates.some((rate) => rate === options.sampleRate);
1848
- if (!isValidRate) {
1849
- throw new Error(
1850
- `Gladia does not support sample rate ${options.sampleRate} Hz. Supported rates (from OpenAPI spec): ${validRates.join(", ")} Hz`
1851
- );
1852
- }
1853
- validatedSampleRate = options.sampleRate;
2098
+ validatedSampleRate = validateEnumValue(
2099
+ options.sampleRate,
2100
+ StreamingSupportedSampleRateEnum,
2101
+ "sample rate",
2102
+ "Gladia"
2103
+ );
1854
2104
  }
1855
2105
  const streamingRequest = {
1856
2106
  encoding: options?.encoding ? mapEncodingToProvider(options.encoding, "gladia") : void 0,
@@ -1872,9 +2122,8 @@ var GladiaAdapter = class extends BaseAdapter {
1872
2122
  const { id, url: wsUrl } = initResponse.data;
1873
2123
  const ws = new import_ws.default(wsUrl);
1874
2124
  let sessionStatus = "connecting";
1875
- ws.on("open", () => {
1876
- sessionStatus = "open";
1877
- callbacks?.onOpen?.();
2125
+ setupWebSocketHandlers(ws, callbacks, (status) => {
2126
+ sessionStatus = status;
1878
2127
  });
1879
2128
  ws.on("message", (data) => {
1880
2129
  try {
@@ -1919,48 +2168,20 @@ var GladiaAdapter = class extends BaseAdapter {
1919
2168
  }
1920
2169
  } catch (error) {
1921
2170
  callbacks?.onError?.({
1922
- code: "PARSE_ERROR",
2171
+ code: ERROR_CODES.PARSE_ERROR,
1923
2172
  message: "Failed to parse WebSocket message",
1924
2173
  details: error
1925
2174
  });
1926
2175
  }
1927
2176
  });
1928
- ws.on("error", (error) => {
1929
- callbacks?.onError?.({
1930
- code: "WEBSOCKET_ERROR",
1931
- message: error.message,
1932
- details: error
1933
- });
1934
- });
1935
- ws.on("close", (code, reason) => {
1936
- sessionStatus = "closed";
1937
- callbacks?.onClose?.(code, reason.toString());
1938
- });
1939
- await new Promise((resolve, reject) => {
1940
- const timeout = setTimeout(() => {
1941
- reject(new Error("WebSocket connection timeout"));
1942
- }, 1e4);
1943
- ws.once("open", () => {
1944
- clearTimeout(timeout);
1945
- resolve();
1946
- });
1947
- ws.once("error", (error) => {
1948
- clearTimeout(timeout);
1949
- reject(error);
1950
- });
1951
- });
2177
+ await waitForWebSocketOpen(ws);
1952
2178
  return {
1953
2179
  id,
1954
2180
  provider: this.name,
1955
2181
  createdAt: /* @__PURE__ */ new Date(),
1956
2182
  getStatus: () => sessionStatus,
1957
2183
  sendAudio: async (chunk) => {
1958
- if (sessionStatus !== "open") {
1959
- throw new Error(`Cannot send audio: session is ${sessionStatus}`);
1960
- }
1961
- if (ws.readyState !== import_ws.default.OPEN) {
1962
- throw new Error("WebSocket is not open");
1963
- }
2184
+ validateSessionForAudio(sessionStatus, ws.readyState, import_ws.default.OPEN);
1964
2185
  ws.send(chunk.data);
1965
2186
  if (chunk.isLast) {
1966
2187
  ws.send(
@@ -1982,18 +2203,8 @@ var GladiaAdapter = class extends BaseAdapter {
1982
2203
  })
1983
2204
  );
1984
2205
  }
1985
- return new Promise((resolve) => {
1986
- const timeout = setTimeout(() => {
1987
- ws.terminate();
1988
- resolve();
1989
- }, 5e3);
1990
- ws.close();
1991
- ws.once("close", () => {
1992
- clearTimeout(timeout);
1993
- sessionStatus = "closed";
1994
- resolve();
1995
- });
1996
- });
2206
+ await closeWebSocket(ws);
2207
+ sessionStatus = "closed";
1997
2208
  }
1998
2209
  };
1999
2210
  }
@@ -2327,9 +2538,6 @@ var createTranscript = (transcriptParams, options) => {
2327
2538
  var getTranscript = (transcriptId, options) => {
2328
2539
  return import_axios2.default.get(`/v2/transcript/${transcriptId}`, options);
2329
2540
  };
2330
- var createTemporaryToken = (createRealtimeTemporaryTokenParams, options) => {
2331
- return import_axios2.default.post("/v2/realtime/token", createRealtimeTemporaryTokenParams, options);
2332
- };
2333
2541
 
2334
2542
  // src/adapters/assemblyai-adapter.ts
2335
2543
  var AssemblyAIAdapter = class extends BaseAdapter {
@@ -2347,26 +2555,17 @@ var AssemblyAIAdapter = class extends BaseAdapter {
2347
2555
  entityDetection: true,
2348
2556
  piiRedaction: true
2349
2557
  };
2350
- this.baseUrl = "https://api.assemblyai.com/v2";
2351
- this.wsBaseUrl = "wss://api.assemblyai.com/v2/realtime/ws";
2558
+ this.baseUrl = "https://api.assemblyai.com";
2559
+ // Generated functions already include /v2 path
2560
+ this.wsBaseUrl = "wss://streaming.assemblyai.com/v3/ws";
2352
2561
  }
2562
+ // v3 Universal Streaming endpoint
2353
2563
  /**
2354
2564
  * Get axios config for generated API client functions
2355
- * Configures headers and base URL
2565
+ * Configures headers and base URL using authorization header
2356
2566
  */
2357
2567
  getAxiosConfig() {
2358
- if (!this.config) {
2359
- throw new Error("Adapter not initialized. Call initialize() first.");
2360
- }
2361
- return {
2362
- baseURL: this.config.baseUrl || this.baseUrl,
2363
- timeout: this.config.timeout || 6e4,
2364
- headers: {
2365
- authorization: this.config.apiKey,
2366
- "Content-Type": "application/json",
2367
- ...this.config.headers
2368
- }
2369
- };
2568
+ return super.getAxiosConfig("authorization");
2370
2569
  }
2371
2570
  /**
2372
2571
  * Submit audio for transcription
@@ -2644,41 +2843,6 @@ var AssemblyAIAdapter = class extends BaseAdapter {
2644
2843
  }))
2645
2844
  }));
2646
2845
  }
2647
- /**
2648
- * Poll for transcription completion
2649
- */
2650
- async pollForCompletion(transcriptId, maxAttempts = 60, intervalMs = 3e3) {
2651
- for (let attempt = 0; attempt < maxAttempts; attempt++) {
2652
- const result = await this.getTranscript(transcriptId);
2653
- if (!result.success) {
2654
- return result;
2655
- }
2656
- const status = result.data?.status;
2657
- if (status === "completed") {
2658
- return result;
2659
- }
2660
- if (status === "error") {
2661
- return {
2662
- success: false,
2663
- provider: this.name,
2664
- error: {
2665
- code: "TRANSCRIPTION_ERROR",
2666
- message: "Transcription failed"
2667
- },
2668
- raw: result.raw
2669
- };
2670
- }
2671
- await new Promise((resolve) => setTimeout(resolve, intervalMs));
2672
- }
2673
- return {
2674
- success: false,
2675
- provider: this.name,
2676
- error: {
2677
- code: "POLLING_TIMEOUT",
2678
- message: `Transcription did not complete after ${maxAttempts} attempts`
2679
- }
2680
- };
2681
- }
2682
2846
  /**
2683
2847
  * Stream audio for real-time transcription
2684
2848
  *
@@ -2719,14 +2883,17 @@ var AssemblyAIAdapter = class extends BaseAdapter {
2719
2883
  */
2720
2884
  async transcribeStream(options, callbacks) {
2721
2885
  this.validateConfig();
2722
- const tokenResponse = await createTemporaryToken(
2723
- { expires_in: 3600 },
2724
- // Token expires in 1 hour
2725
- this.getAxiosConfig()
2726
- );
2727
- const token = tokenResponse.data.token;
2728
- const wsUrl = `${this.wsBaseUrl}?sample_rate=${options?.sampleRate || 16e3}&token=${token}`;
2729
- const ws = new import_ws2.default(wsUrl);
2886
+ if (!this.config?.apiKey) {
2887
+ throw new Error("API key is required for streaming");
2888
+ }
2889
+ const sampleRate = options?.sampleRate || 16e3;
2890
+ const encoding = options?.encoding || "pcm_s16le";
2891
+ const wsUrl = `${this.wsBaseUrl}?sample_rate=${sampleRate}&encoding=${encoding}`;
2892
+ const ws = new import_ws2.default(wsUrl, {
2893
+ headers: {
2894
+ Authorization: this.config.apiKey
2895
+ }
2896
+ });
2730
2897
  let sessionStatus = "connecting";
2731
2898
  const sessionId = `assemblyai-${Date.now()}-${Math.random().toString(36).substring(7)}`;
2732
2899
  ws.on("open", () => {
@@ -2736,41 +2903,42 @@ var AssemblyAIAdapter = class extends BaseAdapter {
2736
2903
  ws.on("message", (data) => {
2737
2904
  try {
2738
2905
  const message = JSON.parse(data.toString());
2739
- if (message.message_type === "SessionBegins") {
2906
+ if ("error" in message) {
2907
+ callbacks?.onError?.({
2908
+ code: "API_ERROR",
2909
+ message: message.error
2910
+ });
2911
+ return;
2912
+ }
2913
+ if (message.type === "Begin") {
2914
+ const beginMsg = message;
2740
2915
  callbacks?.onMetadata?.({
2741
- sessionId: message.session_id,
2742
- expiresAt: message.expires_at
2916
+ sessionId: beginMsg.id,
2917
+ expiresAt: new Date(beginMsg.expires_at).toISOString()
2743
2918
  });
2744
- } else if (message.message_type === "PartialTranscript") {
2919
+ } else if (message.type === "Turn") {
2920
+ const turnMsg = message;
2745
2921
  callbacks?.onTranscript?.({
2746
2922
  type: "transcript",
2747
- text: message.text,
2748
- isFinal: false,
2749
- confidence: message.confidence,
2750
- words: message.words.map((word) => ({
2923
+ text: turnMsg.transcript,
2924
+ isFinal: turnMsg.end_of_turn,
2925
+ confidence: turnMsg.end_of_turn_confidence,
2926
+ words: turnMsg.words.map((word) => ({
2751
2927
  text: word.text,
2752
2928
  start: word.start / 1e3,
2929
+ // Convert ms to seconds
2753
2930
  end: word.end / 1e3,
2754
2931
  confidence: word.confidence
2755
2932
  })),
2756
- data: message
2933
+ data: turnMsg
2757
2934
  });
2758
- } else if (message.message_type === "FinalTranscript") {
2759
- callbacks?.onTranscript?.({
2760
- type: "transcript",
2761
- text: message.text,
2762
- isFinal: true,
2763
- confidence: message.confidence,
2764
- words: message.words.map((word) => ({
2765
- text: word.text,
2766
- start: word.start / 1e3,
2767
- end: word.end / 1e3,
2768
- confidence: word.confidence
2769
- })),
2770
- data: message
2935
+ } else if (message.type === "Termination") {
2936
+ const termMsg = message;
2937
+ callbacks?.onMetadata?.({
2938
+ terminated: true,
2939
+ audioDurationSeconds: termMsg.audio_duration_seconds,
2940
+ sessionDurationSeconds: termMsg.session_duration_seconds
2771
2941
  });
2772
- } else if (message.message_type === "SessionTerminated") {
2773
- callbacks?.onMetadata?.({ terminated: true });
2774
2942
  }
2775
2943
  } catch (error) {
2776
2944
  callbacks?.onError?.({
@@ -3320,7 +3488,24 @@ function createDeepgramAdapter(config) {
3320
3488
  }
3321
3489
 
3322
3490
  // src/adapters/azure-stt-adapter.ts
3491
+ var import_axios5 = __toESM(require("axios"));
3492
+
3493
+ // src/generated/azure/api/speechServicesAPIV31.ts
3323
3494
  var import_axios4 = __toESM(require("axios"));
3495
+ var transcriptionsCreate = (transcription, options) => {
3496
+ return import_axios4.default.post("/transcriptions", transcription, options);
3497
+ };
3498
+ var transcriptionsGet = (id, options) => {
3499
+ return import_axios4.default.get(`/transcriptions/${id}`, options);
3500
+ };
3501
+ var transcriptionsListFiles = (id, params, options) => {
3502
+ return import_axios4.default.get(`/transcriptions/${id}/files`, {
3503
+ ...options,
3504
+ params: { ...params, ...options?.params }
3505
+ });
3506
+ };
3507
+
3508
+ // src/adapters/azure-stt-adapter.ts
3324
3509
  var AzureSTTAdapter = class extends BaseAdapter {
3325
3510
  constructor() {
3326
3511
  super(...arguments);
@@ -3337,20 +3522,20 @@ var AzureSTTAdapter = class extends BaseAdapter {
3337
3522
  entityDetection: false,
3338
3523
  piiRedaction: false
3339
3524
  };
3525
+ this.baseUrl = "https://eastus.api.cognitive.microsoft.com/speechtotext/v3.1";
3340
3526
  }
3527
+ // Default, overridden in initialize()
3341
3528
  initialize(config) {
3342
3529
  super.initialize(config);
3343
3530
  this.region = config.region || "eastus";
3344
3531
  this.baseUrl = config.baseUrl || `https://${this.region}.api.cognitive.microsoft.com/speechtotext/v3.1`;
3345
- this.client = import_axios4.default.create({
3346
- baseURL: this.baseUrl,
3347
- timeout: config.timeout || 6e4,
3348
- headers: {
3349
- "Ocp-Apim-Subscription-Key": config.apiKey,
3350
- "Content-Type": "application/json",
3351
- ...config.headers
3352
- }
3353
- });
3532
+ }
3533
+ /**
3534
+ * Get axios config for generated API client functions
3535
+ * Configures headers and base URL using Azure subscription key
3536
+ */
3537
+ getAxiosConfig() {
3538
+ return super.getAxiosConfig("Ocp-Apim-Subscription-Key");
3354
3539
  }
3355
3540
  /**
3356
3541
  * Submit audio for transcription
@@ -3382,9 +3567,9 @@ var AzureSTTAdapter = class extends BaseAdapter {
3382
3567
  contentUrls: [audio.url],
3383
3568
  properties: this.buildTranscriptionProperties(options)
3384
3569
  };
3385
- const response = await this.client.post(
3386
- "/transcriptions",
3387
- transcriptionRequest
3570
+ const response = await transcriptionsCreate(
3571
+ transcriptionRequest,
3572
+ this.getAxiosConfig()
3388
3573
  );
3389
3574
  const transcription = response.data;
3390
3575
  return {
@@ -3415,9 +3600,7 @@ var AzureSTTAdapter = class extends BaseAdapter {
3415
3600
  async getTranscript(transcriptId) {
3416
3601
  this.validateConfig();
3417
3602
  try {
3418
- const statusResponse = await this.client.get(
3419
- `/transcriptions/${transcriptId}`
3420
- );
3603
+ const statusResponse = await transcriptionsGet(transcriptId, this.getAxiosConfig());
3421
3604
  const transcription = statusResponse.data;
3422
3605
  const status = this.normalizeStatus(transcription.status);
3423
3606
  if (status !== "completed") {
@@ -3445,7 +3628,11 @@ var AzureSTTAdapter = class extends BaseAdapter {
3445
3628
  raw: transcription
3446
3629
  };
3447
3630
  }
3448
- const filesResponse = await this.client.get(transcription.links.files);
3631
+ const filesResponse = await transcriptionsListFiles(
3632
+ transcriptId,
3633
+ void 0,
3634
+ this.getAxiosConfig()
3635
+ );
3449
3636
  const files = filesResponse.data?.values || [];
3450
3637
  const resultFile = files.find((file) => file.kind === "Transcription");
3451
3638
  if (!resultFile?.links?.contentUrl) {
@@ -3459,7 +3646,7 @@ var AzureSTTAdapter = class extends BaseAdapter {
3459
3646
  raw: transcription
3460
3647
  };
3461
3648
  }
3462
- const contentResponse = await import_axios4.default.get(resultFile.links.contentUrl);
3649
+ const contentResponse = await import_axios5.default.get(resultFile.links.contentUrl);
3463
3650
  const transcriptionData = contentResponse.data;
3464
3651
  return this.normalizeResponse(transcription, transcriptionData);
3465
3652
  } catch (error) {
@@ -3558,7 +3745,57 @@ function createAzureSTTAdapter(config) {
3558
3745
  }
3559
3746
 
3560
3747
  // src/adapters/openai-whisper-adapter.ts
3561
- var import_axios5 = __toESM(require("axios"));
3748
+ var import_axios7 = __toESM(require("axios"));
3749
+
3750
+ // src/generated/openai/api/openAIAPI.ts
3751
+ var import_axios6 = __toESM(require("axios"));
3752
+ var createTranscription = (createTranscriptionRequest, options) => {
3753
+ const formData = new FormData();
3754
+ formData.append("file", createTranscriptionRequest.file);
3755
+ formData.append("model", createTranscriptionRequest.model);
3756
+ if (createTranscriptionRequest.language !== void 0) {
3757
+ formData.append("language", createTranscriptionRequest.language);
3758
+ }
3759
+ if (createTranscriptionRequest.prompt !== void 0) {
3760
+ formData.append("prompt", createTranscriptionRequest.prompt);
3761
+ }
3762
+ if (createTranscriptionRequest.response_format !== void 0) {
3763
+ formData.append("response_format", createTranscriptionRequest.response_format);
3764
+ }
3765
+ if (createTranscriptionRequest.temperature !== void 0) {
3766
+ formData.append("temperature", createTranscriptionRequest.temperature.toString());
3767
+ }
3768
+ if (createTranscriptionRequest.include !== void 0) {
3769
+ createTranscriptionRequest.include.forEach((value) => formData.append("include", value));
3770
+ }
3771
+ if (createTranscriptionRequest.timestamp_granularities !== void 0) {
3772
+ createTranscriptionRequest.timestamp_granularities.forEach(
3773
+ (value) => formData.append("timestamp_granularities", value)
3774
+ );
3775
+ }
3776
+ if (createTranscriptionRequest.stream !== void 0 && createTranscriptionRequest.stream !== null) {
3777
+ formData.append("stream", createTranscriptionRequest.stream.toString());
3778
+ }
3779
+ if (createTranscriptionRequest.chunking_strategy !== void 0 && createTranscriptionRequest.chunking_strategy !== null) {
3780
+ formData.append(
3781
+ "chunking_strategy",
3782
+ typeof createTranscriptionRequest.chunking_strategy === "object" ? JSON.stringify(createTranscriptionRequest.chunking_strategy) : createTranscriptionRequest.chunking_strategy
3783
+ );
3784
+ }
3785
+ if (createTranscriptionRequest.known_speaker_names !== void 0) {
3786
+ createTranscriptionRequest.known_speaker_names.forEach(
3787
+ (value) => formData.append("known_speaker_names", value)
3788
+ );
3789
+ }
3790
+ if (createTranscriptionRequest.known_speaker_references !== void 0) {
3791
+ createTranscriptionRequest.known_speaker_references.forEach(
3792
+ (value) => formData.append("known_speaker_references", value)
3793
+ );
3794
+ }
3795
+ return import_axios6.default.post("/audio/transcriptions", formData, options);
3796
+ };
3797
+
3798
+ // src/adapters/openai-whisper-adapter.ts
3562
3799
  var OpenAIWhisperAdapter = class extends BaseAdapter {
3563
3800
  constructor() {
3564
3801
  super(...arguments);
@@ -3580,19 +3817,12 @@ var OpenAIWhisperAdapter = class extends BaseAdapter {
3580
3817
  };
3581
3818
  this.baseUrl = "https://api.openai.com/v1";
3582
3819
  }
3583
- initialize(config) {
3584
- super.initialize(config);
3585
- this.baseUrl = config.baseUrl || this.baseUrl;
3586
- this.client = import_axios5.default.create({
3587
- baseURL: this.baseUrl,
3588
- timeout: config.timeout || 12e4,
3589
- // 2 minutes default (audio processing can take time)
3590
- headers: {
3591
- Authorization: `Bearer ${config.apiKey}`,
3592
- "Content-Type": "multipart/form-data",
3593
- ...config.headers
3594
- }
3595
- });
3820
+ /**
3821
+ * Get axios config for generated API client functions
3822
+ * Configures headers and base URL using Bearer token authorization
3823
+ */
3824
+ getAxiosConfig() {
3825
+ return super.getAxiosConfig("Authorization", (apiKey) => `Bearer ${apiKey}`);
3596
3826
  }
3597
3827
  /**
3598
3828
  * Submit audio for transcription
@@ -3614,7 +3844,7 @@ var OpenAIWhisperAdapter = class extends BaseAdapter {
3614
3844
  let audioData;
3615
3845
  let fileName = "audio.mp3";
3616
3846
  if (audio.type === "url") {
3617
- const response2 = await import_axios5.default.get(audio.url, {
3847
+ const response2 = await import_axios7.default.get(audio.url, {
3618
3848
  responseType: "arraybuffer"
3619
3849
  });
3620
3850
  audioData = Buffer.from(response2.data);
@@ -3639,40 +3869,37 @@ var OpenAIWhisperAdapter = class extends BaseAdapter {
3639
3869
  const model = this.selectModel(options);
3640
3870
  const isDiarization = model === "gpt-4o-transcribe-diarize";
3641
3871
  const needsWords = options?.wordTimestamps === true;
3642
- const requestBody = {
3872
+ const request = {
3643
3873
  file: audioData,
3874
+ // Generated type expects Blob
3644
3875
  model
3645
3876
  };
3646
3877
  if (options?.language) {
3647
- requestBody.language = options.language;
3878
+ request.language = options.language;
3648
3879
  }
3649
3880
  if (options?.metadata?.prompt) {
3650
- requestBody.prompt = options.metadata.prompt;
3881
+ request.prompt = options.metadata.prompt;
3651
3882
  }
3652
3883
  if (options?.metadata?.temperature !== void 0) {
3653
- requestBody.temperature = options.metadata.temperature;
3884
+ request.temperature = options.metadata.temperature;
3654
3885
  }
3655
3886
  if (isDiarization) {
3656
- requestBody.response_format = "diarized_json";
3887
+ request.response_format = "diarized_json";
3657
3888
  if (options?.metadata?.knownSpeakerNames) {
3658
- requestBody["known_speaker_names"] = options.metadata.knownSpeakerNames;
3889
+ request.known_speaker_names = options.metadata.knownSpeakerNames;
3659
3890
  }
3660
3891
  if (options?.metadata?.knownSpeakerReferences) {
3661
- requestBody["known_speaker_references"] = options.metadata.knownSpeakerReferences;
3892
+ request.known_speaker_references = options.metadata.knownSpeakerReferences;
3662
3893
  }
3663
3894
  } else if (needsWords || options?.diarization) {
3664
- requestBody.response_format = "verbose_json";
3895
+ request.response_format = "verbose_json";
3665
3896
  if (needsWords) {
3666
- requestBody.timestamp_granularities = ["word", "segment"];
3897
+ request.timestamp_granularities = ["word", "segment"];
3667
3898
  }
3668
3899
  } else {
3669
- requestBody.response_format = "json";
3900
+ request.response_format = "json";
3670
3901
  }
3671
- const response = await this.client.post("/audio/transcriptions", requestBody, {
3672
- headers: {
3673
- "Content-Type": "multipart/form-data"
3674
- }
3675
- });
3902
+ const response = await createTranscription(request, this.getAxiosConfig());
3676
3903
  return this.normalizeResponse(response.data, model, isDiarization);
3677
3904
  } catch (error) {
3678
3905
  return this.createErrorResponse(error);
@@ -3793,7 +4020,7 @@ function createOpenAIWhisperAdapter(config) {
3793
4020
  }
3794
4021
 
3795
4022
  // src/adapters/speechmatics-adapter.ts
3796
- var import_axios6 = __toESM(require("axios"));
4023
+ var import_axios8 = __toESM(require("axios"));
3797
4024
  var SpeechmaticsAdapter = class extends BaseAdapter {
3798
4025
  constructor() {
3799
4026
  super(...arguments);
@@ -3815,7 +4042,7 @@ var SpeechmaticsAdapter = class extends BaseAdapter {
3815
4042
  initialize(config) {
3816
4043
  super.initialize(config);
3817
4044
  this.baseUrl = config.baseUrl || this.baseUrl;
3818
- this.client = import_axios6.default.create({
4045
+ this.client = import_axios8.default.create({
3819
4046
  baseURL: this.baseUrl,
3820
4047
  timeout: config.timeout || 12e4,
3821
4048
  headers: {
@@ -4951,9 +5178,16 @@ function createWebhookRouter() {
4951
5178
  GladiaAdapter,
4952
5179
  GladiaTypes,
4953
5180
  GladiaWebhookHandler,
5181
+ ListenV1EncodingParameter,
4954
5182
  OpenAIWhisperAdapter,
5183
+ SpeakV1ContainerParameter,
5184
+ SpeakV1EncodingParameter,
5185
+ SpeakV1SampleRateParameter,
4955
5186
  SpeechmaticsAdapter,
4956
5187
  SpeechmaticsWebhookHandler,
5188
+ StreamingSupportedBitDepthEnum,
5189
+ StreamingSupportedEncodingEnum,
5190
+ StreamingSupportedSampleRateEnum,
4957
5191
  VoiceRouter,
4958
5192
  WebhookRouter,
4959
5193
  createAssemblyAIAdapter,