@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.
- package/README.md +91 -0
- package/dist/cli/program.d.ts +3 -0
- package/dist/cli/program.d.ts.map +1 -0
- package/dist/cli/program.js +46 -0
- package/dist/cli/program.js.map +1 -0
- package/dist/cli/shared.d.ts +18 -0
- package/dist/cli/shared.d.ts.map +1 -0
- package/dist/cli/shared.js +2 -0
- package/dist/cli/shared.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +5 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/call.d.ts +7 -0
- package/dist/commands/call.d.ts.map +1 -0
- package/dist/commands/call.js +409 -0
- package/dist/commands/call.js.map +1 -0
- package/dist/commands/config.d.ts +4 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +120 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/find-contact.d.ts +4 -0
- package/dist/commands/find-contact.d.ts.map +1 -0
- package/dist/commands/find-contact.js +57 -0
- package/dist/commands/find-contact.js.map +1 -0
- package/dist/commands/server.d.ts +7 -0
- package/dist/commands/server.d.ts.map +1 -0
- package/dist/commands/server.js +212 -0
- package/dist/commands/server.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/call/audio/mulaw.d.ts +35 -0
- package/dist/lib/call/audio/mulaw.d.ts.map +1 -0
- package/dist/lib/call/audio/mulaw.js +109 -0
- package/dist/lib/call/audio/mulaw.js.map +1 -0
- package/dist/lib/call/audio/pcm-utils.d.ts +62 -0
- package/dist/lib/call/audio/pcm-utils.d.ts.map +1 -0
- package/dist/lib/call/audio/pcm-utils.js +149 -0
- package/dist/lib/call/audio/pcm-utils.js.map +1 -0
- package/dist/lib/call/audio/resample.d.ts +34 -0
- package/dist/lib/call/audio/resample.d.ts.map +1 -0
- package/dist/lib/call/audio/resample.js +97 -0
- package/dist/lib/call/audio/resample.js.map +1 -0
- package/dist/lib/call/audio/streaming-decoder.d.ts +45 -0
- package/dist/lib/call/audio/streaming-decoder.d.ts.map +1 -0
- package/dist/lib/call/audio/streaming-decoder.js +110 -0
- package/dist/lib/call/audio/streaming-decoder.js.map +1 -0
- package/dist/lib/call/call-server.d.ts +110 -0
- package/dist/lib/call/call-server.d.ts.map +1 -0
- package/dist/lib/call/call-server.js +681 -0
- package/dist/lib/call/call-server.js.map +1 -0
- package/dist/lib/call/call-session.d.ts +133 -0
- package/dist/lib/call/call-session.d.ts.map +1 -0
- package/dist/lib/call/call-session.js +890 -0
- package/dist/lib/call/call-session.js.map +1 -0
- package/dist/lib/call/call-types.d.ts +133 -0
- package/dist/lib/call/call-types.d.ts.map +1 -0
- package/dist/lib/call/call-types.js +16 -0
- package/dist/lib/call/call-types.js.map +1 -0
- package/dist/lib/call/conversation-ai.d.ts +56 -0
- package/dist/lib/call/conversation-ai.d.ts.map +1 -0
- package/dist/lib/call/conversation-ai.js +276 -0
- package/dist/lib/call/conversation-ai.js.map +1 -0
- package/dist/lib/call/eval/codec-test.d.ts +45 -0
- package/dist/lib/call/eval/codec-test.d.ts.map +1 -0
- package/dist/lib/call/eval/codec-test.js +169 -0
- package/dist/lib/call/eval/codec-test.js.map +1 -0
- package/dist/lib/call/eval/conversation-scripts.d.ts +55 -0
- package/dist/lib/call/eval/conversation-scripts.d.ts.map +1 -0
- package/dist/lib/call/eval/conversation-scripts.js +359 -0
- package/dist/lib/call/eval/conversation-scripts.js.map +1 -0
- package/dist/lib/call/eval/eval-runner.d.ts +64 -0
- package/dist/lib/call/eval/eval-runner.d.ts.map +1 -0
- package/dist/lib/call/eval/eval-runner.js +369 -0
- package/dist/lib/call/eval/eval-runner.js.map +1 -0
- package/dist/lib/call/eval/index.d.ts +9 -0
- package/dist/lib/call/eval/index.d.ts.map +1 -0
- package/dist/lib/call/eval/index.js +9 -0
- package/dist/lib/call/eval/index.js.map +1 -0
- package/dist/lib/call/eval/integration-test-suite.d.ts +71 -0
- package/dist/lib/call/eval/integration-test-suite.d.ts.map +1 -0
- package/dist/lib/call/eval/integration-test-suite.js +519 -0
- package/dist/lib/call/eval/integration-test-suite.js.map +1 -0
- package/dist/lib/call/eval/turn-taking-test.d.ts +84 -0
- package/dist/lib/call/eval/turn-taking-test.d.ts.map +1 -0
- package/dist/lib/call/eval/turn-taking-test.js +260 -0
- package/dist/lib/call/eval/turn-taking-test.js.map +1 -0
- package/dist/lib/call/index.d.ts +12 -0
- package/dist/lib/call/index.d.ts.map +1 -0
- package/dist/lib/call/index.js +17 -0
- package/dist/lib/call/index.js.map +1 -0
- package/dist/lib/call/providers/deepgram.d.ts +81 -0
- package/dist/lib/call/providers/deepgram.d.ts.map +1 -0
- package/dist/lib/call/providers/deepgram.js +279 -0
- package/dist/lib/call/providers/deepgram.js.map +1 -0
- package/dist/lib/call/providers/elevenlabs.d.ts +78 -0
- package/dist/lib/call/providers/elevenlabs.d.ts.map +1 -0
- package/dist/lib/call/providers/elevenlabs.js +272 -0
- package/dist/lib/call/providers/elevenlabs.js.map +1 -0
- package/dist/lib/call/providers/local-deps.d.ts +18 -0
- package/dist/lib/call/providers/local-deps.d.ts.map +1 -0
- package/dist/lib/call/providers/local-deps.js +114 -0
- package/dist/lib/call/providers/local-deps.js.map +1 -0
- package/dist/lib/call/providers/twilio.d.ts +53 -0
- package/dist/lib/call/providers/twilio.d.ts.map +1 -0
- package/dist/lib/call/providers/twilio.js +173 -0
- package/dist/lib/call/providers/twilio.js.map +1 -0
- package/dist/lib/concierge-client-types.d.ts +68 -0
- package/dist/lib/concierge-client-types.d.ts.map +1 -0
- package/dist/lib/concierge-client-types.js +2 -0
- package/dist/lib/concierge-client-types.js.map +1 -0
- package/dist/lib/concierge-client.d.ts +29 -0
- package/dist/lib/concierge-client.d.ts.map +1 -0
- package/dist/lib/concierge-client.js +534 -0
- package/dist/lib/concierge-client.js.map +1 -0
- package/dist/lib/config.d.ts +9 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +66 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/output.d.ts +7 -0
- package/dist/lib/output.d.ts.map +1 -0
- package/dist/lib/output.js +114 -0
- package/dist/lib/output.js.map +1 -0
- package/dist/lib/utils/contact-extractor.d.ts +12 -0
- package/dist/lib/utils/contact-extractor.d.ts.map +1 -0
- package/dist/lib/utils/contact-extractor.js +159 -0
- package/dist/lib/utils/contact-extractor.js.map +1 -0
- package/dist/lib/utils/formatters.d.ts +15 -0
- package/dist/lib/utils/formatters.d.ts.map +1 -0
- package/dist/lib/utils/formatters.js +107 -0
- package/dist/lib/utils/formatters.js.map +1 -0
- package/dist/lib/utils/url-parser.d.ts +11 -0
- package/dist/lib/utils/url-parser.d.ts.map +1 -0
- package/dist/lib/utils/url-parser.js +103 -0
- package/dist/lib/utils/url-parser.js.map +1 -0
- 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":"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"}
|
|
@@ -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
|