@livekit/agents-plugin-deepgram 0.0.0-next-20260624041820

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 (75) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +17 -0
  3. package/dist/_utils.cjs +56 -0
  4. package/dist/_utils.cjs.map +1 -0
  5. package/dist/_utils.d.cts +12 -0
  6. package/dist/_utils.d.ts +12 -0
  7. package/dist/_utils.d.ts.map +1 -0
  8. package/dist/_utils.js +32 -0
  9. package/dist/_utils.js.map +1 -0
  10. package/dist/index.cjs +38 -0
  11. package/dist/index.cjs.map +1 -0
  12. package/dist/index.d.cts +4 -0
  13. package/dist/index.d.ts +4 -0
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +15 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/models.cjs +17 -0
  18. package/dist/models.cjs.map +1 -0
  19. package/dist/models.d.cts +6 -0
  20. package/dist/models.d.ts +6 -0
  21. package/dist/models.d.ts.map +1 -0
  22. package/dist/models.js +1 -0
  23. package/dist/models.js.map +1 -0
  24. package/dist/stt.cjs +496 -0
  25. package/dist/stt.cjs.map +1 -0
  26. package/dist/stt.d.cts +53 -0
  27. package/dist/stt.d.ts +53 -0
  28. package/dist/stt.d.ts.map +1 -0
  29. package/dist/stt.js +483 -0
  30. package/dist/stt.js.map +1 -0
  31. package/dist/stt.test.cjs +28 -0
  32. package/dist/stt.test.cjs.map +1 -0
  33. package/dist/stt.test.d.cts +2 -0
  34. package/dist/stt.test.d.ts +2 -0
  35. package/dist/stt.test.d.ts.map +1 -0
  36. package/dist/stt.test.js +27 -0
  37. package/dist/stt.test.js.map +1 -0
  38. package/dist/stt_v2.cjs +413 -0
  39. package/dist/stt_v2.cjs.map +1 -0
  40. package/dist/stt_v2.d.cts +97 -0
  41. package/dist/stt_v2.d.ts +97 -0
  42. package/dist/stt_v2.d.ts.map +1 -0
  43. package/dist/stt_v2.js +387 -0
  44. package/dist/stt_v2.js.map +1 -0
  45. package/dist/stt_v2.test.cjs +17 -0
  46. package/dist/stt_v2.test.cjs.map +1 -0
  47. package/dist/stt_v2.test.d.cts +2 -0
  48. package/dist/stt_v2.test.d.ts +2 -0
  49. package/dist/stt_v2.test.d.ts.map +1 -0
  50. package/dist/stt_v2.test.js +16 -0
  51. package/dist/stt_v2.test.js.map +1 -0
  52. package/dist/tts.cjs +385 -0
  53. package/dist/tts.cjs.map +1 -0
  54. package/dist/tts.d.cts +43 -0
  55. package/dist/tts.d.ts +43 -0
  56. package/dist/tts.d.ts.map +1 -0
  57. package/dist/tts.js +369 -0
  58. package/dist/tts.js.map +1 -0
  59. package/dist/tts.test.cjs +17 -0
  60. package/dist/tts.test.cjs.map +1 -0
  61. package/dist/tts.test.d.cts +2 -0
  62. package/dist/tts.test.d.ts +2 -0
  63. package/dist/tts.test.d.ts.map +1 -0
  64. package/dist/tts.test.js +16 -0
  65. package/dist/tts.test.js.map +1 -0
  66. package/package.json +54 -0
  67. package/src/_utils.ts +50 -0
  68. package/src/index.ts +20 -0
  69. package/src/models.ts +93 -0
  70. package/src/stt.test.ts +36 -0
  71. package/src/stt.ts +612 -0
  72. package/src/stt_v2.test.ts +19 -0
  73. package/src/stt_v2.ts +560 -0
  74. package/src/tts.test.ts +19 -0
  75. package/src/tts.ts +450 -0
