@skillhq/concierge 1.5.0

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.
Files changed (138) hide show
  1. package/README.md +91 -0
  2. package/dist/cli/program.d.ts +3 -0
  3. package/dist/cli/program.d.ts.map +1 -0
  4. package/dist/cli/program.js +46 -0
  5. package/dist/cli/program.js.map +1 -0
  6. package/dist/cli/shared.d.ts +18 -0
  7. package/dist/cli/shared.d.ts.map +1 -0
  8. package/dist/cli/shared.js +2 -0
  9. package/dist/cli/shared.js.map +1 -0
  10. package/dist/cli.d.ts +3 -0
  11. package/dist/cli.d.ts.map +1 -0
  12. package/dist/cli.js +5 -0
  13. package/dist/cli.js.map +1 -0
  14. package/dist/commands/call.d.ts +7 -0
  15. package/dist/commands/call.d.ts.map +1 -0
  16. package/dist/commands/call.js +409 -0
  17. package/dist/commands/call.js.map +1 -0
  18. package/dist/commands/config.d.ts +4 -0
  19. package/dist/commands/config.d.ts.map +1 -0
  20. package/dist/commands/config.js +120 -0
  21. package/dist/commands/config.js.map +1 -0
  22. package/dist/commands/find-contact.d.ts +4 -0
  23. package/dist/commands/find-contact.d.ts.map +1 -0
  24. package/dist/commands/find-contact.js +57 -0
  25. package/dist/commands/find-contact.js.map +1 -0
  26. package/dist/commands/server.d.ts +7 -0
  27. package/dist/commands/server.d.ts.map +1 -0
  28. package/dist/commands/server.js +212 -0
  29. package/dist/commands/server.js.map +1 -0
  30. package/dist/index.d.ts +4 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +3 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/lib/call/audio/mulaw.d.ts +35 -0
  35. package/dist/lib/call/audio/mulaw.d.ts.map +1 -0
  36. package/dist/lib/call/audio/mulaw.js +109 -0
  37. package/dist/lib/call/audio/mulaw.js.map +1 -0
  38. package/dist/lib/call/audio/pcm-utils.d.ts +62 -0
  39. package/dist/lib/call/audio/pcm-utils.d.ts.map +1 -0
  40. package/dist/lib/call/audio/pcm-utils.js +149 -0
  41. package/dist/lib/call/audio/pcm-utils.js.map +1 -0
  42. package/dist/lib/call/audio/resample.d.ts +34 -0
  43. package/dist/lib/call/audio/resample.d.ts.map +1 -0
  44. package/dist/lib/call/audio/resample.js +97 -0
  45. package/dist/lib/call/audio/resample.js.map +1 -0
  46. package/dist/lib/call/audio/streaming-decoder.d.ts +45 -0
  47. package/dist/lib/call/audio/streaming-decoder.d.ts.map +1 -0
  48. package/dist/lib/call/audio/streaming-decoder.js +110 -0
  49. package/dist/lib/call/audio/streaming-decoder.js.map +1 -0
  50. package/dist/lib/call/call-server.d.ts +110 -0
  51. package/dist/lib/call/call-server.d.ts.map +1 -0
  52. package/dist/lib/call/call-server.js +681 -0
  53. package/dist/lib/call/call-server.js.map +1 -0
  54. package/dist/lib/call/call-session.d.ts +133 -0
  55. package/dist/lib/call/call-session.d.ts.map +1 -0
  56. package/dist/lib/call/call-session.js +890 -0
  57. package/dist/lib/call/call-session.js.map +1 -0
  58. package/dist/lib/call/call-types.d.ts +133 -0
  59. package/dist/lib/call/call-types.d.ts.map +1 -0
  60. package/dist/lib/call/call-types.js +16 -0
  61. package/dist/lib/call/call-types.js.map +1 -0
  62. package/dist/lib/call/conversation-ai.d.ts +56 -0
  63. package/dist/lib/call/conversation-ai.d.ts.map +1 -0
  64. package/dist/lib/call/conversation-ai.js +276 -0
  65. package/dist/lib/call/conversation-ai.js.map +1 -0
  66. package/dist/lib/call/eval/codec-test.d.ts +45 -0
  67. package/dist/lib/call/eval/codec-test.d.ts.map +1 -0
  68. package/dist/lib/call/eval/codec-test.js +169 -0
  69. package/dist/lib/call/eval/codec-test.js.map +1 -0
  70. package/dist/lib/call/eval/conversation-scripts.d.ts +55 -0
  71. package/dist/lib/call/eval/conversation-scripts.d.ts.map +1 -0
  72. package/dist/lib/call/eval/conversation-scripts.js +359 -0
  73. package/dist/lib/call/eval/conversation-scripts.js.map +1 -0
  74. package/dist/lib/call/eval/eval-runner.d.ts +64 -0
  75. package/dist/lib/call/eval/eval-runner.d.ts.map +1 -0
  76. package/dist/lib/call/eval/eval-runner.js +369 -0
  77. package/dist/lib/call/eval/eval-runner.js.map +1 -0
  78. package/dist/lib/call/eval/index.d.ts +9 -0
  79. package/dist/lib/call/eval/index.d.ts.map +1 -0
  80. package/dist/lib/call/eval/index.js +9 -0
  81. package/dist/lib/call/eval/index.js.map +1 -0
  82. package/dist/lib/call/eval/integration-test-suite.d.ts +71 -0
  83. package/dist/lib/call/eval/integration-test-suite.d.ts.map +1 -0
  84. package/dist/lib/call/eval/integration-test-suite.js +519 -0
  85. package/dist/lib/call/eval/integration-test-suite.js.map +1 -0
  86. package/dist/lib/call/eval/turn-taking-test.d.ts +84 -0
  87. package/dist/lib/call/eval/turn-taking-test.d.ts.map +1 -0
  88. package/dist/lib/call/eval/turn-taking-test.js +260 -0
  89. package/dist/lib/call/eval/turn-taking-test.js.map +1 -0
  90. package/dist/lib/call/index.d.ts +12 -0
  91. package/dist/lib/call/index.d.ts.map +1 -0
  92. package/dist/lib/call/index.js +17 -0
  93. package/dist/lib/call/index.js.map +1 -0
  94. package/dist/lib/call/providers/deepgram.d.ts +81 -0
  95. package/dist/lib/call/providers/deepgram.d.ts.map +1 -0
  96. package/dist/lib/call/providers/deepgram.js +279 -0
  97. package/dist/lib/call/providers/deepgram.js.map +1 -0
  98. package/dist/lib/call/providers/elevenlabs.d.ts +78 -0
  99. package/dist/lib/call/providers/elevenlabs.d.ts.map +1 -0
  100. package/dist/lib/call/providers/elevenlabs.js +272 -0
  101. package/dist/lib/call/providers/elevenlabs.js.map +1 -0
  102. package/dist/lib/call/providers/local-deps.d.ts +18 -0
  103. package/dist/lib/call/providers/local-deps.d.ts.map +1 -0
  104. package/dist/lib/call/providers/local-deps.js +114 -0
  105. package/dist/lib/call/providers/local-deps.js.map +1 -0
  106. package/dist/lib/call/providers/twilio.d.ts +53 -0
  107. package/dist/lib/call/providers/twilio.d.ts.map +1 -0
  108. package/dist/lib/call/providers/twilio.js +173 -0
  109. package/dist/lib/call/providers/twilio.js.map +1 -0
  110. package/dist/lib/concierge-client-types.d.ts +68 -0
  111. package/dist/lib/concierge-client-types.d.ts.map +1 -0
  112. package/dist/lib/concierge-client-types.js +2 -0
  113. package/dist/lib/concierge-client-types.js.map +1 -0
  114. package/dist/lib/concierge-client.d.ts +29 -0
  115. package/dist/lib/concierge-client.d.ts.map +1 -0
  116. package/dist/lib/concierge-client.js +534 -0
  117. package/dist/lib/concierge-client.js.map +1 -0
  118. package/dist/lib/config.d.ts +9 -0
  119. package/dist/lib/config.d.ts.map +1 -0
  120. package/dist/lib/config.js +66 -0
  121. package/dist/lib/config.js.map +1 -0
  122. package/dist/lib/output.d.ts +7 -0
  123. package/dist/lib/output.d.ts.map +1 -0
  124. package/dist/lib/output.js +114 -0
  125. package/dist/lib/output.js.map +1 -0
  126. package/dist/lib/utils/contact-extractor.d.ts +12 -0
  127. package/dist/lib/utils/contact-extractor.d.ts.map +1 -0
  128. package/dist/lib/utils/contact-extractor.js +159 -0
  129. package/dist/lib/utils/contact-extractor.js.map +1 -0
  130. package/dist/lib/utils/formatters.d.ts +15 -0
  131. package/dist/lib/utils/formatters.d.ts.map +1 -0
  132. package/dist/lib/utils/formatters.js +107 -0
  133. package/dist/lib/utils/formatters.js.map +1 -0
  134. package/dist/lib/utils/url-parser.d.ts +11 -0
  135. package/dist/lib/utils/url-parser.d.ts.map +1 -0
  136. package/dist/lib/utils/url-parser.js +103 -0
  137. package/dist/lib/utils/url-parser.js.map +1 -0
  138. package/package.json +67 -0
