@livekit/agents-plugin-deepgram 1.5.0 → 1.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/stt.cjs +54 -3
- package/dist/stt.cjs.map +1 -1
- package/dist/stt.d.cts +5 -0
- package/dist/stt.d.ts +5 -0
- package/dist/stt.d.ts.map +1 -1
- package/dist/stt.js +54 -3
- package/dist/stt.js.map +1 -1
- package/dist/stt_v2.cjs +53 -3
- package/dist/stt_v2.cjs.map +1 -1
- package/dist/stt_v2.d.cts +1 -0
- package/dist/stt_v2.d.ts +1 -0
- package/dist/stt_v2.d.ts.map +1 -1
- package/dist/stt_v2.js +53 -3
- package/dist/stt_v2.js.map +1 -1
- package/dist/tts.cjs +1 -0
- package/dist/tts.cjs.map +1 -1
- package/dist/tts.d.ts.map +1 -1
- package/dist/tts.js +1 -0
- package/dist/tts.js.map +1 -1
- package/package.json +8 -8
- package/src/stt.ts +62 -2
- package/src/stt_v2.ts +60 -2
- package/src/tts.ts +1 -0
package/dist/index.cjs
CHANGED
package/dist/index.js
CHANGED
package/dist/stt.cjs
CHANGED
|
@@ -54,6 +54,10 @@ const defaultSTTOptions = {
|
|
|
54
54
|
class STT extends import_agents.stt.STT {
|
|
55
55
|
#opts;
|
|
56
56
|
#logger = (0, import_agents.log)();
|
|
57
|
+
// session keyterm propagation)
|
|
58
|
+
#streams = /* @__PURE__ */ new Set();
|
|
59
|
+
#userKeyterm;
|
|
60
|
+
#sessionKeyterms = [];
|
|
57
61
|
label = "deepgram.STT";
|
|
58
62
|
abortController = new AbortController();
|
|
59
63
|
get model() {
|
|
@@ -66,7 +70,8 @@ class STT extends import_agents.stt.STT {
|
|
|
66
70
|
super({
|
|
67
71
|
streaming: true,
|
|
68
72
|
interimResults: opts.interimResults ?? defaultSTTOptions.interimResults,
|
|
69
|
-
alignedTranscript: "word"
|
|
73
|
+
alignedTranscript: "word",
|
|
74
|
+
keyterms: true
|
|
70
75
|
});
|
|
71
76
|
if (opts.apiKey === void 0 && defaultSTTOptions.apiKey === void 0) {
|
|
72
77
|
throw new Error(
|
|
@@ -83,6 +88,7 @@ class STT extends import_agents.stt.STT {
|
|
|
83
88
|
} else {
|
|
84
89
|
this.#opts.model = this.#validateModel(this.#opts.model, this.#opts.language);
|
|
85
90
|
}
|
|
91
|
+
this.#userKeyterm = [...this.#opts.keyterm];
|
|
86
92
|
}
|
|
87
93
|
async _recognize(buffer, abortSignal) {
|
|
88
94
|
const frame = (0, import_agents.mergeFrames)(buffer);
|
|
@@ -124,13 +130,38 @@ class STT extends import_agents.stt.STT {
|
|
|
124
130
|
updateOptions(opts) {
|
|
125
131
|
const language = opts.language !== void 0 ? (0, import_agents.normalizeLanguage)(opts.language) : this.#opts.language;
|
|
126
132
|
const model = opts.model !== void 0 || opts.language !== void 0 ? this.#validateModel(opts.model ?? this.#opts.model, language) : this.#opts.model;
|
|
133
|
+
const nextOpts = { ...opts };
|
|
134
|
+
if (nextOpts.keyterm !== void 0) {
|
|
135
|
+
this.#userKeyterm = [...nextOpts.keyterm];
|
|
136
|
+
nextOpts.keyterm = [.../* @__PURE__ */ new Set([...this.#userKeyterm, ...this.#sessionKeyterms])];
|
|
137
|
+
}
|
|
127
138
|
this.#opts = {
|
|
128
139
|
...this.#opts,
|
|
129
|
-
...
|
|
140
|
+
...nextOpts,
|
|
130
141
|
language,
|
|
131
142
|
model
|
|
132
143
|
};
|
|
133
144
|
}
|
|
145
|
+
_updateSessionKeyterms(keyterms) {
|
|
146
|
+
if (keyterms.length === this.#sessionKeyterms.length && keyterms.every((t, i) => t === this.#sessionKeyterms[i])) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
this.#sessionKeyterms = [...keyterms];
|
|
150
|
+
const merged = [.../* @__PURE__ */ new Set([...this.#userKeyterm, ...keyterms])];
|
|
151
|
+
this.#opts.keyterm = merged;
|
|
152
|
+
for (const ref of this.#streams) {
|
|
153
|
+
const stream = ref.deref();
|
|
154
|
+
if (!stream) {
|
|
155
|
+
this.#streams.delete(ref);
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (stream._speaking) {
|
|
159
|
+
stream._pendingKeyterm = merged;
|
|
160
|
+
} else {
|
|
161
|
+
stream.updateOptions({ keyterm: merged });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
134
165
|
#validateModel(model, language) {
|
|
135
166
|
if (language && (0, import_agents.getBaseLanguage)(language) !== "en" && [
|
|
136
167
|
"nova-2-meeting",
|
|
@@ -157,7 +188,9 @@ class STT extends import_agents.stt.STT {
|
|
|
157
188
|
"language detection is not supported in streaming mode, please disable it and specify a language"
|
|
158
189
|
);
|
|
159
190
|
}
|
|
160
|
-
|
|
191
|
+
const stream = new SpeechStream(this, this.#opts, options == null ? void 0 : options.connOptions);
|
|
192
|
+
this.#streams.add(new WeakRef(stream));
|
|
193
|
+
return stream;
|
|
161
194
|
}
|
|
162
195
|
async close() {
|
|
163
196
|
this.abortController.abort();
|
|
@@ -170,6 +203,9 @@ class SpeechStream extends import_agents.stt.SpeechStream {
|
|
|
170
203
|
#speaking = false;
|
|
171
204
|
#resetWS = new import_agents.Future();
|
|
172
205
|
#requestId = "";
|
|
206
|
+
// keyterms set while the user is speaking; applied at END_OF_SPEECH (latest wins)
|
|
207
|
+
/** @internal */
|
|
208
|
+
_pendingKeyterm = null;
|
|
173
209
|
#audioDurationCollector;
|
|
174
210
|
label = "deepgram.SpeechStream";
|
|
175
211
|
constructor(stt2, opts, connOptions) {
|
|
@@ -244,10 +280,23 @@ class SpeechStream extends import_agents.stt.SpeechStream {
|
|
|
244
280
|
}
|
|
245
281
|
this.closed = true;
|
|
246
282
|
}
|
|
283
|
+
/** @internal */
|
|
284
|
+
get _speaking() {
|
|
285
|
+
return this.#speaking;
|
|
286
|
+
}
|
|
247
287
|
updateOptions(opts) {
|
|
248
288
|
this.#opts = { ...this.#opts, ...opts };
|
|
289
|
+
if (opts.keyterm !== void 0) {
|
|
290
|
+
this._pendingKeyterm = null;
|
|
291
|
+
}
|
|
249
292
|
this.#resetWS.resolve();
|
|
250
293
|
}
|
|
294
|
+
#onEndOfSpeech() {
|
|
295
|
+
if (this._pendingKeyterm !== null) {
|
|
296
|
+
this.updateOptions({ keyterm: this._pendingKeyterm });
|
|
297
|
+
this._pendingKeyterm = null;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
251
300
|
async #runWS(ws) {
|
|
252
301
|
this.#resetWS = new import_agents.Future();
|
|
253
302
|
let closing = false;
|
|
@@ -365,6 +414,7 @@ class SpeechStream extends import_agents.stt.SpeechStream {
|
|
|
365
414
|
if (isEndpoint && this.#speaking) {
|
|
366
415
|
this.#speaking = false;
|
|
367
416
|
putMessage({ type: import_agents.stt.SpeechEventType.END_OF_SPEECH });
|
|
417
|
+
this.#onEndOfSpeech();
|
|
368
418
|
}
|
|
369
419
|
break;
|
|
370
420
|
}
|
|
@@ -375,6 +425,7 @@ class SpeechStream extends import_agents.stt.SpeechStream {
|
|
|
375
425
|
if (this.#speaking) {
|
|
376
426
|
this.#speaking = false;
|
|
377
427
|
putMessage({ type: import_agents.stt.SpeechEventType.END_OF_SPEECH });
|
|
428
|
+
this.#onEndOfSpeech();
|
|
378
429
|
}
|
|
379
430
|
break;
|
|
380
431
|
}
|
package/dist/stt.cjs.map
CHANGED
|
@@ -1 +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 | string;\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 utteranceEndMs?: number | null;\n replace?: Record<string, string> | null;\n search?: string[] | null;\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 utteranceEndMs: null,\n replace: null,\n search: null,\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 appendSearchParams(listenURL, params);\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 | string, 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 utterance_end_ms: this.#opts.utteranceEndMs,\n replace: this.#opts.replace,\n search: this.#opts.search,\n language: this.#opts.language,\n mip_opt_out: this.#opts.mipOptOut,\n ...(this.#opts.redact.length > 0 ? { redact: this.#opts.redact } : {}),\n };\n appendSearchParams(streamURL, params);\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 case 'UtteranceEnd': {\n if (this.#speaking) {\n this.#speaking = false;\n putMessage({ type: stt.SpeechEventType.END_OF_SPEECH });\n }\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 appendSearchParams(url: URL, params: Record<string, unknown>) {\n Object.entries(params).forEach(([key, value]) => {\n if (value === undefined || value === null) return;\n\n if (Array.isArray(value)) {\n value.forEach((item) => url.searchParams.append(key, String(item)));\n } else if (typeof value === 'object') {\n Object.entries(value).forEach(([term, replacement]) => {\n url.searchParams.append(key, `${term}:${replacement}`);\n });\n } else {\n url.searchParams.append(key, String(value));\n }\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":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,oBAcO;AAEP,gBAA0B;AAC1B,mBAAkC;AAmClC,MAAM,oBAAgC;AAAA,EACpC,QAAQ,QAAQ,IAAI;AAAA,EACpB,UAAU;AAAA,EACV,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,WAAW;AAAA,EACX,OAAO;AAAA,EACP,aAAa;AAAA,EACb,SAAS;AAAA,EACT,aAAa;AAAA,EACb,aAAa;AAAA,EACb,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,UAAU,CAAC;AAAA,EACX,SAAS,CAAC;AAAA,EACV,iBAAiB;AAAA,EACjB,QAAQ,CAAC;AAAA,EACT,gBAAgB;AAAA,EAChB,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,SAAS;AAAA,EACT,UAAU;AAAA,EACV,WAAW;AAAA,EACX,SAAS;AACX;AAEO,MAAM,YAAY,kBAAI,IAAI;AAAA,EAC/B;AAAA,EACA,cAAU,mBAAI;AAAA,EACd,QAAQ;AAAA,EACA,kBAAkB,IAAI,gBAAgB;AAAA,EAE9C,IAAI,QAAgB;AAClB,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA,EAEA,IAAI,WAAmB;AACrB,WAAO;AAAA,EACT;AAAA,EAEA,YAAY,OAA4B,mBAAmB;AACzD,UAAM;AAAA,MACJ,WAAW;AAAA,MACX,gBAAgB,KAAK,kBAAkB,kBAAkB;AAAA,MACzD,mBAAmB;AAAA,IACrB,CAAC;AACD,QAAI,KAAK,WAAW,UAAa,kBAAkB,WAAW,QAAW;AACvE,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,SAAK,QAAQ;AAAA,MACX,GAAG;AAAA,MACH,GAAG;AAAA,MACH,UAAU,KAAK,eAAW,iCAAkB,KAAK,QAAQ,IAAI,kBAAkB;AAAA,IACjF;AAEA,QAAI,KAAK,MAAM,gBAAgB;AAC7B,WAAK,MAAM,WAAW;AAAA,IACxB,OAAO;AACL,WAAK,MAAM,QAAQ,KAAK,eAAe,KAAK,MAAM,OAAO,KAAK,MAAM,QAAQ;AAAA,IAC9E;AAAA,EACF;AAAA,EAEA,MAAM,WAAW,QAAqB,aAAqD;AACzF,UAAM,YAAQ,2BAAY,MAAM;AAChC,UAAM,YAAY,IAAI,IAAI,GAAG,KAAK,MAAM,QAAQ,QAAQ,OAAO,MAAM,CAAC,YAAY;AAClF,UAAM,SAAS;AAAA,MACb,OAAO,KAAK,MAAM;AAAA,MAClB,WAAW,KAAK,MAAM;AAAA,MACtB,iBAAiB,KAAK,MAAM;AAAA,MAC5B,cAAc,KAAK,MAAM;AAAA,MACzB,UAAU,KAAK,MAAM,SAAS,IAAI,CAAC,MAAM,EAAE,KAAK,GAAG,CAAC;AAAA,MACpD,kBAAkB,KAAK,MAAM;AAAA,MAC7B,UAAU,KAAK,MAAM;AAAA,MACrB,aAAa,KAAK,MAAM;AAAA,MACxB,UAAU,KAAK,MAAM;AAAA,MACrB,GAAI,KAAK,MAAM,QAAQ,SAAS,IAAI,EAAE,SAAS,KAAK,MAAM,QAAQ,IAAI,CAAC;AAAA,MACvE,GAAI,KAAK,MAAM,OAAO,SAAS,IAAI,EAAE,QAAQ,KAAK,MAAM,OAAO,IAAI,CAAC;AAAA,IACtE;AAEA,uBAAmB,WAAW,MAAM;AAEpC,QAAI,KAAK,MAAM,SAAS;AACtB,WAAK,QAAQ,KAAK,sEAAsE;AAAA,IAC1F;AAEA,UAAM,OAAO,MAAM,MAAM,WAAW;AAAA,MAClC,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,eAAe,SAAS,KAAK,MAAM,MAAM;AAAA,QACzC,QAAQ;AAAA,QACR,gBAAgB;AAAA,MAClB;AAAA,MACA,MAAM,IAAI,KAAK,CAAC,IAAI,WAAW,UAAU,KAAK,CAAC,CAAC,GAAG,EAAE,MAAM,YAAY,CAAC;AAAA,MACxE,QAAQ,eAAe;AAAA,IACzB,CAAC;AAED,QAAI,CAAC,KAAK,IAAI;AACZ,YAAM,IAAI;AAAA,QACR,gDAAgD,KAAK,MAAM,KAAK,MAAM,KAAK,KAAK,CAAC;AAAA,MACnF;AAAA,IACF;AAEA,WAAO,sCAAsC,KAAK,MAAM,UAAU,MAAM,KAAK,KAAK,CAAC;AAAA,EACrF;AAAA,EAEA,cAAc,MAA2B;AACvC,UAAM,WACJ,KAAK,aAAa,aAAY,iCAAkB,KAAK,QAAQ,IAAI,KAAK,MAAM;AAC9E,UAAM,QACJ,KAAK,UAAU,UAAa,KAAK,aAAa,SAC1C,KAAK,eAAe,KAAK,SAAS,KAAK,MAAM,OAAO,QAAQ,IAC5D,KAAK,MAAM;AAEjB,SAAK,QAAQ;AAAA,MACX,GAAG,KAAK;AAAA,MACR,GAAG;AAAA,MACH;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EAEA,eAAe,OAA2B,UAAmB;AAC3D,QACE,gBACA,+BAAgB,QAAQ,MAAM,QAC9B;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,EAAE,SAAS,KAAK,GAChB;AACA,WAAK,QAAQ;AAAA,QACX,GAAG,KAAK,8BAA8B,QAAQ;AAAA,MAChD;AACA,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,OAAO,SAA6D;AAElE,QAAI,KAAK,MAAM,kBAAkB,KAAK,MAAM,aAAa,QAAW;AAClE,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,WAAO,IAAI,aAAa,MAAM,KAAK,OAAO,mCAAS,WAAW;AAAA,EAChE;AAAA,EAEA,MAAM,QAAQ;AACZ,SAAK,gBAAgB,MAAM;AAAA,EAC7B;AACF;AAEO,MAAM,qBAAqB,kBAAI,aAAa;AAAA,EACjD;AAAA,EACA;AAAA,EACA,cAAU,mBAAI;AAAA,EACd,YAAY;AAAA,EACZ,WAAW,IAAI,qBAAO;AAAA,EACtB,aAAa;AAAA,EACb;AAAA,EACA,QAAQ;AAAA,EAER,YAAYA,MAAU,MAAkB,aAAiC;AACvE,UAAMA,MAAK,KAAK,YAAY,WAAW;AACvC,SAAK,QAAQ;AACb,SAAK,SAAS;AACd,SAAK,qBAAqB,IAAI,gCAAkB;AAChD,SAAK,0BAA0B,IAAI;AAAA,MACjC,CAAC,aAAa,KAAK,sBAAsB,QAAQ;AAAA,MACjD,EAAE,UAAU,EAAI;AAAA,IAClB;AAAA,EACF;AAAA,EAEA,MAAgB,MAAM;AACpB,UAAM,WAAW;AACjB,QAAI,UAAU;AACd,QAAI;AAEJ,WAAO,CAAC,KAAK,MAAM,UAAU,CAAC,KAAK,QAAQ;AACzC,YAAM,YAAY,IAAI,IAAI,GAAG,KAAK,MAAM,OAAO,YAAY;AAC3D,YAAM,SAAS;AAAA,QACb,OAAO,KAAK,MAAM;AAAA,QAClB,WAAW,KAAK,MAAM;AAAA,QACtB,cAAc,KAAK,MAAM;AAAA,QACzB,WAAW,KAAK,MAAM;AAAA,QACtB,SAAS,KAAK,MAAM;AAAA,QACpB,UAAU,KAAK,MAAM;AAAA,QACrB,UAAU,KAAK,MAAM;AAAA,QACrB,iBAAiB,KAAK,MAAM;AAAA,QAC5B,UAAU;AAAA,QACV,YAAY;AAAA,QACZ,aAAa,KAAK,MAAM;AAAA,QACxB,UAAU,KAAK,MAAM;AAAA,QACrB,aAAa,KAAK,MAAM,eAAe;AAAA,QACvC,cAAc,KAAK,MAAM;AAAA,QACzB,UAAU,KAAK,MAAM,SAAS,IAAI,CAAC,MAAM,EAAE,KAAK,GAAG,CAAC;AAAA,QACpD,SAAS,KAAK,MAAM;AAAA,QACpB,kBAAkB,KAAK,MAAM;AAAA,QAC7B,kBAAkB,KAAK,MAAM;AAAA,QAC7B,SAAS,KAAK,MAAM;AAAA,QACpB,QAAQ,KAAK,MAAM;AAAA,QACnB,UAAU,KAAK,MAAM;AAAA,QACrB,aAAa,KAAK,MAAM;AAAA,QACxB,GAAI,KAAK,MAAM,OAAO,SAAS,IAAI,EAAE,QAAQ,KAAK,MAAM,OAAO,IAAI,CAAC;AAAA,MACtE;AACA,yBAAmB,WAAW,MAAM;AAEpC,WAAK,IAAI,oBAAU,WAAW;AAAA,QAC5B,SAAS,EAAE,eAAe,SAAS,KAAK,MAAM,MAAM,GAAG;AAAA,MACzD,CAAC;AAED,UAAI;AACF,cAAM,IAAI,QAAQ,CAAC,SAAS,WAAW;AACrC,aAAG,GAAG,QAAQ,OAAO;AACrB,aAAG,GAAG,SAAS,CAAC,UAAU,OAAO,KAAK,CAAC;AACvC,aAAG,GAAG,SAAS,CAAC,SAAS,OAAO,sBAAsB,IAAI,EAAE,CAAC;AAAA,QAC/D,CAAC;AAED,cAAM,KAAK,OAAO,EAAE;AAAA,MACtB,SAAS,GAAG;AACV,YAAI,CAAC,KAAK,UAAU,CAAC,KAAK,MAAM,QAAQ;AACtC,cAAI,WAAW,UAAU;AACvB,kBAAM,IAAI,MAAM,uCAAuC,OAAO,cAAc,CAAC,EAAE;AAAA,UACjF;AAEA,gBAAM,QAAQ,KAAK,IAAI,UAAU,GAAG,EAAE;AACtC;AAEA,eAAK,QAAQ;AAAA,YACX,8CAA8C,KAAK,aAAa,CAAC,KAAK,OAAO,IAAI,QAAQ;AAAA,UAC3F;AACA,gBAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,QAAQ,GAAI,CAAC;AAAA,QAClE,OAAO;AACL,eAAK,QAAQ;AAAA,YACX,gDAAgD,CAAC,kBAAkB,KAAK,MAAM,MAAM,eAAe,KAAK,MAAM;AAAA,UAChH;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,cAAc,MAA2B;AACvC,SAAK,QAAQ,EAAE,GAAG,KAAK,OAAO,GAAG,KAAK;AACtC,SAAK,SAAS,QAAQ;AAAA,EACxB;AAAA,EAEA,MAAM,OAAO,IAAe;AAC1B,SAAK,WAAW,IAAI,qBAAO;AAC3B,QAAI,UAAU;AAEd,UAAM,YAAY,YAAY,MAAM;AAClC,UAAI;AACF,WAAG,KAAK,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC,CAAC;AAAA,MAC/C,QAAQ;AACN,sBAAc,SAAS;AACvB;AAAA,MACF;AAAA,IACF,GAAG,GAAI;AAGP,UAAM,YAAY,mBAAK,KAAK,OAAO,eAAe;AAChD,YAAM,SAAS,IAAI,QAAc,OAAO,GAAG,WAAW;AACpD,WAAG,KAAK,SAAS,CAAC,MAAM,WAAW;AACjC,cAAI,CAAC,SAAS;AACZ,iBAAK,QAAQ,MAAM,8BAA8B,IAAI,KAAK,MAAM,EAAE;AAClE,mBAAO,IAAI,MAAM,kBAAkB,CAAC;AAAA,UACtC;AAAA,QACF,CAAC;AAAA,MACH,CAAC;AAED,YAAM,QAAQ,KAAK,CAAC,YAAQ,4BAAa,WAAW,MAAM,CAAC,CAAC;AAAA,IAC9D,CAAC;AAED,UAAM,WAAW,YAAY;AAC3B,YAAM,eAAe,KAAK,MAAM,KAAK,MAAM,aAAa,EAAE;AAC1D,YAAM,SAAS,IAAI;AAAA,QACjB,KAAK,MAAM;AAAA,QACX,KAAK,MAAM;AAAA,QACX;AAAA,MACF;AAIA,YAAM,mBAAe,4BAAa,KAAK,WAAW;AAElD,UAAI;AACF,eAAO,CAAC,KAAK,QAAQ;AACnB,gBAAM,SAAS,MAAM,QAAQ,KAAK,CAAC,KAAK,MAAM,KAAK,GAAG,YAAY,CAAC;AAEnE,cAAI,WAAW,OAAW;AAC1B,cAAI,OAAO,MAAM;AACf;AAAA,UACF;AAEA,gBAAM,OAAO,OAAO;AAEpB,cAAI;AACJ,cAAI,SAAS,aAAa,gBAAgB;AACxC,qBAAS,OAAO,MAAM;AACtB,iBAAK,wBAAwB,MAAM;AAAA,UACrC,WACE,KAAK,eAAe,KAAK,MAAM,cAC/B,KAAK,aAAa,KAAK,MAAM,aAC7B;AACA,qBAAS,OAAO,MAAM,KAAK,KAAK,MAAqB;AAAA,UACvD,OAAO;AACL,kBAAM,IAAI,MAAM,sDAAsD;AAAA,UACxE;AAEA,2BAAiB,SAAS,QAAQ;AAChC,gBAAI,KAAK,mBAAmB,UAAU,KAAK,GAAG;AAC5C,oBAAM,gBAAgB,MAAM,oBAAoB,MAAM;AACtD,mBAAK,wBAAwB,KAAK,aAAa;AAC/C,iBAAG,KAAK,MAAM,KAAK,MAAM;AAAA,YAC3B;AAAA,UACF;AAAA,QACF;AAAA,MACF,UAAE;AACA,kBAAU;AACV,WAAG,KAAK,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC,CAAC;AAC/C,kBAAU,OAAO;AAAA,MACnB;AAAA,IACF;AAEA,UAAM,aAAa,mBAAK,KAAK,OAAO,eAAe;AACjD,YAAM,aAAa,CAAC,YAA6B;AAC/C,YAAI,CAAC,KAAK,MAAM,QAAQ;AACtB,cAAI;AACF,iBAAK,MAAM,IAAI,OAAO;AAAA,UACxB,SAAS,GAAG;AAAA,UAEZ;AAAA,QACF;AAAA,MACF;AAEA,YAAM,gBAAgB,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3D,WAAG,GAAG,WAAW,CAAC,QAAQ;AACxB,cAAI;AACF,kBAAM,OAAO,KAAK,MAAM,IAAI,SAAS,CAAC;AACtC,oBAAQ,KAAK,MAAM,GAAG;AAAA,cACpB,KAAK,iBAAiB;AAKpB,oBAAI,KAAK,UAAW;AACpB,qBAAK,YAAY;AACjB,2BAAW,EAAE,MAAM,kBAAI,gBAAgB,gBAAgB,CAAC;AACxD;AAAA,cACF;AAAA;AAAA;AAAA;AAAA,cAIA,KAAK,WAAW;AACd,sBAAM,WAAW,KAAK,UAAU;AAChC,sBAAM,YAAY,SAAS,YAAY;AACvC,sBAAM,UAAU,KAAK,UAAU;AAC/B,sBAAM,aAAa,KAAK,cAAc;AACtC,qBAAK,aAAa;AAElB,sBAAM,eAAe;AAAA,kBACnB,KAAK,MAAM;AAAA,kBACX;AAAA,kBACA,KAAK;AAAA,gBACP;AAKA,oBAAI,aAAa,CAAC,KAAK,aAAa,CAAC,EAAE,MAAM;AAC3C,sBAAI,CAAC,KAAK,WAAW;AACnB,yBAAK,YAAY;AACjB,+BAAW;AAAA,sBACT,MAAM,kBAAI,gBAAgB;AAAA,oBAC5B,CAAC;AAAA,kBACH;AAEA,sBAAI,SAAS;AACX,+BAAW;AAAA,sBACT,MAAM,kBAAI,gBAAgB;AAAA,sBAC1B,cAAc,CAAC,aAAa,CAAC,GAAG,GAAG,aAAa,MAAM,CAAC,CAAC;AAAA,oBAC1D,CAAC;AAAA,kBACH,OAAO;AACL,+BAAW;AAAA,sBACT,MAAM,kBAAI,gBAAgB;AAAA,sBAC1B,cAAc,CAAC,aAAa,CAAC,GAAG,GAAG,aAAa,MAAM,CAAC,CAAC;AAAA,oBAC1D,CAAC;AAAA,kBACH;AAAA,gBACF;AAKA,oBAAI,cAAc,KAAK,WAAW;AAChC,uBAAK,YAAY;AACjB,6BAAW,EAAE,MAAM,kBAAI,gBAAgB,cAAc,CAAC;AAAA,gBACxD;AAEA;AAAA,cACF;AAAA,cACA,KAAK,YAAY;AACf;AAAA,cACF;AAAA,cACA,KAAK,gBAAgB;AACnB,oBAAI,KAAK,WAAW;AAClB,uBAAK,YAAY;AACjB,6BAAW,EAAE,MAAM,kBAAI,gBAAgB,cAAc,CAAC;AAAA,gBACxD;AACA;AAAA,cACF;AAAA,cACA,SAAS;AACP,qBAAK,QAAQ,MAAM,EAAE,KAAK,KAAK,CAAC,EAAE,KAAK,2CAA2C;AAClF;AAAA,cACF;AAAA,YACF;AAEA,gBAAI,KAAK,UAAU,SAAS;AAC1B,sBAAQ;AAAA,YACV;AAAA,UACF,SAAS,KAAK;AACZ,iBAAK,QAAQ,MAAM,kCAAkC,GAAG,EAAE;AAC1D,mBAAO,GAAG;AAAA,UACZ;AAAA,QACF,CAAC;AAAA,MACH,CAAC;AAED,YAAM,QAAQ,KAAK,CAAC,mBAAe,4BAAa,WAAW,MAAM,CAAC,CAAC;AAAA,IACrE,GAAG,KAAK,eAAe;AAEvB,UAAM,QAAQ,KAAK;AAAA,MACjB,KAAK,SAAS;AAAA,MACd,QAAQ,IAAI,CAAC,SAAS,GAAG,WAAW,QAAQ,SAAS,CAAC;AAAA,IACxD,CAAC;AACD,cAAU;AACV,OAAG,MAAM;AACT,kBAAc,SAAS;AAAA,EACzB;AAAA,EAEQ,sBAAsB,UAAkB;AAC9C,UAAM,aAA8B;AAAA,MAClC,MAAM,kBAAI,gBAAgB;AAAA,MAC1B,WAAW,KAAK;AAAA,MAChB,kBAAkB;AAAA,QAChB,eAAe;AAAA,MACjB;AAAA,IACF;AACA,SAAK,MAAM,IAAI,UAAU;AAAA,EAC3B;AACF;AAEA,SAAS,mBAAmB,KAAU,QAAiC;AACrE,SAAO,QAAQ,MAAM,EAAE,QAAQ,CAAC,CAAC,KAAK,KAAK,MAAM;AAC/C,QAAI,UAAU,UAAa,UAAU,KAAM;AAE3C,QAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,YAAM,QAAQ,CAAC,SAAS,IAAI,aAAa,OAAO,KAAK,OAAO,IAAI,CAAC,CAAC;AAAA,IACpE,WAAW,OAAO,UAAU,UAAU;AACpC,aAAO,QAAQ,KAAK,EAAE,QAAQ,CAAC,CAAC,MAAM,WAAW,MAAM;AACrD,YAAI,aAAa,OAAO,KAAK,GAAG,IAAI,IAAI,WAAW,EAAE;AAAA,MACvD,CAAC;AAAA,IACH,OAAO;AACL,UAAI,aAAa,OAAO,KAAK,OAAO,KAAK,CAAC;AAAA,IAC5C;AAAA,EACF,CAAC;AACH;AAEA,SAAS,UAAU,OAA2B;AAC5C,QAAM,gBAAgB;AACtB,QAAM,WAAY,MAAM,aAAa,MAAM,WAAW,gBAAiB;AACvE,QAAM,aAAc,MAAM,WAAW,gBAAiB;AAEtD,QAAM,SAAS,OAAO,MAAM,EAAE;AAC9B,SAAO,MAAM,QAAQ,CAAC;AACtB,SAAO,cAAc,KAAK,MAAM,KAAK,YAAY,CAAC;AAClD,SAAO,MAAM,QAAQ,CAAC;AACtB,SAAO,MAAM,QAAQ,EAAE;AACvB,SAAO,cAAc,IAAI,EAAE;AAC3B,SAAO,cAAc,GAAG,EAAE;AAC1B,SAAO,cAAc,MAAM,UAAU,EAAE;AACvC,SAAO,cAAc,MAAM,YAAY,EAAE;AACzC,SAAO,cAAc,UAAU,EAAE;AACjC,SAAO,cAAc,YAAY,EAAE;AACnC,SAAO,cAAc,eAAe,EAAE;AACtC,SAAO,MAAM,QAAQ,EAAE;AACvB,SAAO,cAAc,MAAM,KAAK,YAAY,EAAE;AAE9C,QAAM,MAAM,OAAO,KAAK,MAAM,KAAK,QAAQ,MAAM,KAAK,YAAY,MAAM,KAAK,UAAU;AACvF,SAAO,OAAO,OAAO,CAAC,QAAQ,GAAG,CAAC;AACpC;AAEA,MAAM,gCAAgC,CACpC,UACA,MACA,kBAA0B,MACL;AACrB,QAAM,OAAc,KAAK,SAAS,EAAE,cAAc;AAElD,SAAO,KAAK,IAAI,CAAC,QAAQ;AAxjB3B;AAyjBI,UAAM,YAAmB,IAAI,OAAO,KAAK,CAAC;AAC1C,UAAM,mBACJ,aAAa,YAAU,SAAI,WAAW,MAAf,mBAAmB,OAAM,WAAW,IAAI,UAAU,KAAK;AAEhF,WAAO;AAAA,MACL,cAAU,iCAAkB,gBAAgB;AAAA,MAC5C,WAAW,UAAU,SAAS,UAAU,CAAC,EAAE,OAAO,IAAI,kBAAkB;AAAA,MACxE,SAAS,UAAU,SACf,UAAU,UAAU,SAAS,CAAC,EAAE,KAAK,IAAI,kBACzC;AAAA,MACJ,YAAY,IAAI,YAAY;AAAA,MAC5B,MAAM,IAAI,YAAY;AAAA,MACtB,OAAO,UAAU;AAAA,QAAI,CAAC,aACpB,iCAAkB;AAAA,UAChB,MAAM,KAAK,MAAM,KAAK;AAAA,UACtB,YAAY,KAAK,OAAO,KAAK,KAAK;AAAA,UAClC,UAAU,KAAK,KAAK,KAAK,KAAK;AAAA,UAC9B,YAAY,KAAK,YAAY,KAAK;AAAA,UAClC;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF,CAAC;AACH;AAEA,SAAS,sCACP,UACA,MACiB;AACjB,QAAM,UAAU,KAAK,SAAS,EAAE,UAAU,EAAE,CAAC;AAC7C,QAAM,mBAAmB,QAAQ,mBAAmB,KAAK;AACzD,QAAM,eAAe,QAAQ,cAAc,EAAE,IAAI,CAAC,QAA+B;AAC/E,UAAM,YAAmB,IAAI,OAAO,KAAK,CAAC;AAE1C,WAAO;AAAA,MACL,cAAU,iCAAkB,YAAY,gBAAgB;AAAA,MACxD,WAAW,UAAU,SAAS,UAAU,CAAC,EAAE,OAAO,IAAI;AAAA,MACtD,SAAS,UAAU,SAAS,UAAU,UAAU,SAAS,CAAC,EAAE,KAAK,IAAI;AAAA,MACrE,YAAY,IAAI,YAAY;AAAA,MAC5B,MAAM,IAAI,YAAY;AAAA,MACtB,OAAO,UAAU;AAAA,QAAI,CAAC,aACpB,iCAAkB;AAAA,UAChB,MAAM,KAAK,MAAM,KAAK;AAAA,UACtB,WAAW,KAAK,OAAO,KAAK;AAAA,UAC5B,SAAS,KAAK,KAAK,KAAK;AAAA,UACxB,YAAY,KAAK,YAAY,KAAK;AAAA,QACpC,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF,CAAC;AAED,SAAO;AAAA,IACL,MAAM,kBAAI,gBAAgB;AAAA,IAC1B,WAAW,KAAK,UAAU,EAAE,YAAY;AAAA,IACxC,GAAI,aAAa,SACb,EAAE,aAAoE,IACtE,CAAC;AAAA,EACP;AACF;","names":["stt"]}
|
|
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 | string;\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 utteranceEndMs?: number | null;\n replace?: Record<string, string> | null;\n search?: string[] | null;\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 utteranceEndMs: null,\n replace: null,\n search: null,\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 // session keyterm propagation)\n #streams = new Set<WeakRef<SpeechStream>>();\n #userKeyterm: string[];\n #sessionKeyterms: string[] = [];\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 keyterms: true,\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 this.#userKeyterm = [...this.#opts.keyterm];\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 appendSearchParams(listenURL, params);\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 const nextOpts = { ...opts };\n if (nextOpts.keyterm !== undefined) {\n this.#userKeyterm = [...nextOpts.keyterm];\n nextOpts.keyterm = [...new Set([...this.#userKeyterm, ...this.#sessionKeyterms])];\n }\n\n this.#opts = {\n ...this.#opts,\n ...nextOpts,\n language,\n model,\n };\n }\n\n override _updateSessionKeyterms(keyterms: string[]): void {\n if (\n keyterms.length === this.#sessionKeyterms.length &&\n keyterms.every((t, i) => t === this.#sessionKeyterms[i])\n ) {\n return;\n }\n this.#sessionKeyterms = [...keyterms];\n const merged = [...new Set([...this.#userKeyterm, ...keyterms])];\n this.#opts.keyterm = merged;\n for (const ref of this.#streams) {\n const stream = ref.deref();\n if (!stream) {\n this.#streams.delete(ref);\n continue;\n }\n if (stream._speaking) {\n // defer the reconnect to the end of the utterance so we don't cut it off\n stream._pendingKeyterm = merged;\n } else {\n stream.updateOptions({ keyterm: merged });\n }\n }\n }\n\n #validateModel(model: STTModels | string, 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 const stream = new SpeechStream(this, this.#opts, options?.connOptions);\n this.#streams.add(new WeakRef(stream));\n return stream;\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 // keyterms set while the user is speaking; applied at END_OF_SPEECH (latest wins)\n /** @internal */\n _pendingKeyterm: string[] | null = null;\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 utterance_end_ms: this.#opts.utteranceEndMs,\n replace: this.#opts.replace,\n search: this.#opts.search,\n language: this.#opts.language,\n mip_opt_out: this.#opts.mipOptOut,\n ...(this.#opts.redact.length > 0 ? { redact: this.#opts.redact } : {}),\n };\n appendSearchParams(streamURL, params);\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 /** @internal */\n get _speaking(): boolean {\n return this.#speaking;\n }\n\n updateOptions(opts: Partial<STTOptions>) {\n this.#opts = { ...this.#opts, ...opts };\n if (opts.keyterm !== undefined) {\n this._pendingKeyterm = null;\n }\n this.#resetWS.resolve();\n }\n\n #onEndOfSpeech() {\n if (this._pendingKeyterm !== null) {\n this.updateOptions({ keyterm: this._pendingKeyterm });\n this._pendingKeyterm = null;\n }\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 this.#onEndOfSpeech();\n }\n\n break;\n }\n case 'Metadata': {\n break;\n }\n case 'UtteranceEnd': {\n if (this.#speaking) {\n this.#speaking = false;\n putMessage({ type: stt.SpeechEventType.END_OF_SPEECH });\n this.#onEndOfSpeech();\n }\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 appendSearchParams(url: URL, params: Record<string, unknown>) {\n Object.entries(params).forEach(([key, value]) => {\n if (value === undefined || value === null) return;\n\n if (Array.isArray(value)) {\n value.forEach((item) => url.searchParams.append(key, String(item)));\n } else if (typeof value === 'object') {\n Object.entries(value).forEach(([term, replacement]) => {\n url.searchParams.append(key, `${term}:${replacement}`);\n });\n } else {\n url.searchParams.append(key, String(value));\n }\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"]}
|
package/dist/stt.d.cts
CHANGED
|
@@ -40,6 +40,7 @@ export declare class STT extends stt.STT {
|
|
|
40
40
|
constructor(opts?: Partial<STTOptions>);
|
|
41
41
|
_recognize(buffer: AudioBuffer, abortSignal?: AbortSignal): Promise<stt.SpeechEvent>;
|
|
42
42
|
updateOptions(opts: Partial<STTOptions>): void;
|
|
43
|
+
_updateSessionKeyterms(keyterms: string[]): void;
|
|
43
44
|
stream(options?: {
|
|
44
45
|
connOptions?: APIConnectOptions;
|
|
45
46
|
}): SpeechStream;
|
|
@@ -47,9 +48,13 @@ export declare class STT extends stt.STT {
|
|
|
47
48
|
}
|
|
48
49
|
export declare class SpeechStream extends stt.SpeechStream {
|
|
49
50
|
#private;
|
|
51
|
+
/** @internal */
|
|
52
|
+
_pendingKeyterm: string[] | null;
|
|
50
53
|
label: string;
|
|
51
54
|
constructor(stt: STT, opts: STTOptions, connOptions?: APIConnectOptions);
|
|
52
55
|
protected run(): Promise<void>;
|
|
56
|
+
/** @internal */
|
|
57
|
+
get _speaking(): boolean;
|
|
53
58
|
updateOptions(opts: Partial<STTOptions>): void;
|
|
54
59
|
private onAudioDurationReport;
|
|
55
60
|
}
|
package/dist/stt.d.ts
CHANGED
|
@@ -40,6 +40,7 @@ export declare class STT extends stt.STT {
|
|
|
40
40
|
constructor(opts?: Partial<STTOptions>);
|
|
41
41
|
_recognize(buffer: AudioBuffer, abortSignal?: AbortSignal): Promise<stt.SpeechEvent>;
|
|
42
42
|
updateOptions(opts: Partial<STTOptions>): void;
|
|
43
|
+
_updateSessionKeyterms(keyterms: string[]): void;
|
|
43
44
|
stream(options?: {
|
|
44
45
|
connOptions?: APIConnectOptions;
|
|
45
46
|
}): SpeechStream;
|
|
@@ -47,9 +48,13 @@ export declare class STT extends stt.STT {
|
|
|
47
48
|
}
|
|
48
49
|
export declare class SpeechStream extends stt.SpeechStream {
|
|
49
50
|
#private;
|
|
51
|
+
/** @internal */
|
|
52
|
+
_pendingKeyterm: string[] | null;
|
|
50
53
|
label: string;
|
|
51
54
|
constructor(stt: STT, opts: STTOptions, connOptions?: APIConnectOptions);
|
|
52
55
|
protected run(): Promise<void>;
|
|
56
|
+
/** @internal */
|
|
57
|
+
get _speaking(): boolean;
|
|
53
58
|
updateOptions(opts: Partial<STTOptions>): void;
|
|
54
59
|
private onAudioDurationReport;
|
|
55
60
|
}
|
package/dist/stt.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stt.d.ts","sourceRoot":"","sources":["../src/stt.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAUhB,GAAG,EAEJ,MAAM,iBAAiB,CAAC;AAIzB,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE3D,MAAM,WAAW,UAAU;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,YAAY,GAAG,MAAM,CAAC;IACjC,cAAc,EAAE,OAAO,CAAC;IACxB,cAAc,EAAE,OAAO,CAAC;IACxB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,SAAS,GAAG,MAAM,CAAC;IAC1B,WAAW,EAAE,OAAO,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,OAAO,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;IAC7B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,eAAe,EAAE,OAAO,CAAC;IACzB;;;;OAIG;IACH,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;IACxC,MAAM,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB;AA6BD,qBAAa,GAAI,SAAQ,GAAG,CAAC,GAAG;;
|
|
1
|
+
{"version":3,"file":"stt.d.ts","sourceRoot":"","sources":["../src/stt.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAUhB,GAAG,EAEJ,MAAM,iBAAiB,CAAC;AAIzB,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE3D,MAAM,WAAW,UAAU;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,YAAY,GAAG,MAAM,CAAC;IACjC,cAAc,EAAE,OAAO,CAAC;IACxB,cAAc,EAAE,OAAO,CAAC;IACxB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,SAAS,GAAG,MAAM,CAAC;IAC1B,WAAW,EAAE,OAAO,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,OAAO,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;IAC7B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,eAAe,EAAE,OAAO,CAAC;IACzB;;;;OAIG;IACH,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;IACxC,MAAM,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB;AA6BD,qBAAa,GAAI,SAAQ,GAAG,CAAC,GAAG;;IAO9B,KAAK,SAAkB;IACvB,OAAO,CAAC,eAAe,CAAyB;IAEhD,IAAI,KAAK,IAAI,MAAM,CAElB;IAED,IAAI,QAAQ,IAAI,MAAM,CAErB;gBAEW,IAAI,GAAE,OAAO,CAAC,UAAU,CAAqB;IA4BnD,UAAU,CAAC,MAAM,EAAE,WAAW,EAAE,WAAW,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC;IA2C1F,aAAa,CAAC,IAAI,EAAE,OAAO,CAAC,UAAU,CAAC;IAsB9B,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,IAAI;IAmDzD,MAAM,CAAC,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,iBAAiB,CAAA;KAAE,GAAG,YAAY;IAY7D,KAAK;CAGZ;AAED,qBAAa,YAAa,SAAQ,GAAG,CAAC,YAAY;;IAQhD,gBAAgB;IAChB,eAAe,EAAE,MAAM,EAAE,GAAG,IAAI,CAAQ;IAExC,KAAK,SAA2B;gBAEpB,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,WAAW,CAAC,EAAE,iBAAiB;cAWvD,GAAG;IAsEnB,gBAAgB;IAChB,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,aAAa,CAAC,IAAI,EAAE,OAAO,CAAC,UAAU,CAAC;IAiNvC,OAAO,CAAC,qBAAqB;CAU9B"}
|
package/dist/stt.js
CHANGED
|
@@ -42,6 +42,10 @@ const defaultSTTOptions = {
|
|
|
42
42
|
class STT extends stt.STT {
|
|
43
43
|
#opts;
|
|
44
44
|
#logger = log();
|
|
45
|
+
// session keyterm propagation)
|
|
46
|
+
#streams = /* @__PURE__ */ new Set();
|
|
47
|
+
#userKeyterm;
|
|
48
|
+
#sessionKeyterms = [];
|
|
45
49
|
label = "deepgram.STT";
|
|
46
50
|
abortController = new AbortController();
|
|
47
51
|
get model() {
|
|
@@ -54,7 +58,8 @@ class STT extends stt.STT {
|
|
|
54
58
|
super({
|
|
55
59
|
streaming: true,
|
|
56
60
|
interimResults: opts.interimResults ?? defaultSTTOptions.interimResults,
|
|
57
|
-
alignedTranscript: "word"
|
|
61
|
+
alignedTranscript: "word",
|
|
62
|
+
keyterms: true
|
|
58
63
|
});
|
|
59
64
|
if (opts.apiKey === void 0 && defaultSTTOptions.apiKey === void 0) {
|
|
60
65
|
throw new Error(
|
|
@@ -71,6 +76,7 @@ class STT extends stt.STT {
|
|
|
71
76
|
} else {
|
|
72
77
|
this.#opts.model = this.#validateModel(this.#opts.model, this.#opts.language);
|
|
73
78
|
}
|
|
79
|
+
this.#userKeyterm = [...this.#opts.keyterm];
|
|
74
80
|
}
|
|
75
81
|
async _recognize(buffer, abortSignal) {
|
|
76
82
|
const frame = mergeFrames(buffer);
|
|
@@ -112,13 +118,38 @@ class STT extends stt.STT {
|
|
|
112
118
|
updateOptions(opts) {
|
|
113
119
|
const language = opts.language !== void 0 ? normalizeLanguage(opts.language) : this.#opts.language;
|
|
114
120
|
const model = opts.model !== void 0 || opts.language !== void 0 ? this.#validateModel(opts.model ?? this.#opts.model, language) : this.#opts.model;
|
|
121
|
+
const nextOpts = { ...opts };
|
|
122
|
+
if (nextOpts.keyterm !== void 0) {
|
|
123
|
+
this.#userKeyterm = [...nextOpts.keyterm];
|
|
124
|
+
nextOpts.keyterm = [.../* @__PURE__ */ new Set([...this.#userKeyterm, ...this.#sessionKeyterms])];
|
|
125
|
+
}
|
|
115
126
|
this.#opts = {
|
|
116
127
|
...this.#opts,
|
|
117
|
-
...
|
|
128
|
+
...nextOpts,
|
|
118
129
|
language,
|
|
119
130
|
model
|
|
120
131
|
};
|
|
121
132
|
}
|
|
133
|
+
_updateSessionKeyterms(keyterms) {
|
|
134
|
+
if (keyterms.length === this.#sessionKeyterms.length && keyterms.every((t, i) => t === this.#sessionKeyterms[i])) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
this.#sessionKeyterms = [...keyterms];
|
|
138
|
+
const merged = [.../* @__PURE__ */ new Set([...this.#userKeyterm, ...keyterms])];
|
|
139
|
+
this.#opts.keyterm = merged;
|
|
140
|
+
for (const ref of this.#streams) {
|
|
141
|
+
const stream = ref.deref();
|
|
142
|
+
if (!stream) {
|
|
143
|
+
this.#streams.delete(ref);
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (stream._speaking) {
|
|
147
|
+
stream._pendingKeyterm = merged;
|
|
148
|
+
} else {
|
|
149
|
+
stream.updateOptions({ keyterm: merged });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
122
153
|
#validateModel(model, language) {
|
|
123
154
|
if (language && getBaseLanguage(language) !== "en" && [
|
|
124
155
|
"nova-2-meeting",
|
|
@@ -145,7 +176,9 @@ class STT extends stt.STT {
|
|
|
145
176
|
"language detection is not supported in streaming mode, please disable it and specify a language"
|
|
146
177
|
);
|
|
147
178
|
}
|
|
148
|
-
|
|
179
|
+
const stream = new SpeechStream(this, this.#opts, options == null ? void 0 : options.connOptions);
|
|
180
|
+
this.#streams.add(new WeakRef(stream));
|
|
181
|
+
return stream;
|
|
149
182
|
}
|
|
150
183
|
async close() {
|
|
151
184
|
this.abortController.abort();
|
|
@@ -158,6 +191,9 @@ class SpeechStream extends stt.SpeechStream {
|
|
|
158
191
|
#speaking = false;
|
|
159
192
|
#resetWS = new Future();
|
|
160
193
|
#requestId = "";
|
|
194
|
+
// keyterms set while the user is speaking; applied at END_OF_SPEECH (latest wins)
|
|
195
|
+
/** @internal */
|
|
196
|
+
_pendingKeyterm = null;
|
|
161
197
|
#audioDurationCollector;
|
|
162
198
|
label = "deepgram.SpeechStream";
|
|
163
199
|
constructor(stt2, opts, connOptions) {
|
|
@@ -232,10 +268,23 @@ class SpeechStream extends stt.SpeechStream {
|
|
|
232
268
|
}
|
|
233
269
|
this.closed = true;
|
|
234
270
|
}
|
|
271
|
+
/** @internal */
|
|
272
|
+
get _speaking() {
|
|
273
|
+
return this.#speaking;
|
|
274
|
+
}
|
|
235
275
|
updateOptions(opts) {
|
|
236
276
|
this.#opts = { ...this.#opts, ...opts };
|
|
277
|
+
if (opts.keyterm !== void 0) {
|
|
278
|
+
this._pendingKeyterm = null;
|
|
279
|
+
}
|
|
237
280
|
this.#resetWS.resolve();
|
|
238
281
|
}
|
|
282
|
+
#onEndOfSpeech() {
|
|
283
|
+
if (this._pendingKeyterm !== null) {
|
|
284
|
+
this.updateOptions({ keyterm: this._pendingKeyterm });
|
|
285
|
+
this._pendingKeyterm = null;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
239
288
|
async #runWS(ws) {
|
|
240
289
|
this.#resetWS = new Future();
|
|
241
290
|
let closing = false;
|
|
@@ -353,6 +402,7 @@ class SpeechStream extends stt.SpeechStream {
|
|
|
353
402
|
if (isEndpoint && this.#speaking) {
|
|
354
403
|
this.#speaking = false;
|
|
355
404
|
putMessage({ type: stt.SpeechEventType.END_OF_SPEECH });
|
|
405
|
+
this.#onEndOfSpeech();
|
|
356
406
|
}
|
|
357
407
|
break;
|
|
358
408
|
}
|
|
@@ -363,6 +413,7 @@ class SpeechStream extends stt.SpeechStream {
|
|
|
363
413
|
if (this.#speaking) {
|
|
364
414
|
this.#speaking = false;
|
|
365
415
|
putMessage({ type: stt.SpeechEventType.END_OF_SPEECH });
|
|
416
|
+
this.#onEndOfSpeech();
|
|
366
417
|
}
|
|
367
418
|
break;
|
|
368
419
|
}
|