package/dist/stt.js ADDED
@@ -0,0 +1,483 @@
1
+ import {
2
+ AudioByteStream,
3
+ AudioEnergyFilter,
4
+ Future,
5
+ Task,
6
+ createTimedString,
7
+ getBaseLanguage,
8
+ log,
9
+ mergeFrames,
10
+ normalizeLanguage,
11
+ stt,
12
+ waitForAbort
13
+ } from "@livekit/agents";
14
+ import { WebSocket } from "ws";
15
+ import { PeriodicCollector } from "./_utils.js";
16
+ const defaultSTTOptions = {
17
+ apiKey: process.env.DEEPGRAM_API_KEY,
18
+ language: "en-US",
19
+ detectLanguage: false,
20
+ interimResults: true,
21
+ punctuate: true,
22
+ model: "nova-3",
23
+ smartFormat: true,
24
+ noDelay: true,
25
+ endpointing: 25,
26
+ fillerWords: false,
27
+ sampleRate: 16e3,
28
+ numChannels: 1,
29
+ keywords: [],
30
+ keyterm: [],
31
+ profanityFilter: false,
32
+ redact: [],
33
+ dictation: false,
34
+ diarize: false,
35
+ numerals: false,
36
+ mipOptOut: false,
37
+ baseUrl: "wss://api.deepgram.com"
38
+ };
39
+ class STT extends stt.STT {
40
+ #opts;
41
+ #logger = log();
42
+ label = "deepgram.STT";
43
+ abortController = new AbortController();
44
+ get model() {
45
+ return this.#opts.model;
46
+ }
47
+ get provider() {
48
+ return "Deepgram";
49
+ }
50
+ constructor(opts = defaultSTTOptions) {
51
+ super({
52
+ streaming: true,
53
+ interimResults: opts.interimResults ?? defaultSTTOptions.interimResults,
54
+ alignedTranscript: "word"
55
+ });
56
+ if (opts.apiKey === void 0 && defaultSTTOptions.apiKey === void 0) {
57
+ throw new Error(
58
+ "Deepgram API key is required, whether as an argument or as $DEEPGRAM_API_KEY"
59
+ );
60
+ }
61
+ this.#opts = {
62
+ ...defaultSTTOptions,
63
+ ...opts,
64
+ language: opts.language ? normalizeLanguage(opts.language) : defaultSTTOptions.language
65
+ };
66
+ if (this.#opts.detectLanguage) {
67
+ this.#opts.language = void 0;
68
+ } else {
69
+ this.#opts.model = this.#validateModel(this.#opts.model, this.#opts.language);
70
+ }
71
+ }
72
+ async _recognize(buffer, abortSignal) {
73
+ const frame = mergeFrames(buffer);
74
+ const listenURL = new URL(`${this.#opts.baseUrl.replace(/^ws/, "http")}/v1/listen`);
75
+ const params = {
76
+ model: this.#opts.model,
77
+ punctuate: this.#opts.punctuate,
78
+ detect_language: this.#opts.detectLanguage,
79
+ smart_format: this.#opts.smartFormat,
80
+ keywords: this.#opts.keywords.map((x) => x.join(":")),
81
+ profanity_filter: this.#opts.profanityFilter,
82
+ numerals: this.#opts.numerals,
83
+ mip_opt_out: this.#opts.mipOptOut,
84
+ language: this.#opts.language,
85
+ ...this.#opts.keyterm.length > 0 ? { keyterm: this.#opts.keyterm } : {},
86
+ ...this.#opts.redact.length > 0 ? { redact: this.#opts.redact } : {}
87
+ };
88
+ Object.entries(params).forEach(([k, v]) => {
89
+ if (v !== void 0) {
90
+ if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
91
+ listenURL.searchParams.append(k, String(v));
92
+ } else {
93
+ v.forEach((x) => listenURL.searchParams.append(k, String(x)));
94
+ }
95
+ }
96
+ });
97
+ if (this.#opts.diarize) {
98
+ this.#logger.warn("speaker diarization is not supported in non-streaming mode, ignoring");
99
+ }
100
+ const resp = await fetch(listenURL, {
101
+ method: "POST",
102
+ headers: {
103
+ Authorization: `Token ${this.#opts.apiKey}`,
104
+ Accept: "application/json",
105
+ "Content-Type": "audio/wav"
106
+ },
107
+ body: new Blob([new Uint8Array(createWav(frame))], { type: "audio/wav" }),
108
+ signal: abortSignal ?? null
109
+ });
110
+ if (!resp.ok) {
111
+ throw new Error(
112
+ `Deepgram STT REST request failed with status ${resp.status}: ${await resp.text()}`
113
+ );
114
+ }
115
+ return prerecordedTranscriptionToSpeechEvent(this.#opts.language, await resp.json());
116
+ }
117
+ updateOptions(opts) {
118
+ const language = opts.language !== void 0 ? normalizeLanguage(opts.language) : this.#opts.language;
119
+ const model = opts.model !== void 0 || opts.language !== void 0 ? this.#validateModel(opts.model ?? this.#opts.model, language) : this.#opts.model;
120
+ this.#opts = {
121
+ ...this.#opts,
122
+ ...opts,
123
+ language,
124
+ model
125
+ };
126
+ }
127
+ #validateModel(model, language) {
128
+ if (language && getBaseLanguage(language) !== "en" && [
129
+ "nova-2-meeting",
130
+ "nova-2-phonecall",
131
+ "nova-2-finance",
132
+ "nova-2-conversationalai",
133
+ "nova-2-voicemail",
134
+ "nova-2-video",
135
+ "nova-2-medical",
136
+ "nova-2-drivethru",
137
+ "nova-2-automotive",
138
+ "nova-3-general"
139
+ ].includes(model)) {
140
+ this.#logger.warn(
141
+ `${model} does not support language ${language}, falling back to nova-2-general`
142
+ );
143
+ return "nova-2-general";
144
+ }
145
+ return model;
146
+ }
147
+ stream(options) {
148
+ if (this.#opts.detectLanguage || this.#opts.language === void 0) {
149
+ throw new Error(
150
+ "language detection is not supported in streaming mode, please disable it and specify a language"
151
+ );
152
+ }
153
+ return new SpeechStream(this, this.#opts, options == null ? void 0 : options.connOptions);
154
+ }
155
+ async close() {
156
+ this.abortController.abort();
157
+ }
158
+ }
159
+ class SpeechStream extends stt.SpeechStream {
160
+ #opts;
161
+ #audioEnergyFilter;
162
+ #logger = log();
163
+ #speaking = false;
164
+ #resetWS = new Future();
165
+ #requestId = "";
166
+ #audioDurationCollector;
167
+ label = "deepgram.SpeechStream";
168
+ constructor(stt2, opts, connOptions) {
169
+ super(stt2, opts.sampleRate, connOptions);
170
+ this.#opts = opts;
171
+ this.closed = false;
172
+ this.#audioEnergyFilter = new AudioEnergyFilter();
173
+ this.#audioDurationCollector = new PeriodicCollector(
174
+ (duration) => this.onAudioDurationReport(duration),
175
+ { duration: 5 }
176
+ );
177
+ }
178
+ async run() {
179
+ const maxRetry = 32;
180
+ let retries = 0;
181
+ let ws;
182
+ while (!this.input.closed && !this.closed) {
183
+ const streamURL = new URL(`${this.#opts.baseUrl}/v1/listen`);
184
+ const params = {
185
+ model: this.#opts.model,
186
+ punctuate: this.#opts.punctuate,
187
+ smart_format: this.#opts.smartFormat,
188
+ dictation: this.#opts.dictation,
189
+ diarize: this.#opts.diarize,
190
+ numerals: this.#opts.numerals,
191
+ no_delay: this.#opts.noDelay,
192
+ interim_results: this.#opts.interimResults,
193
+ encoding: "linear16",
194
+ vad_events: true,
195
+ sample_rate: this.#opts.sampleRate,
196
+ channels: this.#opts.numChannels,
197
+ endpointing: this.#opts.endpointing || false,
198
+ filler_words: this.#opts.fillerWords,
199
+ keywords: this.#opts.keywords.map((x) => x.join(":")),
200
+ keyterm: this.#opts.keyterm,
201
+ profanity_filter: this.#opts.profanityFilter,
202
+ redact: this.#opts.redact,
203
+ language: this.#opts.language,
204
+ mip_opt_out: this.#opts.mipOptOut
205
+ };
206
+ Object.entries(params).forEach(([k, v]) => {
207
+ if (v !== void 0) {
208
+ if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
209
+ streamURL.searchParams.append(k, String(v));
210
+ } else {
211
+ v.forEach((x) => streamURL.searchParams.append(k, String(x)));
212
+ }
213
+ }
214
+ });
215
+ ws = new WebSocket(streamURL, {
216
+ headers: { Authorization: `Token ${this.#opts.apiKey}` }
217
+ });
218
+ try {
219
+ await new Promise((resolve, reject) => {
220
+ ws.on("open", resolve);
221
+ ws.on("error", (error) => reject(error));
222
+ ws.on("close", (code) => reject(`WebSocket returned ${code}`));
223
+ });
224
+ await this.#runWS(ws);
225
+ } catch (e) {
226
+ if (!this.closed && !this.input.closed) {
227
+ if (retries >= maxRetry) {
228
+ throw new Error(`failed to connect to Deepgram after ${retries} attempts: ${e}`);
229
+ }
230
+ const delay = Math.min(retries * 5, 10);
231
+ retries++;
232
+ this.#logger.warn(
233
+ `failed to connect to Deepgram, retrying in ${delay} seconds: ${e} (${retries}/${maxRetry})`
234
+ );
235
+ await new Promise((resolve) => setTimeout(resolve, delay * 1e3));
236
+ } else {
237
+ this.#logger.warn(
238
+ `Deepgram disconnected, connection is closed: ${e} (inputClosed: ${this.input.closed}, isClosed: ${this.closed})`
239
+ );
240
+ }
241
+ }
242
+ }
243
+ this.closed = true;
244
+ }
245
+ updateOptions(opts) {
246
+ this.#opts = { ...this.#opts, ...opts };
247
+ this.#resetWS.resolve();
248
+ }
249
+ async #runWS(ws) {
250
+ this.#resetWS = new Future();
251
+ let closing = false;
252
+ const keepalive = setInterval(() => {
253
+ try {
254
+ ws.send(JSON.stringify({ type: "KeepAlive" }));
255
+ } catch {
256
+ clearInterval(keepalive);
257
+ return;
258
+ }
259
+ }, 5e3);
260
+ const wsMonitor = Task.from(async (controller) => {
261
+ const closed = new Promise(async (_, reject) => {
262
+ ws.once("close", (code, reason) => {
263
+ if (!closing) {
264
+ this.#logger.error(`WebSocket closed with code ${code}: ${reason}`);
265
+ reject(new Error("WebSocket closed"));
266
+ }
267
+ });
268
+ });
269
+ await Promise.race([closed, waitForAbort(controller.signal)]);
270
+ });
271
+ const sendTask = async () => {
272
+ const samples100Ms = Math.floor(this.#opts.sampleRate / 10);
273
+ const stream = new AudioByteStream(
274
+ this.#opts.sampleRate,
275
+ this.#opts.numChannels,
276
+ samples100Ms
277
+ );
278
+ const abortPromise = waitForAbort(this.abortSignal);
279
+ try {
280
+ while (!this.closed) {
281
+ const result = await Promise.race([this.input.next(), abortPromise]);
282
+ if (result === void 0) return;
283
+ if (result.done) {
284
+ break;
285
+ }
286
+ const data = result.value;
287
+ let frames;
288
+ if (data === SpeechStream.FLUSH_SENTINEL) {
289
+ frames = stream.flush();
290
+ this.#audioDurationCollector.flush();
291
+ } else if (data.sampleRate === this.#opts.sampleRate && data.channels === this.#opts.numChannels) {
292
+ frames = stream.write(data.data.buffer);
293
+ } else {
294
+ throw new Error(`sample rate or channel count of frame does not match`);
295
+ }
296
+ for await (const frame of frames) {
297
+ if (this.#audioEnergyFilter.pushFrame(frame)) {
298
+ const frameDuration = frame.samplesPerChannel / frame.sampleRate;
299
+ this.#audioDurationCollector.push(frameDuration);
300
+ ws.send(frame.data.buffer);
301
+ }
302
+ }
303
+ }
304
+ } finally {
305
+ closing = true;
306
+ ws.send(JSON.stringify({ type: "CloseStream" }));
307
+ wsMonitor.cancel();
308
+ }
309
+ };
310
+ const listenTask = Task.from(async (controller) => {
311
+ const putMessage = (message) => {
312
+ if (!this.queue.closed) {
313
+ try {
314
+ this.queue.put(message);
315
+ } catch (e) {
316
+ }
317
+ }
318
+ };
319
+ const listenMessage = new Promise((resolve, reject) => {
320
+ ws.on("message", (msg) => {
321
+ try {
322
+ const json = JSON.parse(msg.toString());
323
+ switch (json["type"]) {
324
+ case "SpeechStarted": {
325
+ if (this.#speaking) return;
326
+ this.#speaking = true;
327
+ putMessage({ type: stt.SpeechEventType.START_OF_SPEECH });
328
+ break;
329
+ }
330
+ // see this page:
331
+ // https://developers.deepgram.com/docs/understand-endpointing-interim-results#using-endpointing-speech_final
332
+ // for more information about the different types of events
333
+ case "Results": {
334
+ const metadata = json["metadata"];
335
+ const requestId = metadata["request_id"];
336
+ const isFinal = json["is_final"];
337
+ const isEndpoint = json["speech_final"];
338
+ this.#requestId = requestId;
339
+ const alternatives = liveTranscriptionToSpeechData(
340
+ this.#opts.language,
341
+ json,
342
+ this.startTimeOffset
343
+ );
344
+ if (alternatives[0] && alternatives[0].text) {
345
+ if (!this.#speaking) {
346
+ this.#speaking = true;
347
+ putMessage({
348
+ type: stt.SpeechEventType.START_OF_SPEECH
349
+ });
350
+ }
351
+ if (isFinal) {
352
+ putMessage({
353
+ type: stt.SpeechEventType.FINAL_TRANSCRIPT,
354
+ alternatives: [alternatives[0], ...alternatives.slice(1)]
355
+ });
356
+ } else {
357
+ putMessage({
358
+ type: stt.SpeechEventType.INTERIM_TRANSCRIPT,
359
+ alternatives: [alternatives[0], ...alternatives.slice(1)]
360
+ });
361
+ }
362
+ }
363
+ if (isEndpoint && this.#speaking) {
364
+ this.#speaking = false;
365
+ putMessage({ type: stt.SpeechEventType.END_OF_SPEECH });
366
+ }
367
+ break;
368
+ }
369
+ case "Metadata": {
370
+ break;
371
+ }
372
+ default: {
373
+ this.#logger.child({ msg: json }).warn("received unexpected message from Deepgram");
374
+ break;
375
+ }
376
+ }
377
+ if (this.closed || closing) {
378
+ resolve();
379
+ }
380
+ } catch (err) {
381
+ this.#logger.error(`STT: Error processing message: ${msg}`);
382
+ reject(err);
383
+ }
384
+ });
385
+ });
386
+ await Promise.race([listenMessage, waitForAbort(controller.signal)]);
387
+ }, this.abortController);
388
+ await Promise.race([
389
+ this.#resetWS.await,
390
+ Promise.all([sendTask(), listenTask.result, wsMonitor])
391
+ ]);
392
+ closing = true;
393
+ ws.close();
394
+ clearInterval(keepalive);
395
+ }
396
+ onAudioDurationReport(duration) {
397
+ const usageEvent = {
398
+ type: stt.SpeechEventType.RECOGNITION_USAGE,
399
+ requestId: this.#requestId,
400
+ recognitionUsage: {
401
+ audioDuration: duration
402
+ }
403
+ };
404
+ this.queue.put(usageEvent);
405
+ }
406
+ }
407
+ function createWav(frame) {
408
+ const bitsPerSample = 16;
409
+ const byteRate = frame.sampleRate * frame.channels * bitsPerSample / 8;
410
+ const blockAlign = frame.channels * bitsPerSample / 8;
411
+ const header = Buffer.alloc(44);
412
+ header.write("RIFF", 0);
413
+ header.writeUInt32LE(36 + frame.data.byteLength, 4);
414
+ header.write("WAVE", 8);
415
+ header.write("fmt ", 12);
416
+ header.writeUInt32LE(16, 16);
417
+ header.writeUInt16LE(1, 20);
418
+ header.writeUInt16LE(frame.channels, 22);
419
+ header.writeUInt32LE(frame.sampleRate, 24);
420
+ header.writeUInt32LE(byteRate, 28);
421
+ header.writeUInt16LE(blockAlign, 32);
422
+ header.writeUInt16LE(bitsPerSample, 34);
423
+ header.write("data", 36);
424
+ header.writeUInt32LE(frame.data.byteLength, 40);
425
+ const pcm = Buffer.from(frame.data.buffer, frame.data.byteOffset, frame.data.byteLength);
426
+ return Buffer.concat([header, pcm]);
427
+ }
428
+ const liveTranscriptionToSpeechData = (language, data, startTimeOffset = 0) => {
429
+ const alts = data["channel"]["alternatives"];
430
+ return alts.map((alt) => {
431
+ var _a;
432
+ const wordsData = alt["words"] ?? [];
433
+ const detectedLanguage = language === "multi" ? ((_a = alt["languages"]) == null ? void 0 : _a[0]) ?? language : alt["language"] ?? language;
434
+ return {
435
+ language: normalizeLanguage(detectedLanguage),
436
+ startTime: wordsData.length ? wordsData[0]["start"] + startTimeOffset : startTimeOffset,
437
+ endTime: wordsData.length ? wordsData[wordsData.length - 1]["end"] + startTimeOffset : startTimeOffset,
438
+ confidence: alt["confidence"],
439
+ text: alt["transcript"],
440
+ words: wordsData.map(
441
+ (word) => createTimedString({
442
+ text: word["word"] ?? "",
443
+ startTime: (word["start"] ?? 0) + startTimeOffset,
444
+ endTime: (word["end"] ?? 0) + startTimeOffset,
445
+ confidence: word["confidence"] ?? 0,
446
+ startTimeOffset
447
+ })
448
+ )
449
+ };
450
+ });
451
+ };
452
+ function prerecordedTranscriptionToSpeechEvent(language, data) {
453
+ const channel = data["results"]["channels"][0];
454
+ const detectedLanguage = channel["detected_language"] ?? "";
455
+ const alternatives = channel["alternatives"].map((alt) => {
456
+ const wordsData = alt["words"] ?? [];
457
+ return {
458
+ language: normalizeLanguage(language ?? detectedLanguage),
459
+ startTime: wordsData.length ? wordsData[0]["start"] : 0,
460
+ endTime: wordsData.length ? wordsData[wordsData.length - 1]["end"] : 0,
461
+ confidence: alt["confidence"],
462
+ text: alt["transcript"],
463
+ words: wordsData.map(
464
+ (word) => createTimedString({
465
+ text: word["word"] ?? "",
466
+ startTime: word["start"] ?? 0,
467
+ endTime: word["end"] ?? 0,
468
+ confidence: word["confidence"] ?? 0
469
+ })
470
+ )
471
+ };
472
+ });
473
+ return {
474
+ type: stt.SpeechEventType.FINAL_TRANSCRIPT,
475
+ requestId: data["metadata"]["request_id"],
476
+ ...alternatives.length ? { alternatives } : {}
477
+ };
478
+ }
479
+ export {
480
+ STT,
481
+ SpeechStream
482
+ };
483
+ //# sourceMappingURL=stt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/stt.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport {\n type APIConnectOptions,\n type AudioBuffer,\n AudioByteStream,\n AudioEnergyFilter,\n Future,\n Task,\n createTimedString,\n getBaseLanguage,\n log,\n mergeFrames,\n normalizeLanguage,\n stt,\n waitForAbort,\n} from '@livekit/agents';\nimport type { AudioFrame } from '@livekit/rtc-node';\nimport { WebSocket } from 'ws';\nimport { PeriodicCollector } from './_utils.js';\nimport type { STTLanguages, STTModels } from './models.js';\n\nexport interface STTOptions {\n apiKey?: string;\n language?: STTLanguages | string;\n detectLanguage: boolean;\n interimResults: boolean;\n punctuate: boolean;\n model: STTModels;\n smartFormat: boolean;\n noDelay: boolean;\n endpointing: number;\n fillerWords: boolean;\n sampleRate: number;\n numChannels: number;\n keywords: [string, number][];\n keyterm: string[];\n profanityFilter: boolean;\n /**\n * Redact sensitive information from the transcription. Accepts a single value or list of\n * values. Supported values: \"pci\", \"numbers\", \"ssn\", \"true\" (redact all).\n * See https://developers.deepgram.com/docs/redaction for details.\n */\n redact: string | string[];\n dictation: boolean;\n diarize: boolean;\n numerals: boolean;\n mipOptOut: boolean;\n baseUrl: string;\n}\n\nconst defaultSTTOptions: STTOptions = {\n apiKey: process.env.DEEPGRAM_API_KEY,\n language: 'en-US',\n detectLanguage: false,\n interimResults: true,\n punctuate: true,\n model: 'nova-3',\n smartFormat: true,\n noDelay: true,\n endpointing: 25,\n fillerWords: false,\n sampleRate: 16000,\n numChannels: 1,\n keywords: [],\n keyterm: [],\n profanityFilter: false,\n redact: [],\n dictation: false,\n diarize: false,\n numerals: false,\n mipOptOut: false,\n baseUrl: 'wss://api.deepgram.com',\n};\n\nexport class STT extends stt.STT {\n #opts: STTOptions;\n #logger = log();\n label = 'deepgram.STT';\n private abortController = new AbortController();\n\n get model(): string {\n return this.#opts.model;\n }\n\n get provider(): string {\n return 'Deepgram';\n }\n\n constructor(opts: Partial<STTOptions> = defaultSTTOptions) {\n super({\n streaming: true,\n interimResults: opts.interimResults ?? defaultSTTOptions.interimResults,\n alignedTranscript: 'word',\n });\n if (opts.apiKey === undefined && defaultSTTOptions.apiKey === undefined) {\n throw new Error(\n 'Deepgram API key is required, whether as an argument or as $DEEPGRAM_API_KEY',\n );\n }\n\n this.#opts = {\n ...defaultSTTOptions,\n ...opts,\n language: opts.language ? normalizeLanguage(opts.language) : defaultSTTOptions.language,\n };\n\n if (this.#opts.detectLanguage) {\n this.#opts.language = undefined;\n } else {\n this.#opts.model = this.#validateModel(this.#opts.model, this.#opts.language);\n }\n }\n\n async _recognize(buffer: AudioBuffer, abortSignal?: AbortSignal): Promise<stt.SpeechEvent> {\n const frame = mergeFrames(buffer);\n const listenURL = new URL(`${this.#opts.baseUrl.replace(/^ws/, 'http')}/v1/listen`);\n const params = {\n model: this.#opts.model,\n punctuate: this.#opts.punctuate,\n detect_language: this.#opts.detectLanguage,\n smart_format: this.#opts.smartFormat,\n keywords: this.#opts.keywords.map((x) => x.join(':')),\n profanity_filter: this.#opts.profanityFilter,\n numerals: this.#opts.numerals,\n mip_opt_out: this.#opts.mipOptOut,\n language: this.#opts.language,\n ...(this.#opts.keyterm.length > 0 ? { keyterm: this.#opts.keyterm } : {}),\n ...(this.#opts.redact.length > 0 ? { redact: this.#opts.redact } : {}),\n };\n\n Object.entries(params).forEach(([k, v]) => {\n if (v !== undefined) {\n if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean') {\n listenURL.searchParams.append(k, String(v));\n } else {\n v.forEach((x) => listenURL.searchParams.append(k, String(x)));\n }\n }\n });\n\n if (this.#opts.diarize) {\n this.#logger.warn('speaker diarization is not supported in non-streaming mode, ignoring');\n }\n\n const resp = await fetch(listenURL, {\n method: 'POST',\n headers: {\n Authorization: `Token ${this.#opts.apiKey}`,\n Accept: 'application/json',\n 'Content-Type': 'audio/wav',\n },\n body: new Blob([new Uint8Array(createWav(frame))], { type: 'audio/wav' }),\n signal: abortSignal ?? null,\n });\n\n if (!resp.ok) {\n throw new Error(\n `Deepgram STT REST request failed with status ${resp.status}: ${await resp.text()}`,\n );\n }\n\n return prerecordedTranscriptionToSpeechEvent(this.#opts.language, await resp.json());\n }\n\n updateOptions(opts: Partial<STTOptions>) {\n const language =\n opts.language !== undefined ? normalizeLanguage(opts.language) : this.#opts.language;\n const model =\n opts.model !== undefined || opts.language !== undefined\n ? this.#validateModel(opts.model ?? this.#opts.model, language)\n : this.#opts.model;\n\n this.#opts = {\n ...this.#opts,\n ...opts,\n language,\n model,\n };\n }\n\n #validateModel(model: STTModels, language?: string) {\n if (\n language &&\n getBaseLanguage(language) !== 'en' &&\n [\n 'nova-2-meeting',\n 'nova-2-phonecall',\n 'nova-2-finance',\n 'nova-2-conversationalai',\n 'nova-2-voicemail',\n 'nova-2-video',\n 'nova-2-medical',\n 'nova-2-drivethru',\n 'nova-2-automotive',\n 'nova-3-general',\n ].includes(model)\n ) {\n this.#logger.warn(\n `${model} does not support language ${language}, falling back to nova-2-general`,\n );\n return 'nova-2-general';\n }\n\n return model;\n }\n\n stream(options?: { connOptions?: APIConnectOptions }): SpeechStream {\n // Deepgram only supports language detection for prerecorded audio, not streaming.\n if (this.#opts.detectLanguage || this.#opts.language === undefined) {\n throw new Error(\n 'language detection is not supported in streaming mode, please disable it and specify a language',\n );\n }\n return new SpeechStream(this, this.#opts, options?.connOptions);\n }\n\n async close() {\n this.abortController.abort();\n }\n}\n\nexport class SpeechStream extends stt.SpeechStream {\n #opts: STTOptions;\n #audioEnergyFilter: AudioEnergyFilter;\n #logger = log();\n #speaking = false;\n #resetWS = new Future();\n #requestId = '';\n #audioDurationCollector: PeriodicCollector<number>;\n label = 'deepgram.SpeechStream';\n\n constructor(stt: STT, opts: STTOptions, connOptions?: APIConnectOptions) {\n super(stt, opts.sampleRate, connOptions);\n this.#opts = opts;\n this.closed = false;\n this.#audioEnergyFilter = new AudioEnergyFilter();\n this.#audioDurationCollector = new PeriodicCollector(\n (duration) => this.onAudioDurationReport(duration),\n { duration: 5.0 },\n );\n }\n\n protected async run() {\n const maxRetry = 32;\n let retries = 0;\n let ws: WebSocket;\n\n while (!this.input.closed && !this.closed) {\n const streamURL = new URL(`${this.#opts.baseUrl}/v1/listen`);\n const params = {\n model: this.#opts.model,\n punctuate: this.#opts.punctuate,\n smart_format: this.#opts.smartFormat,\n dictation: this.#opts.dictation,\n diarize: this.#opts.diarize,\n numerals: this.#opts.numerals,\n no_delay: this.#opts.noDelay,\n interim_results: this.#opts.interimResults,\n encoding: 'linear16',\n vad_events: true,\n sample_rate: this.#opts.sampleRate,\n channels: this.#opts.numChannels,\n endpointing: this.#opts.endpointing || false,\n filler_words: this.#opts.fillerWords,\n keywords: this.#opts.keywords.map((x) => x.join(':')),\n keyterm: this.#opts.keyterm,\n profanity_filter: this.#opts.profanityFilter,\n redact: this.#opts.redact,\n language: this.#opts.language,\n mip_opt_out: this.#opts.mipOptOut,\n };\n Object.entries(params).forEach(([k, v]) => {\n if (v !== undefined) {\n if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean') {\n streamURL.searchParams.append(k, String(v));\n } else {\n v.forEach((x) => streamURL.searchParams.append(k, String(x)));\n }\n }\n });\n\n ws = new WebSocket(streamURL, {\n headers: { Authorization: `Token ${this.#opts.apiKey}` },\n });\n\n try {\n await new Promise((resolve, reject) => {\n ws.on('open', resolve);\n ws.on('error', (error) => reject(error));\n ws.on('close', (code) => reject(`WebSocket returned ${code}`));\n });\n\n await this.#runWS(ws);\n } catch (e) {\n if (!this.closed && !this.input.closed) {\n if (retries >= maxRetry) {\n throw new Error(`failed to connect to Deepgram after ${retries} attempts: ${e}`);\n }\n\n const delay = Math.min(retries * 5, 10);\n retries++;\n\n this.#logger.warn(\n `failed to connect to Deepgram, retrying in ${delay} seconds: ${e} (${retries}/${maxRetry})`,\n );\n await new Promise((resolve) => setTimeout(resolve, delay * 1000));\n } else {\n this.#logger.warn(\n `Deepgram disconnected, connection is closed: ${e} (inputClosed: ${this.input.closed}, isClosed: ${this.closed})`,\n );\n }\n }\n }\n\n this.closed = true;\n }\n\n updateOptions(opts: Partial<STTOptions>) {\n this.#opts = { ...this.#opts, ...opts };\n this.#resetWS.resolve();\n }\n\n async #runWS(ws: WebSocket) {\n this.#resetWS = new Future();\n let closing = false;\n\n const keepalive = setInterval(() => {\n try {\n ws.send(JSON.stringify({ type: 'KeepAlive' }));\n } catch {\n clearInterval(keepalive);\n return;\n }\n }, 5000);\n\n // gets cancelled also when sendTask is complete\n const wsMonitor = Task.from(async (controller) => {\n const closed = new Promise<void>(async (_, reject) => {\n ws.once('close', (code, reason) => {\n if (!closing) {\n this.#logger.error(`WebSocket closed with code ${code}: ${reason}`);\n reject(new Error('WebSocket closed'));\n }\n });\n });\n\n await Promise.race([closed, waitForAbort(controller.signal)]);\n });\n\n const sendTask = async () => {\n const samples100Ms = Math.floor(this.#opts.sampleRate / 10);\n const stream = new AudioByteStream(\n this.#opts.sampleRate,\n this.#opts.numChannels,\n samples100Ms,\n );\n\n // waitForAbort internally sets up an abort listener on the abort signal\n // we need to put it outside loop to avoid constant re-registration of the listener\n const abortPromise = waitForAbort(this.abortSignal);\n\n try {\n while (!this.closed) {\n const result = await Promise.race([this.input.next(), abortPromise]);\n\n if (result === undefined) return; // aborted\n if (result.done) {\n break;\n }\n\n const data = result.value;\n\n let frames: AudioFrame[];\n if (data === SpeechStream.FLUSH_SENTINEL) {\n frames = stream.flush();\n this.#audioDurationCollector.flush();\n } else if (\n data.sampleRate === this.#opts.sampleRate &&\n data.channels === this.#opts.numChannels\n ) {\n frames = stream.write(data.data.buffer as ArrayBuffer);\n } else {\n throw new Error(`sample rate or channel count of frame does not match`);\n }\n\n for await (const frame of frames) {\n if (this.#audioEnergyFilter.pushFrame(frame)) {\n const frameDuration = frame.samplesPerChannel / frame.sampleRate;\n this.#audioDurationCollector.push(frameDuration);\n ws.send(frame.data.buffer);\n }\n }\n }\n } finally {\n closing = true;\n ws.send(JSON.stringify({ type: 'CloseStream' }));\n wsMonitor.cancel();\n }\n };\n\n const listenTask = Task.from(async (controller) => {\n const putMessage = (message: stt.SpeechEvent) => {\n if (!this.queue.closed) {\n try {\n this.queue.put(message);\n } catch (e) {\n // ignore\n }\n }\n };\n\n const listenMessage = new Promise<void>((resolve, reject) => {\n ws.on('message', (msg) => {\n try {\n const json = JSON.parse(msg.toString());\n switch (json['type']) {\n case 'SpeechStarted': {\n // This is a normal case. Deepgram's SpeechStarted events\n // are not correlated with speech_final or utterance end.\n // It's possible that we receive two in a row without an endpoint\n // It's also possible we receive a transcript without a SpeechStarted event.\n if (this.#speaking) return;\n this.#speaking = true;\n putMessage({ type: stt.SpeechEventType.START_OF_SPEECH });\n break;\n }\n // see this page:\n // https://developers.deepgram.com/docs/understand-endpointing-interim-results#using-endpointing-speech_final\n // for more information about the different types of events\n case 'Results': {\n const metadata = json['metadata'];\n const requestId = metadata['request_id'];\n const isFinal = json['is_final'];\n const isEndpoint = json['speech_final'];\n this.#requestId = requestId;\n\n const alternatives = liveTranscriptionToSpeechData(\n this.#opts.language!,\n json,\n this.startTimeOffset,\n );\n\n // If, for some reason, we didn't get a SpeechStarted event but we got\n // a transcript with text, we should start speaking. It's rare but has\n // been observed.\n if (alternatives[0] && alternatives[0].text) {\n if (!this.#speaking) {\n this.#speaking = true;\n putMessage({\n type: stt.SpeechEventType.START_OF_SPEECH,\n });\n }\n\n if (isFinal) {\n putMessage({\n type: stt.SpeechEventType.FINAL_TRANSCRIPT,\n alternatives: [alternatives[0], ...alternatives.slice(1)],\n });\n } else {\n putMessage({\n type: stt.SpeechEventType.INTERIM_TRANSCRIPT,\n alternatives: [alternatives[0], ...alternatives.slice(1)],\n });\n }\n }\n\n // if we receive an endpoint, only end the speech if\n // we either had a SpeechStarted event or we have a seen\n // a non-empty transcript (deepgram doesn't have a SpeechEnded event)\n if (isEndpoint && this.#speaking) {\n this.#speaking = false;\n putMessage({ type: stt.SpeechEventType.END_OF_SPEECH });\n }\n\n break;\n }\n case 'Metadata': {\n break;\n }\n default: {\n this.#logger.child({ msg: json }).warn('received unexpected message from Deepgram');\n break;\n }\n }\n\n if (this.closed || closing) {\n resolve();\n }\n } catch (err) {\n this.#logger.error(`STT: Error processing message: ${msg}`);\n reject(err);\n }\n });\n });\n\n await Promise.race([listenMessage, waitForAbort(controller.signal)]);\n }, this.abortController);\n\n await Promise.race([\n this.#resetWS.await,\n Promise.all([sendTask(), listenTask.result, wsMonitor]),\n ]);\n closing = true;\n ws.close();\n clearInterval(keepalive);\n }\n\n private onAudioDurationReport(duration: number) {\n const usageEvent: stt.SpeechEvent = {\n type: stt.SpeechEventType.RECOGNITION_USAGE,\n requestId: this.#requestId,\n recognitionUsage: {\n audioDuration: duration,\n },\n };\n this.queue.put(usageEvent);\n }\n}\n\nfunction createWav(frame: AudioFrame): Buffer {\n const bitsPerSample = 16;\n const byteRate = (frame.sampleRate * frame.channels * bitsPerSample) / 8;\n const blockAlign = (frame.channels * bitsPerSample) / 8;\n\n const header = Buffer.alloc(44);\n header.write('RIFF', 0);\n header.writeUInt32LE(36 + frame.data.byteLength, 4);\n header.write('WAVE', 8);\n header.write('fmt ', 12);\n header.writeUInt32LE(16, 16);\n header.writeUInt16LE(1, 20);\n header.writeUInt16LE(frame.channels, 22);\n header.writeUInt32LE(frame.sampleRate, 24);\n header.writeUInt32LE(byteRate, 28);\n header.writeUInt16LE(blockAlign, 32);\n header.writeUInt16LE(bitsPerSample, 34);\n header.write('data', 36);\n header.writeUInt32LE(frame.data.byteLength, 40);\n\n const pcm = Buffer.from(frame.data.buffer, frame.data.byteOffset, frame.data.byteLength);\n return Buffer.concat([header, pcm]);\n}\n\nconst liveTranscriptionToSpeechData = (\n language: STTLanguages | string,\n data: { [id: string]: any },\n startTimeOffset: number = 0,\n): stt.SpeechData[] => {\n const alts: any[] = data['channel']['alternatives'];\n\n return alts.map((alt) => {\n const wordsData: any[] = alt['words'] ?? [];\n const detectedLanguage =\n language === 'multi' ? alt['languages']?.[0] ?? language : alt['language'] ?? language;\n\n return {\n language: normalizeLanguage(detectedLanguage),\n startTime: wordsData.length ? wordsData[0]['start'] + startTimeOffset : startTimeOffset,\n endTime: wordsData.length\n ? wordsData[wordsData.length - 1]['end'] + startTimeOffset\n : startTimeOffset,\n confidence: alt['confidence'],\n text: alt['transcript'],\n words: wordsData.map((word) =>\n createTimedString({\n text: word['word'] ?? '',\n startTime: (word['start'] ?? 0) + startTimeOffset,\n endTime: (word['end'] ?? 0) + startTimeOffset,\n confidence: word['confidence'] ?? 0.0,\n startTimeOffset,\n }),\n ),\n };\n });\n};\n\nfunction prerecordedTranscriptionToSpeechEvent(\n language: STTLanguages | string | undefined,\n data: { [id: string]: any },\n): stt.SpeechEvent {\n const channel = data['results']['channels'][0];\n const detectedLanguage = channel['detected_language'] ?? '';\n const alternatives = channel['alternatives'].map((alt: { [id: string]: any }) => {\n const wordsData: any[] = alt['words'] ?? [];\n\n return {\n language: normalizeLanguage(language ?? detectedLanguage),\n startTime: wordsData.length ? wordsData[0]['start'] : 0,\n endTime: wordsData.length ? wordsData[wordsData.length - 1]['end'] : 0,\n confidence: alt['confidence'],\n text: alt['transcript'],\n words: wordsData.map((word) =>\n createTimedString({\n text: word['word'] ?? '',\n startTime: word['start'] ?? 0,\n endTime: word['end'] ?? 0,\n confidence: word['confidence'] ?? 0.0,\n }),\n ),\n };\n }) as stt.SpeechData[];\n\n return {\n type: stt.SpeechEventType.FINAL_TRANSCRIPT,\n requestId: data['metadata']['request_id'],\n ...(alternatives.length\n ? { alternatives: alternatives as [stt.SpeechData, ...stt.SpeechData[]] }\n : {}),\n };\n}\n"],"mappings":"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","names":["stt"]}
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ var import_agents_plugin_silero = require("@livekit/agents-plugin-silero");
3
+ var import_agents_plugins_test = require("@livekit/agents-plugins-test");
4
+ var import_vitest = require("vitest");
5
+ var import_stt = require("./stt.cjs");
6
+ const hasDeepgramApiKey = Boolean(process.env.DEEPGRAM_API_KEY);
7
+ (0, import_vitest.describe)("Deepgram streaming language detection", () => {
8
+ (0, import_vitest.it)("throws when starting a stream with detectLanguage enabled", () => {
9
+ const stt2 = new import_stt.STT({ apiKey: "test", detectLanguage: true });
10
+ (0, import_vitest.expect)(() => stt2.stream()).toThrow("language detection is not supported in streaming mode");
11
+ });
12
+ (0, import_vitest.it)("allows streaming with an explicit language", () => {
13
+ const stt2 = new import_stt.STT({ apiKey: "test", language: "en-US" });
14
+ const stream = stt2.stream();
15
+ stream.close();
16
+ });
17
+ });
18
+ if (hasDeepgramApiKey) {
19
+ (0, import_vitest.describe)("Deepgram", async () => {
20
+ await (0, import_agents_plugins_test.stt)(new import_stt.STT(), await import_agents_plugin_silero.VAD.load(), { nonStreaming: false });
21
+ });
22
+ } else {
23
+ (0, import_vitest.describe)("Deepgram", () => {
24
+ import_vitest.it.skip("requires DEEPGRAM_API_KEY", () => {
25
+ });
26
+ });
27
+ }
28
+ //# sourceMappingURL=stt.test.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/stt.test.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { VAD } from '@livekit/agents-plugin-silero';\nimport { stt } from '@livekit/agents-plugins-test';\nimport { describe, expect, it } from 'vitest';\nimport { STT } from './stt.js';\n\nconst hasDeepgramApiKey = Boolean(process.env.DEEPGRAM_API_KEY);\n\ndescribe('Deepgram streaming language detection', () => {\n // Deepgram only supports language detection for prerecorded audio, so a\n // streaming session must reject it rather than silently default to English.\n // Mirrors livekit-plugins-deepgram (Python).\n it('throws when starting a stream with detectLanguage enabled', () => {\n const stt = new STT({ apiKey: 'test', detectLanguage: true });\n expect(() => stt.stream()).toThrow('language detection is not supported in streaming mode');\n });\n\n it('allows streaming with an explicit language', () => {\n const stt = new STT({ apiKey: 'test', language: 'en-US' });\n const stream = stt.stream();\n // Close immediately so the connection loop never starts (no network in unit tests).\n stream.close();\n });\n});\n\nif (hasDeepgramApiKey) {\n describe('Deepgram', async () => {\n await stt(new STT(), await VAD.load(), { nonStreaming: false });\n });\n} else {\n describe('Deepgram', () => {\n it.skip('requires DEEPGRAM_API_KEY', () => {});\n });\n}\n"],"mappings":";AAGA,kCAAoB;AACpB,iCAAoB;AACpB,oBAAqC;AACrC,iBAAoB;AAEpB,MAAM,oBAAoB,QAAQ,QAAQ,IAAI,gBAAgB;AAAA,IAE9D,wBAAS,yCAAyC,MAAM;AAItD,wBAAG,6DAA6D,MAAM;AACpE,UAAMA,OAAM,IAAI,eAAI,EAAE,QAAQ,QAAQ,gBAAgB,KAAK,CAAC;AAC5D,8BAAO,MAAMA,KAAI,OAAO,CAAC,EAAE,QAAQ,uDAAuD;AAAA,EAC5F,CAAC;AAED,wBAAG,8CAA8C,MAAM;AACrD,UAAMA,OAAM,IAAI,eAAI,EAAE,QAAQ,QAAQ,UAAU,QAAQ,CAAC;AACzD,UAAM,SAASA,KAAI,OAAO;AAE1B,WAAO,MAAM;AAAA,EACf,CAAC;AACH,CAAC;AAED,IAAI,mBAAmB;AACrB,8BAAS,YAAY,YAAY;AAC/B,cAAM,gCAAI,IAAI,eAAI,GAAG,MAAM,gCAAI,KAAK,GAAG,EAAE,cAAc,MAAM,CAAC;AAAA,EAChE,CAAC;AACH,OAAO;AACL,8BAAS,YAAY,MAAM;AACzB,qBAAG,KAAK,6BAA6B,MAAM;AAAA,IAAC,CAAC;AAAA,EAC/C,CAAC;AACH;","names":["stt"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=stt.test.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=stt.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stt.test.d.ts","sourceRoot":"","sources":["../src/stt.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,27 @@
1
+ import { VAD } from "@livekit/agents-plugin-silero";
2
+ import { stt } from "@livekit/agents-plugins-test";
3
+ import { describe, expect, it } from "vitest";
4
+ import { STT } from "./stt.js";
5
+ const hasDeepgramApiKey = Boolean(process.env.DEEPGRAM_API_KEY);
6
+ describe("Deepgram streaming language detection", () => {
7
+ it("throws when starting a stream with detectLanguage enabled", () => {
8
+ const stt2 = new STT({ apiKey: "test", detectLanguage: true });
9
+ expect(() => stt2.stream()).toThrow("language detection is not supported in streaming mode");
10
+ });
11
+ it("allows streaming with an explicit language", () => {
12
+ const stt2 = new STT({ apiKey: "test", language: "en-US" });
13
+ const stream = stt2.stream();
14
+ stream.close();
15
+ });
16
+ });
17
+ if (hasDeepgramApiKey) {
18
+ describe("Deepgram", async () => {
19
+ await stt(new STT(), await VAD.load(), { nonStreaming: false });
20
+ });
21
+ } else {
22
+ describe("Deepgram", () => {
23
+ it.skip("requires DEEPGRAM_API_KEY", () => {
24
+ });
25
+ });
26
+ }
27
+ //# sourceMappingURL=stt.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/stt.test.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { VAD } from '@livekit/agents-plugin-silero';\nimport { stt } from '@livekit/agents-plugins-test';\nimport { describe, expect, it } from 'vitest';\nimport { STT } from './stt.js';\n\nconst hasDeepgramApiKey = Boolean(process.env.DEEPGRAM_API_KEY);\n\ndescribe('Deepgram streaming language detection', () => {\n // Deepgram only supports language detection for prerecorded audio, so a\n // streaming session must reject it rather than silently default to English.\n // Mirrors livekit-plugins-deepgram (Python).\n it('throws when starting a stream with detectLanguage enabled', () => {\n const stt = new STT({ apiKey: 'test', detectLanguage: true });\n expect(() => stt.stream()).toThrow('language detection is not supported in streaming mode');\n });\n\n it('allows streaming with an explicit language', () => {\n const stt = new STT({ apiKey: 'test', language: 'en-US' });\n const stream = stt.stream();\n // Close immediately so the connection loop never starts (no network in unit tests).\n stream.close();\n });\n});\n\nif (hasDeepgramApiKey) {\n describe('Deepgram', async () => {\n await stt(new STT(), await VAD.load(), { nonStreaming: false });\n });\n} else {\n describe('Deepgram', () => {\n it.skip('requires DEEPGRAM_API_KEY', () => {});\n });\n}\n"],"mappings":"AAGA,SAAS,WAAW;AACpB,SAAS,WAAW;AACpB,SAAS,UAAU,QAAQ,UAAU;AACrC,SAAS,WAAW;AAEpB,MAAM,oBAAoB,QAAQ,QAAQ,IAAI,gBAAgB;AAE9D,SAAS,yCAAyC,MAAM;AAItD,KAAG,6DAA6D,MAAM;AACpE,UAAMA,OAAM,IAAI,IAAI,EAAE,QAAQ,QAAQ,gBAAgB,KAAK,CAAC;AAC5D,WAAO,MAAMA,KAAI,OAAO,CAAC,EAAE,QAAQ,uDAAuD;AAAA,EAC5F,CAAC;AAED,KAAG,8CAA8C,MAAM;AACrD,UAAMA,OAAM,IAAI,IAAI,EAAE,QAAQ,QAAQ,UAAU,QAAQ,CAAC;AACzD,UAAM,SAASA,KAAI,OAAO;AAE1B,WAAO,MAAM;AAAA,EACf,CAAC;AACH,CAAC;AAED,IAAI,mBAAmB;AACrB,WAAS,YAAY,YAAY;AAC/B,UAAM,IAAI,IAAI,IAAI,GAAG,MAAM,IAAI,KAAK,GAAG,EAAE,cAAc,MAAM,CAAC;AAAA,EAChE,CAAC;AACH,OAAO;AACL,WAAS,YAAY,MAAM;AACzB,OAAG,KAAK,6BAA6B,MAAM;AAAA,IAAC,CAAC;AAAA,EAC/C,CAAC;AACH;","names":["stt"]}