@@ -0,0 +1,260 @@
1
+ /**
2
+ * Turn-Taking Integration Tests
3
+ *
4
+ * Tests the debounce logic and transcript accumulation to ensure:
5
+ * 1. AI doesn't interrupt mid-sentence pauses
6
+ * 2. Multiple transcript segments are properly combined
7
+ * 3. Response timing is appropriate
8
+ * 4. Edge cases are handled (rapid speech, long pauses, etc.)
9
+ */
10
+ import { EventEmitter } from 'node:events';
11
+ // Response debounce time (must match CallSession.RESPONSE_DEBOUNCE_MS)
12
+ const RESPONSE_DEBOUNCE_MS = 1000;
13
+ /**
14
+ * Simulates the turn-taking logic from CallSession
15
+ */
16
+ export class TurnTakingSimulator extends EventEmitter {
17
+ responseDebounceTimer = null;
18
+ pendingTranscript = '';
19
+ isProcessingResponse = false;
20
+ responseTriggeredAt = null;
21
+ startTime = 0;
22
+ /**
23
+ * Simulate receiving a transcript event
24
+ */
25
+ handleTranscript(text, isFinal) {
26
+ if (!text.trim())
27
+ return;
28
+ if (isFinal) {
29
+ // Cancel any pending response timer
30
+ if (this.responseDebounceTimer) {
31
+ clearTimeout(this.responseDebounceTimer);
32
+ }
33
+ // Accumulate transcript
34
+ if (this.pendingTranscript) {
35
+ this.pendingTranscript += ` ${text}`;
36
+ }
37
+ else {
38
+ this.pendingTranscript = text;
39
+ }
40
+ // If already processing, don't queue another
41
+ if (this.isProcessingResponse) {
42
+ return;
43
+ }
44
+ // Start debounce timer
45
+ this.responseDebounceTimer = setTimeout(() => {
46
+ this.responseDebounceTimer = null;
47
+ const fullTranscript = this.pendingTranscript;
48
+ this.pendingTranscript = '';
49
+ if (fullTranscript && !this.isProcessingResponse) {
50
+ this.responseTriggeredAt = Date.now();
51
+ this.isProcessingResponse = true;
52
+ this.emit('response', {
53
+ transcript: fullTranscript,
54
+ delayMs: this.responseTriggeredAt - this.startTime,
55
+ });
56
+ }
57
+ }, RESPONSE_DEBOUNCE_MS);
58
+ }
59
+ }
60
+ /**
61
+ * Run a sequence of transcript events
62
+ */
63
+ async runSequence(events) {
64
+ return new Promise((resolve) => {
65
+ this.startTime = Date.now();
66
+ this.pendingTranscript = '';
67
+ this.isProcessingResponse = false;
68
+ this.responseTriggeredAt = null;
69
+ let responded = false;
70
+ let responseTranscript = '';
71
+ let responseDelay = 0;
72
+ this.once('response', ({ transcript, delayMs }) => {
73
+ responded = true;
74
+ responseTranscript = transcript;
75
+ responseDelay = delayMs;
76
+ });
77
+ // Schedule all events
78
+ let totalDelay = 0;
79
+ for (const event of events) {
80
+ totalDelay += event.delayMs;
81
+ setTimeout(() => {
82
+ this.handleTranscript(event.text, event.isFinal);
83
+ }, totalDelay);
84
+ }
85
+ // Wait for response or timeout
86
+ const maxWait = totalDelay + RESPONSE_DEBOUNCE_MS + 500;
87
+ setTimeout(() => {
88
+ // Clean up
89
+ if (this.responseDebounceTimer) {
90
+ clearTimeout(this.responseDebounceTimer);
91
+ }
92
+ resolve({
93
+ responded,
94
+ transcript: responseTranscript,
95
+ delayMs: responseDelay,
96
+ });
97
+ }, maxWait);
98
+ });
99
+ }
100
+ }
101
+ /**
102
+ * Test cases for turn-taking
103
+ */
104
+ export const TURN_TAKING_TEST_CASES = [
105
+ // Basic cases
106
+ {
107
+ id: 'simple-sentence',
108
+ name: 'Simple sentence',
109
+ description: 'Single final transcript should trigger response after debounce',
110
+ events: [{ text: 'Hello, how are you?', isFinal: true, delayMs: 0 }],
111
+ expectedTranscript: 'Hello, how are you?',
112
+ expectedMinDelayMs: RESPONSE_DEBOUNCE_MS - 50,
113
+ expectedMaxDelayMs: RESPONSE_DEBOUNCE_MS + 100,
114
+ shouldRespond: true,
115
+ },
116
+ {
117
+ id: 'interim-then-final',
118
+ name: 'Interim followed by final',
119
+ description: 'Interim results should be ignored, only final triggers response',
120
+ events: [
121
+ { text: 'Hello', isFinal: false, delayMs: 0 },
122
+ { text: 'Hello how', isFinal: false, delayMs: 100 },
123
+ { text: 'Hello, how are you?', isFinal: true, delayMs: 200 },
124
+ ],
125
+ expectedTranscript: 'Hello, how are you?',
126
+ expectedMinDelayMs: 200 + RESPONSE_DEBOUNCE_MS - 50,
127
+ expectedMaxDelayMs: 200 + RESPONSE_DEBOUNCE_MS + 150, // Allow more tolerance for timer variance
128
+ shouldRespond: true,
129
+ },
130
+ // Mid-sentence pause cases (the main issue we're fixing)
131
+ {
132
+ id: 'pause-mid-sentence',
133
+ name: 'Pause mid-sentence',
134
+ description: 'User pauses thinking, continues - transcripts should combine',
135
+ events: [
136
+ { text: 'How much', isFinal: true, delayMs: 0 },
137
+ { text: 'were you looking for?', isFinal: true, delayMs: 600 }, // Within debounce window
138
+ ],
139
+ expectedTranscript: 'How much were you looking for?',
140
+ expectedMinDelayMs: 600 + RESPONSE_DEBOUNCE_MS - 50,
141
+ expectedMaxDelayMs: 600 + RESPONSE_DEBOUNCE_MS + 100,
142
+ shouldRespond: true,
143
+ },
144
+ {
145
+ id: 'longer-pause-mid-sentence',
146
+ name: 'Longer pause mid-sentence',
147
+ description: 'User pauses longer but still within window',
148
+ events: [
149
+ { text: 'Yeah. I think so. But it feels', isFinal: true, delayMs: 0 },
150
+ { text: 'a little bit weird', isFinal: true, delayMs: 700 }, // Just within debounce
151
+ ],
152
+ expectedTranscript: 'Yeah. I think so. But it feels a little bit weird',
153
+ expectedMinDelayMs: 700 + RESPONSE_DEBOUNCE_MS - 50,
154
+ expectedMaxDelayMs: 700 + RESPONSE_DEBOUNCE_MS + 100,
155
+ shouldRespond: true,
156
+ },
157
+ {
158
+ id: 'three-part-sentence',
159
+ name: 'Three-part sentence with pauses',
160
+ description: 'User pauses twice while thinking',
161
+ events: [
162
+ { text: 'Well', isFinal: true, delayMs: 0 },
163
+ { text: 'let me think', isFinal: true, delayMs: 500 },
164
+ { text: 'maybe around three fifty?', isFinal: true, delayMs: 600 },
165
+ ],
166
+ expectedTranscript: 'Well let me think maybe around three fifty?',
167
+ expectedMinDelayMs: 500 + 600 + RESPONSE_DEBOUNCE_MS - 50,
168
+ expectedMaxDelayMs: 500 + 600 + RESPONSE_DEBOUNCE_MS + 100,
169
+ shouldRespond: true,
170
+ },
171
+ // Edge cases
172
+ {
173
+ id: 'very-long-pause',
174
+ name: 'Very long pause (separate utterances)',
175
+ description: 'Pause longer than debounce should trigger two responses',
176
+ events: [
177
+ { text: 'First sentence.', isFinal: true, delayMs: 0 },
178
+ // This comes after debounce fires, so it's a new utterance
179
+ { text: 'Second sentence.', isFinal: true, delayMs: 1500 },
180
+ ],
181
+ expectedTranscript: 'First sentence.', // First response only
182
+ expectedMinDelayMs: RESPONSE_DEBOUNCE_MS - 50,
183
+ expectedMaxDelayMs: RESPONSE_DEBOUNCE_MS + 100,
184
+ shouldRespond: true,
185
+ },
186
+ {
187
+ id: 'rapid-fire',
188
+ name: 'Rapid speech (no pauses)',
189
+ description: 'Fast talker with quick transcript segments',
190
+ events: [
191
+ { text: 'Yes', isFinal: true, delayMs: 0 },
192
+ { text: 'that sounds', isFinal: true, delayMs: 100 },
193
+ { text: 'great', isFinal: true, delayMs: 100 },
194
+ { text: 'lets do it', isFinal: true, delayMs: 100 },
195
+ ],
196
+ expectedTranscript: 'Yes that sounds great lets do it',
197
+ expectedMinDelayMs: 300 + RESPONSE_DEBOUNCE_MS - 50,
198
+ expectedMaxDelayMs: 300 + RESPONSE_DEBOUNCE_MS + 100,
199
+ shouldRespond: true,
200
+ },
201
+ {
202
+ id: 'empty-transcripts',
203
+ name: 'Empty transcripts ignored',
204
+ description: 'Empty or whitespace transcripts should not affect debounce',
205
+ events: [
206
+ { text: 'Hello', isFinal: true, delayMs: 0 },
207
+ { text: '', isFinal: true, delayMs: 200 },
208
+ { text: ' ', isFinal: true, delayMs: 200 },
209
+ { text: 'world', isFinal: true, delayMs: 200 },
210
+ ],
211
+ expectedTranscript: 'Hello world',
212
+ expectedMinDelayMs: 600 + RESPONSE_DEBOUNCE_MS - 50,
213
+ expectedMaxDelayMs: 600 + RESPONSE_DEBOUNCE_MS + 100,
214
+ shouldRespond: true,
215
+ },
216
+ ];
217
+ /**
218
+ * Run a single turn-taking test
219
+ */
220
+ export async function runTurnTakingTest(testCase) {
221
+ const simulator = new TurnTakingSimulator();
222
+ const result = await simulator.runSequence(testCase.events);
223
+ const transcriptMatch = result.transcript === testCase.expectedTranscript;
224
+ const delayInRange = result.delayMs >= testCase.expectedMinDelayMs && result.delayMs <= testCase.expectedMaxDelayMs;
225
+ const respondedCorrectly = result.responded === testCase.shouldRespond;
226
+ return {
227
+ testId: testCase.id,
228
+ testName: testCase.name,
229
+ passed: transcriptMatch && delayInRange && respondedCorrectly,
230
+ responded: result.responded,
231
+ expectedRespond: testCase.shouldRespond,
232
+ transcript: result.transcript,
233
+ expectedTranscript: testCase.expectedTranscript,
234
+ transcriptMatch,
235
+ delayMs: result.delayMs,
236
+ expectedMinDelayMs: testCase.expectedMinDelayMs,
237
+ expectedMaxDelayMs: testCase.expectedMaxDelayMs,
238
+ delayInRange,
239
+ };
240
+ }
241
+ /**
242
+ * Run all turn-taking tests
243
+ */
244
+ export async function runAllTurnTakingTests() {
245
+ const results = [];
246
+ let passed = 0;
247
+ let failed = 0;
248
+ for (const testCase of TURN_TAKING_TEST_CASES) {
249
+ const result = await runTurnTakingTest(testCase);
250
+ results.push(result);
251
+ if (result.passed) {
252
+ passed++;
253
+ }
254
+ else {
255
+ failed++;
256
+ }
257
+ }
258
+ return { passed, failed, results };
259
+ }
260
+ //# sourceMappingURL=turn-taking-test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"turn-taking-test.js","sourceRoot":"","sources":["../../../../src/lib/call/eval/turn-taking-test.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,uEAAuE;AACvE,MAAM,oBAAoB,GAAG,IAAI,CAAC;AAwBlC;;GAEG;AACH,MAAM,OAAO,mBAAoB,SAAQ,YAAY;IAC3C,qBAAqB,GAA0B,IAAI,CAAC;IACpD,iBAAiB,GAAG,EAAE,CAAC;IACvB,oBAAoB,GAAG,KAAK,CAAC;IAC7B,mBAAmB,GAAkB,IAAI,CAAC;IAC1C,SAAS,GAAW,CAAC,CAAC;IAE9B;;OAEG;IACH,gBAAgB,CAAC,IAAY,EAAE,OAAgB;QAC7C,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;YAAE,OAAO;QAEzB,IAAI,OAAO,EAAE,CAAC;YACZ,oCAAoC;YACpC,IAAI,IAAI,CAAC,qBAAqB,EAAE,CAAC;gBAC/B,YAAY,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;YAC3C,CAAC;YAED,wBAAwB;YACxB,IAAI,IAAI,CAAC,iBAAiB,EAAE,CAAC;gBAC3B,IAAI,CAAC,iBAAiB,IAAI,IAAI,IAAI,EAAE,CAAC;YACvC,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC;YAChC,CAAC;YAED,6CAA6C;YAC7C,IAAI,IAAI,CAAC,oBAAoB,EAAE,CAAC;gBAC9B,OAAO;YACT,CAAC;YAED,uBAAuB;YACvB,IAAI,CAAC,qBAAqB,GAAG,UAAU,CAAC,GAAG,EAAE;gBAC3C,IAAI,CAAC,qBAAqB,GAAG,IAAI,CAAC;gBAClC,MAAM,cAAc,GAAG,IAAI,CAAC,iBAAiB,CAAC;gBAC9C,IAAI,CAAC,iBAAiB,GAAG,EAAE,CAAC;gBAE5B,IAAI,cAAc,IAAI,CAAC,IAAI,CAAC,oBAAoB,EAAE,CAAC;oBACjD,IAAI,CAAC,mBAAmB,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;oBACtC,IAAI,CAAC,oBAAoB,GAAG,IAAI,CAAC;oBACjC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE;wBACpB,UAAU,EAAE,cAAc;wBAC1B,OAAO,EAAE,IAAI,CAAC,mBAAmB,GAAG,IAAI,CAAC,SAAS;qBACnD,CAAC,CAAC;gBACL,CAAC;YACH,CAAC,EAAE,oBAAoB,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,WAAW,CAAC,MAAyB;QAKzC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;YAC7B,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAC5B,IAAI,CAAC,iBAAiB,GAAG,EAAE,CAAC;YAC5B,IAAI,CAAC,oBAAoB,GAAG,KAAK,CAAC;YAClC,IAAI,CAAC,mBAAmB,GAAG,IAAI,CAAC;YAEhC,IAAI,SAAS,GAAG,KAAK,CAAC;YACtB,IAAI,kBAAkB,GAAG,EAAE,CAAC;YAC5B,IAAI,aAAa,GAAG,CAAC,CAAC;YAEtB,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE,EAAE;gBAChD,SAAS,GAAG,IAAI,CAAC;gBACjB,kBAAkB,GAAG,UAAU,CAAC;gBAChC,aAAa,GAAG,OAAO,CAAC;YAC1B,CAAC,CAAC,CAAC;YAEH,sBAAsB;YACtB,IAAI,UAAU,GAAG,CAAC,CAAC;YACnB,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;gBAC3B,UAAU,IAAI,KAAK,CAAC,OAAO,CAAC;gBAC5B,UAAU,CAAC,GAAG,EAAE;oBACd,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;gBACnD,CAAC,EAAE,UAAU,CAAC,CAAC;YACjB,CAAC;YAED,+BAA+B;YAC/B,MAAM,OAAO,GAAG,UAAU,GAAG,oBAAoB,GAAG,GAAG,CAAC;YACxD,UAAU,CAAC,GAAG,EAAE;gBACd,WAAW;gBACX,IAAI,IAAI,CAAC,qBAAqB,EAAE,CAAC;oBAC/B,YAAY,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;gBAC3C,CAAC;gBAED,OAAO,CAAC;oBACN,SAAS;oBACT,UAAU,EAAE,kBAAkB;oBAC9B,OAAO,EAAE,aAAa;iBACvB,CAAC,CAAC;YACL,CAAC,EAAE,OAAO,CAAC,CAAC;QACd,CAAC,CAAC,CAAC;IACL,CAAC;CACF;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAyB;IAC1D,cAAc;IACd;QACE,EAAE,EAAE,iBAAiB;QACrB,IAAI,EAAE,iBAAiB;QACvB,WAAW,EAAE,gEAAgE;QAC7E,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,qBAAqB,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;QACpE,kBAAkB,EAAE,qBAAqB;QACzC,kBAAkB,EAAE,oBAAoB,GAAG,EAAE;QAC7C,kBAAkB,EAAE,oBAAoB,GAAG,GAAG;QAC9C,aAAa,EAAE,IAAI;KACpB;IACD;QACE,EAAE,EAAE,oBAAoB;QACxB,IAAI,EAAE,2BAA2B;QACjC,WAAW,EAAE,iEAAiE;QAC9E,MAAM,EAAE;YACN,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE;YAC7C,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE;YACnD,EAAE,IAAI,EAAE,qBAAqB,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE;SAC7D;QACD,kBAAkB,EAAE,qBAAqB;QACzC,kBAAkB,EAAE,GAAG,GAAG,oBAAoB,GAAG,EAAE;QACnD,kBAAkB,EAAE,GAAG,GAAG,oBAAoB,GAAG,GAAG,EAAE,0CAA0C;QAChG,aAAa,EAAE,IAAI;KACpB;IAED,yDAAyD;IACzD;QACE,EAAE,EAAE,oBAAoB;QACxB,IAAI,EAAE,oBAAoB;QAC1B,WAAW,EAAE,8DAA8D;QAC3E,MAAM,EAAE;YACN,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE;YAC/C,EAAE,IAAI,EAAE,uBAAuB,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE,yBAAyB;SAC1F;QACD,kBAAkB,EAAE,gCAAgC;QACpD,kBAAkB,EAAE,GAAG,GAAG,oBAAoB,GAAG,EAAE;QACnD,kBAAkB,EAAE,GAAG,GAAG,oBAAoB,GAAG,GAAG;QACpD,aAAa,EAAE,IAAI;KACpB;IACD;QACE,EAAE,EAAE,2BAA2B;QAC/B,IAAI,EAAE,2BAA2B;QACjC,WAAW,EAAE,4CAA4C;QACzD,MAAM,EAAE;YACN,EAAE,IAAI,EAAE,gCAAgC,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE;YACrE,EAAE,IAAI,EAAE,oBAAoB,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE,uBAAuB;SACrF;QACD,kBAAkB,EAAE,mDAAmD;QACvE,kBAAkB,EAAE,GAAG,GAAG,oBAAoB,GAAG,EAAE;QACnD,kBAAkB,EAAE,GAAG,GAAG,oBAAoB,GAAG,GAAG;QACpD,aAAa,EAAE,IAAI;KACpB;IACD;QACE,EAAE,EAAE,qBAAqB;QACzB,IAAI,EAAE,iCAAiC;QACvC,WAAW,EAAE,kCAAkC;QAC/C,MAAM,EAAE;YACN,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE;YAC3C,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE;YACrD,EAAE,IAAI,EAAE,2BAA2B,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE;SACnE;QACD,kBAAkB,EAAE,6CAA6C;QACjE,kBAAkB,EAAE,GAAG,GAAG,GAAG,GAAG,oBAAoB,GAAG,EAAE;QACzD,kBAAkB,EAAE,GAAG,GAAG,GAAG,GAAG,oBAAoB,GAAG,GAAG;QAC1D,aAAa,EAAE,IAAI;KACpB;IAED,aAAa;IACb;QACE,EAAE,EAAE,iBAAiB;QACrB,IAAI,EAAE,uCAAuC;QAC7C,WAAW,EAAE,yDAAyD;QACtE,MAAM,EAAE;YACN,EAAE,IAAI,EAAE,iBAAiB,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE;YACtD,2DAA2D;YAC3D,EAAE,IAAI,EAAE,kBAAkB,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE;SAC3D;QACD,kBAAkB,EAAE,iBAAiB,EAAE,sBAAsB;QAC7D,kBAAkB,EAAE,oBAAoB,GAAG,EAAE;QAC7C,kBAAkB,EAAE,oBAAoB,GAAG,GAAG;QAC9C,aAAa,EAAE,IAAI;KACpB;IACD;QACE,EAAE,EAAE,YAAY;QAChB,IAAI,EAAE,0BAA0B;QAChC,WAAW,EAAE,4CAA4C;QACzD,MAAM,EAAE;YACN,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE;YAC1C,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE;YACpD,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE;YAC9C,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE;SACpD;QACD,kBAAkB,EAAE,kCAAkC;QACtD,kBAAkB,EAAE,GAAG,GAAG,oBAAoB,GAAG,EAAE;QACnD,kBAAkB,EAAE,GAAG,GAAG,oBAAoB,GAAG,GAAG;QACpD,aAAa,EAAE,IAAI;KACpB;IACD;QACE,EAAE,EAAE,mBAAmB;QACvB,IAAI,EAAE,2BAA2B;QACjC,WAAW,EAAE,4DAA4D;QACzE,MAAM,EAAE;YACN,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE;YAC5C,EAAE,IAAI,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE;YACzC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE;YAC5C,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE;SAC/C;QACD,kBAAkB,EAAE,aAAa;QACjC,kBAAkB,EAAE,GAAG,GAAG,oBAAoB,GAAG,EAAE;QACnD,kBAAkB,EAAE,GAAG,GAAG,oBAAoB,GAAG,GAAG;QACpD,aAAa,EAAE,IAAI;KACpB;CACF,CAAC;AAkBF;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,QAA4B;IAClE,MAAM,SAAS,GAAG,IAAI,mBAAmB,EAAE,CAAC;IAC5C,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,WAAW,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAE5D,MAAM,eAAe,GAAG,MAAM,CAAC,UAAU,KAAK,QAAQ,CAAC,kBAAkB,CAAC;IAC1E,MAAM,YAAY,GAAG,MAAM,CAAC,OAAO,IAAI,QAAQ,CAAC,kBAAkB,IAAI,MAAM,CAAC,OAAO,IAAI,QAAQ,CAAC,kBAAkB,CAAC;IACpH,MAAM,kBAAkB,GAAG,MAAM,CAAC,SAAS,KAAK,QAAQ,CAAC,aAAa,CAAC;IAEvE,OAAO;QACL,MAAM,EAAE,QAAQ,CAAC,EAAE;QACnB,QAAQ,EAAE,QAAQ,CAAC,IAAI;QACvB,MAAM,EAAE,eAAe,IAAI,YAAY,IAAI,kBAAkB;QAC7D,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,eAAe,EAAE,QAAQ,CAAC,aAAa;QACvC,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,kBAAkB,EAAE,QAAQ,CAAC,kBAAkB;QAC/C,eAAe;QACf,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,kBAAkB,EAAE,QAAQ,CAAC,kBAAkB;QAC/C,kBAAkB,EAAE,QAAQ,CAAC,kBAAkB;QAC/C,YAAY;KACb,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB;IAKzC,MAAM,OAAO,GAA2B,EAAE,CAAC;IAC3C,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,MAAM,GAAG,CAAC,CAAC;IAEf,KAAK,MAAM,QAAQ,IAAI,sBAAsB,EAAE,CAAC;QAC9C,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC,QAAQ,CAAC,CAAC;QACjD,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACrB,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;YAClB,MAAM,EAAE,CAAC;QACX,CAAC;aAAM,CAAC;YACN,MAAM,EAAE,CAAC;QACX,CAAC;IACH,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;AACrC,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Voice call module exports
3
+ */
4
+ export * from './audio/mulaw.js';
5
+ export * from './audio/pcm-utils.js';
6
+ export { CallServer, createCallServer } from './call-server.js';
7
+ export { CallSession } from './call-session.js';
8
+ export * from './call-types.js';
9
+ export * from './providers/deepgram.js';
10
+ export * from './providers/elevenlabs.js';
11
+ export * from './providers/twilio.js';
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/call/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AAErC,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEhE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,cAAc,iBAAiB,CAAC;AAChC,cAAc,yBAAyB,CAAC;AACxC,cAAc,2BAA2B,CAAC;AAE1C,cAAc,uBAAuB,CAAC"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Voice call module exports
3
+ */
4
+ // Audio utilities
5
+ export * from './audio/mulaw.js';
6
+ export * from './audio/pcm-utils.js';
7
+ // Server
8
+ export { CallServer, createCallServer } from './call-server.js';
9
+ // Session management
10
+ export { CallSession } from './call-session.js';
11
+ // Types
12
+ export * from './call-types.js';
13
+ export * from './providers/deepgram.js';
14
+ export * from './providers/elevenlabs.js';
15
+ // Providers
16
+ export * from './providers/twilio.js';
17
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/call/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,kBAAkB;AAClB,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,SAAS;AACT,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAChE,qBAAqB;AACrB,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,QAAQ;AACR,cAAc,iBAAiB,CAAC;AAChC,cAAc,yBAAyB,CAAC;AACxC,cAAc,2BAA2B,CAAC;AAC1C,YAAY;AACZ,cAAc,uBAAuB,CAAC"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Deepgram provider for real-time speech-to-text
3
+ */
4
+ import { EventEmitter } from 'node:events';
5
+ export interface DeepgramConfig {
6
+ apiKey: string;
7
+ language?: string;
8
+ model?: string;
9
+ punctuate?: boolean;
10
+ interimResults?: boolean;
11
+ endpointing?: number;
12
+ /** Minimum confidence threshold (0-1) for final transcripts. Below this, transcripts are dropped. */
13
+ confidenceThreshold?: number;
14
+ /** Keywords to boost recognition of (e.g., common responses like "yes", "no") */
15
+ keywords?: string[];
16
+ }
17
+ export interface TranscriptResult {
18
+ text: string;
19
+ isFinal: boolean;
20
+ confidence: number;
21
+ words?: Array<{
22
+ word: string;
23
+ start: number;
24
+ end: number;
25
+ confidence: number;
26
+ }>;
27
+ }
28
+ export interface DeepgramEvents {
29
+ transcript: (result: TranscriptResult) => void;
30
+ error: (error: Error) => void;
31
+ close: () => void;
32
+ open: () => void;
33
+ }
34
+ export interface DeepgramPreflightResult {
35
+ ok: boolean;
36
+ provider: 'deepgram';
37
+ status?: number;
38
+ message: string;
39
+ }
40
+ /**
41
+ * Preflight check for Deepgram STT readiness.
42
+ * Validates API key and basic control-plane reachability.
43
+ */
44
+ export declare function preflightDeepgramSTT(apiKey: string): Promise<DeepgramPreflightResult>;
45
+ /**
46
+ * Real-time speech-to-text using Deepgram
47
+ */
48
+ export declare class DeepgramSTT extends EventEmitter {
49
+ private connection;
50
+ private readonly config;
51
+ private isConnected;
52
+ constructor(config: DeepgramConfig);
53
+ /**
54
+ * Connect to Deepgram's real-time transcription service
55
+ */
56
+ connect(): Promise<void>;
57
+ private audioBytesSent;
58
+ /**
59
+ * Send audio data to Deepgram for transcription
60
+ * @param audio - 16-bit PCM audio at 8kHz
61
+ */
62
+ sendAudio(audio: Buffer): void;
63
+ /**
64
+ * Signal end of audio (for final transcript)
65
+ */
66
+ finalize(): void;
67
+ /**
68
+ * Close the Deepgram connection
69
+ */
70
+ close(): void;
71
+ /**
72
+ * Check if connected
73
+ */
74
+ get connected(): boolean;
75
+ }
76
+ export declare const PHONE_CALL_KEYWORDS: string[];
77
+ /**
78
+ * Create a Deepgram STT instance optimized for phone calls
79
+ */
80
+ export declare function createPhoneCallSTT(apiKey: string): DeepgramSTT;
81
+ //# sourceMappingURL=deepgram.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deepgram.d.ts","sourceRoot":"","sources":["../../../../src/lib/call/providers/deepgram.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qGAAqG;IACrG,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,iFAAiF;IACjF,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;QACd,GAAG,EAAE,MAAM,CAAC;QACZ,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAC/C,KAAK,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAC9B,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,IAAI,EAAE,MAAM,IAAI,CAAC;CAClB;AAED,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,OAAO,CAAC;IACZ,QAAQ,EAAE,UAAU,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAyC3F;AAED;;GAEG;AACH,qBAAa,WAAY,SAAQ,YAAY;IAC3C,OAAO,CAAC,UAAU,CAA2B;IAC7C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiB;IACxC,OAAO,CAAC,WAAW,CAAS;gBAEhB,MAAM,EAAE,cAAc;IAKlC;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA+G9B,OAAO,CAAC,cAAc,CAAK;IAE3B;;;OAGG;IACH,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAiB9B;;OAEG;IACH,QAAQ,IAAI,IAAI;IAahB;;OAEG;IACH,KAAK,IAAI,IAAI;IAYb;;OAEG;IACH,IAAI,SAAS,IAAI,OAAO,CAEvB;CACF;AAID,eAAO,MAAM,mBAAmB,UAuC/B,CAAC;AAEF;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,CAkB9D"}
@@ -0,0 +1,279 @@
1
+ /**
2
+ * Deepgram provider for real-time speech-to-text
3
+ */
4
+ import { EventEmitter } from 'node:events';
5
+ import { createClient, LiveTranscriptionEvents } from '@deepgram/sdk';
6
+ /**
7
+ * Preflight check for Deepgram STT readiness.
8
+ * Validates API key and basic control-plane reachability.
9
+ */
10
+ export async function preflightDeepgramSTT(apiKey) {
11
+ try {
12
+ const response = await fetch('https://api.deepgram.com/v1/projects', {
13
+ method: 'GET',
14
+ headers: {
15
+ Authorization: `Token ${apiKey}`,
16
+ },
17
+ });
18
+ if (response.ok) {
19
+ return {
20
+ ok: true,
21
+ provider: 'deepgram',
22
+ status: response.status,
23
+ message: 'Deepgram preflight passed: API key accepted.',
24
+ };
25
+ }
26
+ const body = await response.text();
27
+ if (response.status === 401 || response.status === 403) {
28
+ return {
29
+ ok: false,
30
+ provider: 'deepgram',
31
+ status: response.status,
32
+ message: `Deepgram preflight failed: invalid or unauthorized API key (${response.status}).`,
33
+ };
34
+ }
35
+ return {
36
+ ok: false,
37
+ provider: 'deepgram',
38
+ status: response.status,
39
+ message: `Deepgram preflight failed: HTTP ${response.status}${body ? ` - ${body}` : ''}`,
40
+ };
41
+ }
42
+ catch (error) {
43
+ return {
44
+ ok: false,
45
+ provider: 'deepgram',
46
+ message: `Deepgram preflight failed: ${error instanceof Error ? error.message : String(error)}`,
47
+ };
48
+ }
49
+ }
50
+ /**
51
+ * Real-time speech-to-text using Deepgram
52
+ */
53
+ export class DeepgramSTT extends EventEmitter {
54
+ connection = null;
55
+ config;
56
+ isConnected = false;
57
+ constructor(config) {
58
+ super();
59
+ this.config = config;
60
+ }
61
+ /**
62
+ * Connect to Deepgram's real-time transcription service
63
+ */
64
+ async connect() {
65
+ const deepgram = createClient(this.config.apiKey);
66
+ // Build live transcription options
67
+ const liveOptions = {
68
+ model: this.config.model ?? 'nova-2',
69
+ language: this.config.language ?? 'en-US',
70
+ punctuate: this.config.punctuate ?? true,
71
+ interim_results: this.config.interimResults ?? true,
72
+ endpointing: this.config.endpointing ?? 300,
73
+ // Optimized for phone audio
74
+ encoding: 'linear16',
75
+ sample_rate: 8000,
76
+ channels: 1,
77
+ smart_format: true,
78
+ };
79
+ // Add keyword boosting if configured
80
+ // Format: "word:intensifier" where intensifier is an exponential boost factor
81
+ if (this.config.keywords && this.config.keywords.length > 0) {
82
+ liveOptions.keywords = this.config.keywords.map((kw) => `${kw}:2`);
83
+ console.log(`[Deepgram] Keyword boosting enabled for ${this.config.keywords.length} keywords`);
84
+ }
85
+ this.connection = deepgram.listen.live(liveOptions);
86
+ return new Promise((resolve, reject) => {
87
+ if (!this.connection) {
88
+ reject(new Error('Failed to create Deepgram connection'));
89
+ return;
90
+ }
91
+ this.connection.on(LiveTranscriptionEvents.Open, () => {
92
+ this.isConnected = true;
93
+ this.emit('open');
94
+ resolve();
95
+ });
96
+ this.connection.on(LiveTranscriptionEvents.Transcript, (data) => {
97
+ // Log raw Deepgram response for debugging
98
+ const transcript = data.channel?.alternatives?.[0];
99
+ const confidence = transcript?.confidence ?? 0;
100
+ const isFinal = data.is_final ?? false;
101
+ const threshold = this.config.confidenceThreshold ?? 0;
102
+ console.log(`[Deepgram] Transcript event - text: "${transcript?.transcript || ''}", confidence: ${confidence.toFixed(3)}, is_final: ${isFinal}${threshold > 0 ? `, threshold: ${threshold}` : ''}`);
103
+ if (transcript?.transcript) {
104
+ // Filter out low-confidence final transcripts (likely noise/misrecognition)
105
+ if (isFinal && threshold > 0 && confidence < threshold) {
106
+ console.log(`[Deepgram] Dropping low-confidence transcript: "${transcript.transcript}" (${(confidence * 100).toFixed(1)}% < ${(threshold * 100).toFixed(0)}% threshold)`);
107
+ return;
108
+ }
109
+ const result = {
110
+ text: transcript.transcript,
111
+ isFinal,
112
+ confidence,
113
+ words: transcript.words?.map((w) => ({
114
+ word: w.word,
115
+ start: w.start,
116
+ end: w.end,
117
+ confidence: w.confidence,
118
+ })),
119
+ };
120
+ this.emit('transcript', result);
121
+ }
122
+ });
123
+ this.connection.on(LiveTranscriptionEvents.Error, (error) => {
124
+ console.error('[Deepgram] Error event:', error);
125
+ this.emit('error', error instanceof Error ? error : new Error(String(error)));
126
+ });
127
+ this.connection.on(LiveTranscriptionEvents.Close, () => {
128
+ console.log(`[Deepgram] Connection closed. Total bytes sent: ${this.audioBytesSent}`);
129
+ this.isConnected = false;
130
+ this.emit('close');
131
+ });
132
+ // Log other events for debugging
133
+ this.connection.on(LiveTranscriptionEvents.Metadata, (data) => {
134
+ console.log('[Deepgram] Metadata event:', JSON.stringify(data));
135
+ });
136
+ this.connection.on(LiveTranscriptionEvents.UtteranceEnd, () => {
137
+ console.log('[Deepgram] UtteranceEnd event');
138
+ });
139
+ // Timeout for connection
140
+ setTimeout(() => {
141
+ if (!this.isConnected) {
142
+ // Clean up on timeout
143
+ if (this.connection) {
144
+ try {
145
+ this.connection.requestClose();
146
+ }
147
+ catch {
148
+ // Ignore close errors
149
+ }
150
+ this.connection = null;
151
+ }
152
+ reject(new Error('Deepgram connection timeout'));
153
+ }
154
+ }, 10000);
155
+ });
156
+ }
157
+ audioBytesSent = 0;
158
+ /**
159
+ * Send audio data to Deepgram for transcription
160
+ * @param audio - 16-bit PCM audio at 8kHz
161
+ */
162
+ sendAudio(audio) {
163
+ if (!this.connection || !this.isConnected) {
164
+ return;
165
+ }
166
+ try {
167
+ // Create a copy of the buffer data to avoid issues with pooled buffers
168
+ // Using Uint8Array.from creates a new ArrayBuffer with copied data
169
+ const copy = new Uint8Array(audio.length);
170
+ copy.set(audio);
171
+ this.connection.send(copy.buffer);
172
+ this.audioBytesSent += audio.length;
173
+ }
174
+ catch (error) {
175
+ this.emit('error', error instanceof Error ? error : new Error(String(error)));
176
+ }
177
+ }
178
+ /**
179
+ * Signal end of audio (for final transcript)
180
+ */
181
+ finalize() {
182
+ if (!this.connection || !this.isConnected) {
183
+ return;
184
+ }
185
+ try {
186
+ // Send keep-alive to flush any pending audio
187
+ this.connection.keepAlive();
188
+ }
189
+ catch {
190
+ // Ignore errors when finalizing
191
+ }
192
+ }
193
+ /**
194
+ * Close the Deepgram connection
195
+ */
196
+ close() {
197
+ if (this.connection) {
198
+ try {
199
+ this.connection.requestClose();
200
+ }
201
+ catch {
202
+ // Ignore close errors
203
+ }
204
+ this.connection = null;
205
+ this.isConnected = false;
206
+ }
207
+ }
208
+ /**
209
+ * Check if connected
210
+ */
211
+ get connected() {
212
+ return this.isConnected;
213
+ }
214
+ }
215
+ // Common conversational keywords to boost recognition accuracy
216
+ // Exported for future use when keyword format is verified with Deepgram SDK
217
+ export const PHONE_CALL_KEYWORDS = [
218
+ // Affirmatives
219
+ 'yes',
220
+ 'yeah',
221
+ 'yep',
222
+ 'sure',
223
+ 'okay',
224
+ 'ok',
225
+ 'correct',
226
+ 'right',
227
+ 'absolutely',
228
+ 'definitely',
229
+ // Negatives
230
+ 'no',
231
+ 'nope',
232
+ 'not',
233
+ // Common responses
234
+ 'hello',
235
+ 'hi',
236
+ 'thanks',
237
+ 'thank you',
238
+ 'please',
239
+ 'sorry',
240
+ 'pardon',
241
+ 'what',
242
+ 'when',
243
+ 'where',
244
+ 'how',
245
+ // Booking-related
246
+ 'booking',
247
+ 'reservation',
248
+ 'room',
249
+ 'night',
250
+ 'nights',
251
+ 'check-in',
252
+ 'checkout',
253
+ 'available',
254
+ 'confirm',
255
+ 'cancel',
256
+ ];
257
+ /**
258
+ * Create a Deepgram STT instance optimized for phone calls
259
+ */
260
+ export function createPhoneCallSTT(apiKey) {
261
+ return new DeepgramSTT({
262
+ apiKey,
263
+ // Nova-2 phonecall variant - optimized for telephony audio
264
+ model: 'nova-2-phonecall',
265
+ language: 'en-US',
266
+ punctuate: true,
267
+ interimResults: true,
268
+ // 800ms of silence within a phrase to end the utterance
269
+ // Combined with 1000ms response debounce = ~1.8s total before AI responds
270
+ // This allows for natural thinking pauses without interruption
271
+ endpointing: 800,
272
+ // Filter out low-confidence transcripts (noise, misrecognition)
273
+ // 80% threshold filters out garbage like "West" (62%) while keeping good transcripts
274
+ confidenceThreshold: 0.8,
275
+ // Boost common conversational words for better recognition
276
+ keywords: PHONE_CALL_KEYWORDS,
277
+ });
278
+ }
279
+ //# sourceMappingURL=deepgram.js.map