@livekit/agents-plugin-deepgram 1.5.0 → 1.5.1
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/stt.js.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":"AAGA;AAAA,EAGE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,SAAS,iBAAiB;AAC1B,SAAS,yBAAyB;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,IAAI,IAAI;AAAA,EAC/B;AAAA,EACA,UAAU,IAAI;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,WAAW,kBAAkB,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,QAAQ,YAAY,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,SAAY,kBAAkB,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,YACA,gBAAgB,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,IAAI,aAAa;AAAA,EACjD;AAAA,EACA;AAAA,EACA,UAAU,IAAI;AAAA,EACd,YAAY;AAAA,EACZ,WAAW,IAAI,OAAO;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,kBAAkB;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,UAAU,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,OAAO;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,KAAK,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,QAAQ,aAAa,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,eAAe,aAAa,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,KAAK,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,IAAI,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,IAAI,gBAAgB;AAAA,oBAC5B,CAAC;AAAA,kBACH;AAEA,sBAAI,SAAS;AACX,+BAAW;AAAA,sBACT,MAAM,IAAI,gBAAgB;AAAA,sBAC1B,cAAc,CAAC,aAAa,CAAC,GAAG,GAAG,aAAa,MAAM,CAAC,CAAC;AAAA,oBAC1D,CAAC;AAAA,kBACH,OAAO;AACL,+BAAW;AAAA,sBACT,MAAM,IAAI,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,IAAI,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,IAAI,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,eAAe,aAAa,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,IAAI,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,UAAU,kBAAkB,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,SACpB,kBAAkB;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,UAAU,kBAAkB,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,SACpB,kBAAkB;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,IAAI,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_v2.cjs
CHANGED
|
@@ -57,6 +57,10 @@ class STTv2 extends import_agents.stt.STT {
|
|
|
57
57
|
#opts;
|
|
58
58
|
#apiKey;
|
|
59
59
|
#logger = (0, import_agents.log)();
|
|
60
|
+
// session keyterm propagation)
|
|
61
|
+
#streams = /* @__PURE__ */ new Set();
|
|
62
|
+
#userKeyterms;
|
|
63
|
+
#sessionKeyterms = [];
|
|
60
64
|
/**
|
|
61
65
|
* Create a new Deepgram STTv2 instance.
|
|
62
66
|
*
|
|
@@ -77,13 +81,15 @@ class STTv2 extends import_agents.stt.STT {
|
|
|
77
81
|
super({
|
|
78
82
|
streaming: true,
|
|
79
83
|
interimResults: true,
|
|
80
|
-
alignedTranscript: "word"
|
|
84
|
+
alignedTranscript: "word",
|
|
85
|
+
keyterms: true
|
|
81
86
|
});
|
|
82
87
|
this.#opts = {
|
|
83
88
|
...defaultSTTv2Options,
|
|
84
89
|
...opts,
|
|
85
90
|
language: opts.language ? (0, import_agents.normalizeLanguage)(opts.language) : defaultSTTv2Options.language
|
|
86
91
|
};
|
|
92
|
+
this.#userKeyterms = [...this.#opts.keyterms];
|
|
87
93
|
const apiKey = opts.apiKey || process.env.DEEPGRAM_API_KEY;
|
|
88
94
|
if (!apiKey) {
|
|
89
95
|
throw new Error("Deepgram API key is required");
|
|
@@ -118,7 +124,9 @@ class STTv2 extends import_agents.stt.STT {
|
|
|
118
124
|
*/
|
|
119
125
|
stream(options) {
|
|
120
126
|
const streamOpts = { ...this.#opts, apiKey: this.#apiKey };
|
|
121
|
-
|
|
127
|
+
const stream = new SpeechStreamv2(this, streamOpts, options == null ? void 0 : options.connOptions);
|
|
128
|
+
this.#streams.add(new WeakRef(stream));
|
|
129
|
+
return stream;
|
|
122
130
|
}
|
|
123
131
|
/**
|
|
124
132
|
* Update STT options. Changes will take effect on the next stream.
|
|
@@ -126,9 +134,14 @@ class STTv2 extends import_agents.stt.STT {
|
|
|
126
134
|
* @param opts - Partial options to update
|
|
127
135
|
*/
|
|
128
136
|
updateOptions(opts) {
|
|
137
|
+
const nextOpts = { ...opts };
|
|
138
|
+
if (nextOpts.keyterms !== void 0) {
|
|
139
|
+
this.#userKeyterms = [...nextOpts.keyterms];
|
|
140
|
+
nextOpts.keyterms = [.../* @__PURE__ */ new Set([...this.#userKeyterms, ...this.#sessionKeyterms])];
|
|
141
|
+
}
|
|
129
142
|
this.#opts = {
|
|
130
143
|
...this.#opts,
|
|
131
|
-
...
|
|
144
|
+
...nextOpts,
|
|
132
145
|
language: opts.language !== void 0 ? (0, import_agents.normalizeLanguage)(opts.language) : this.#opts.language
|
|
133
146
|
};
|
|
134
147
|
if (opts.tags) this.#opts.tags = validateTags(opts.tags);
|
|
@@ -140,6 +153,26 @@ class STTv2 extends import_agents.stt.STT {
|
|
|
140
153
|
}
|
|
141
154
|
this.#logger.debug("Updated STTv2 options");
|
|
142
155
|
}
|
|
156
|
+
_updateSessionKeyterms(keyterms) {
|
|
157
|
+
if (keyterms.length === this.#sessionKeyterms.length && keyterms.every((t, i) => t === this.#sessionKeyterms[i])) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
this.#sessionKeyterms = [...keyterms];
|
|
161
|
+
const merged = [.../* @__PURE__ */ new Set([...this.#userKeyterms, ...keyterms])];
|
|
162
|
+
this.#opts.keyterms = merged;
|
|
163
|
+
for (const ref of this.#streams) {
|
|
164
|
+
const stream = ref.deref();
|
|
165
|
+
if (!stream) {
|
|
166
|
+
this.#streams.delete(ref);
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (stream._speaking) {
|
|
170
|
+
stream._pendingKeyterm = merged;
|
|
171
|
+
} else {
|
|
172
|
+
stream.updateOptions({ keyterms: merged });
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
143
176
|
}
|
|
144
177
|
class SpeechStreamv2 extends import_agents.stt.SpeechStream {
|
|
145
178
|
label = "deepgram.SpeechStreamv2";
|
|
@@ -151,6 +184,9 @@ class SpeechStreamv2 extends import_agents.stt.SpeechStream {
|
|
|
151
184
|
#speaking = false;
|
|
152
185
|
// Parity: _reconnect_event - using existing Event class from @livekit/agents
|
|
153
186
|
#reconnectEvent = new import_agents.Event();
|
|
187
|
+
// keyterms set while the user is speaking; applied at END_OF_SPEECH (latest wins)
|
|
188
|
+
/** @internal */
|
|
189
|
+
_pendingKeyterm = null;
|
|
154
190
|
constructor(sttInstance, opts, connOptions) {
|
|
155
191
|
super(sttInstance, opts.sampleRate, connOptions);
|
|
156
192
|
this.#opts = opts;
|
|
@@ -159,6 +195,10 @@ class SpeechStreamv2 extends import_agents.stt.SpeechStream {
|
|
|
159
195
|
{ duration: 5 }
|
|
160
196
|
);
|
|
161
197
|
}
|
|
198
|
+
/** @internal */
|
|
199
|
+
get _speaking() {
|
|
200
|
+
return this.#speaking;
|
|
201
|
+
}
|
|
162
202
|
updateOptions(opts) {
|
|
163
203
|
this.#logger.debug("Stream received option update", opts);
|
|
164
204
|
this.#opts = {
|
|
@@ -167,8 +207,17 @@ class SpeechStreamv2 extends import_agents.stt.SpeechStream {
|
|
|
167
207
|
language: opts.language !== void 0 ? (0, import_agents.normalizeLanguage)(opts.language) : this.#opts.language
|
|
168
208
|
};
|
|
169
209
|
if (opts.tags) this.#opts.tags = validateTags(opts.tags);
|
|
210
|
+
if (opts.keyterms !== void 0) {
|
|
211
|
+
this._pendingKeyterm = null;
|
|
212
|
+
}
|
|
170
213
|
this.#reconnectEvent.set();
|
|
171
214
|
}
|
|
215
|
+
#onEndOfSpeech() {
|
|
216
|
+
if (this._pendingKeyterm !== null) {
|
|
217
|
+
this.updateOptions({ keyterms: this._pendingKeyterm });
|
|
218
|
+
this._pendingKeyterm = null;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
172
221
|
async run() {
|
|
173
222
|
var _a;
|
|
174
223
|
while (!this.closed) {
|
|
@@ -316,6 +365,7 @@ class SpeechStreamv2 extends import_agents.stt.SpeechStream {
|
|
|
316
365
|
type: import_agents.stt.SpeechEventType.END_OF_SPEECH,
|
|
317
366
|
requestId: this.#requestId
|
|
318
367
|
});
|
|
368
|
+
this.#onEndOfSpeech();
|
|
319
369
|
}
|
|
320
370
|
} else if (data.type === "Error") {
|
|
321
371
|
this.#logger.warn("deepgram sent an error", { data });
|
package/dist/stt_v2.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/stt_v2.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport {\n type APIConnectOptions,\n AudioByteStream,\n Event,\n calculateAudioDurationSeconds,\n createTimedString,\n log,\n normalizeLanguage,\n stt,\n} from '@livekit/agents';\nimport type { AudioFrame } from '@livekit/rtc-node';\nimport * as queryString from 'node:querystring';\nimport { WebSocket } from 'ws';\nimport { PeriodicCollector } from './_utils.js';\nimport type { V2Models } from './models.js';\n\nconst _CLOSE_MSG = JSON.stringify({ type: 'CloseStream' });\n\n// --- Configuration ---\n\n/**\n * Configuration options for STTv2 (Deepgram Flux model).\n */\nexport interface STTv2Options {\n apiKey?: string;\n model: V2Models | string;\n sampleRate: number;\n keyterms: string[];\n endpointUrl: string;\n language?: string;\n eagerEotThreshold?: number;\n eotThreshold?: number;\n eotTimeoutMs?: number;\n mipOptOut?: boolean;\n tags?: string[];\n /**\n * List of language hints to bias the model for improved accuracy.\n * Only usable with `flux-general-multi`.\n */\n // Ref: python livekit-plugins/livekit-plugins-deepgram/livekit/plugins/deepgram/stt_v2.py - 61 line\n languageHint?: string[];\n}\n\nconst defaultSTTv2Options: Omit<STTv2Options, 'apiKey'> = {\n model: 'flux-general-en',\n sampleRate: 16000,\n keyterms: [],\n endpointUrl: 'wss://api.deepgram.com/v2/listen',\n language: 'en',\n mipOptOut: false,\n};\n\nfunction validateTags(tags: string[]): string[] {\n for (const tag of tags) {\n if (tag.length > 128) {\n throw new Error('tag must be no more than 128 characters');\n }\n }\n return tags;\n}\n\n/**\n * Deepgram STTv2 using the Flux model for streaming speech-to-text.\n *\n * This uses Deepgram's V2 API (`/v2/listen`) which provides turn-based\n * transcription with support for preemptive generation.\n *\n * @remarks\n * Key differences from STT (V1):\n * - Uses `TurnInfo` events instead of `SpeechStarted`/`Results`\n * - Supports `eagerEotThreshold` for preemptive LLM generation\n * - Sends `PREFLIGHT_TRANSCRIPT` events when eager end-of-turn is detected\n *\n * @example\n * ```typescript\n * import { STTv2 } from '@livekit/agents-plugin-deepgram';\n *\n * const stt = new STTv2({\n * model: 'flux-general-en',\n * eagerEotThreshold: 0.5, // Enable preemptive generation\n * });\n *\n * const stream = stt.stream();\n * stream.pushFrame(audioFrame);\n *\n * for await (const event of stream) {\n * if (event.type === SpeechEventType.FINAL_TRANSCRIPT) {\n * console.log(event.alternatives?.[0]?.text);\n * }\n * }\n * ```\n */\nexport class STTv2 extends stt.STT {\n readonly label = 'deepgram.STTv2';\n #opts: STTv2Options;\n #apiKey: string;\n #logger = log();\n\n /**\n * Create a new Deepgram STTv2 instance.\n *\n * @param opts - Configuration options\n * @param opts.apiKey - Deepgram API key (defaults to `DEEPGRAM_API_KEY` env var)\n * @param opts.model - Model to use (default: `flux-general-en`)\n * @param opts.eagerEotThreshold - Threshold (0.3-0.9) for preemptive generation\n * @param opts.eotThreshold - End-of-turn detection threshold (default: 0.7)\n * @param opts.eotTimeoutMs - End-of-turn timeout in ms (default: 3000)\n * @param opts.keyterms - List of key terms to improve recognition\n * @param opts.tags - Tags for usage reporting (max 128 chars each)\n * @param opts.languageHint - List of language hints to bias the model for improved accuracy.\n * Only usable with `flux-general-multi`.\n *\n * @throws Error if no API key is provided\n */\n constructor(opts: Partial<STTv2Options> = {}) {\n super({\n streaming: true,\n interimResults: true,\n alignedTranscript: 'word',\n });\n\n this.#opts = {\n ...defaultSTTv2Options,\n ...opts,\n language: opts.language ? normalizeLanguage(opts.language) : defaultSTTv2Options.language,\n };\n\n const apiKey = opts.apiKey || process.env.DEEPGRAM_API_KEY;\n if (!apiKey) {\n throw new Error('Deepgram API key is required');\n }\n this.#apiKey = apiKey;\n\n if (this.#opts.tags) {\n this.#opts.tags = validateTags(this.#opts.tags);\n }\n\n // Ref: python livekit-plugins/livekit-plugins-deepgram/livekit/plugins/deepgram/stt_v2.py - 134-138 lines\n if (\n this.#opts.languageHint &&\n this.#opts.languageHint.length > 0 &&\n this.#opts.model !== 'flux-general-multi'\n ) {\n this.#logger.warn(\n { model: this.#opts.model },\n '`languageHint` is only supported by `flux-general-multi` and will be ignored for this model',\n );\n }\n }\n\n /** The model being used for transcription */\n get model(): string {\n return this.#opts.model;\n }\n\n /** The STT provider name */\n get provider(): string {\n return 'Deepgram';\n }\n\n protected async _recognize(\n _frame: AudioFrame | AudioFrame[],\n _abortSignal?: AbortSignal,\n ): Promise<stt.SpeechEvent> {\n throw new Error('V2 API does not support non-streaming recognize. Use .stream()');\n }\n\n /**\n * Create a new streaming transcription session.\n *\n * @param options - Stream options\n * @returns A SpeechStream that emits transcription events\n */\n stream(options?: { connOptions?: APIConnectOptions }): stt.SpeechStream {\n const streamOpts = { ...this.#opts, apiKey: this.#apiKey };\n return new SpeechStreamv2(this, streamOpts, options?.connOptions);\n }\n\n /**\n * Update STT options. Changes will take effect on the next stream.\n *\n * @param opts - Partial options to update\n */\n updateOptions(opts: Partial<STTv2Options>) {\n this.#opts = {\n ...this.#opts,\n ...opts,\n language:\n opts.language !== undefined ? normalizeLanguage(opts.language) : this.#opts.language,\n };\n if (opts.tags) this.#opts.tags = validateTags(opts.tags);\n // Ref: python livekit-plugins/livekit-plugins-deepgram/livekit/plugins/deepgram/stt_v2.py - 244-249 lines\n if (\n this.#opts.languageHint &&\n this.#opts.languageHint.length > 0 &&\n this.#opts.model !== 'flux-general-multi'\n ) {\n this.#logger.warn(\n { model: this.#opts.model },\n '`languageHint` is only supported by `flux-general-multi` and will be ignored for this model',\n );\n }\n this.#logger.debug('Updated STTv2 options');\n }\n}\n\n// --- Stream Implementation ---\n\nclass SpeechStreamv2 extends stt.SpeechStream {\n readonly label = 'deepgram.SpeechStreamv2';\n #opts: STTv2Options & { apiKey: string };\n #logger = log();\n #ws: WebSocket | null = null;\n\n #audioDurationCollector: PeriodicCollector<number>;\n #requestId = '';\n #speaking = false;\n\n // Parity: _reconnect_event - using existing Event class from @livekit/agents\n #reconnectEvent = new Event();\n\n constructor(\n sttInstance: STTv2,\n opts: STTv2Options & { apiKey: string },\n connOptions?: APIConnectOptions,\n ) {\n super(sttInstance, opts.sampleRate, connOptions);\n this.#opts = opts;\n\n this.#audioDurationCollector = new PeriodicCollector(\n (duration) => this.#onAudioDurationReport(duration),\n { duration: 5.0 },\n );\n }\n\n updateOptions(opts: Partial<STTv2Options>) {\n this.#logger.debug('Stream received option update', opts);\n this.#opts = {\n ...this.#opts,\n ...opts,\n language:\n opts.language !== undefined ? normalizeLanguage(opts.language) : this.#opts.language,\n };\n if (opts.tags) this.#opts.tags = validateTags(opts.tags);\n\n // Trigger reconnection loop\n this.#reconnectEvent.set();\n }\n\n protected async run() {\n // Outer Loop: Handles reconnections (Configuration updates)\n while (!this.closed) {\n try {\n this.#reconnectEvent.clear();\n\n const url = this.#getDeepgramUrl();\n this.#logger.debug(`Connecting to Deepgram: ${url}`);\n\n this.#ws = new WebSocket(url, {\n headers: { Authorization: `Token ${this.#opts.apiKey}` },\n });\n\n // 1. Wait for Connection Open\n await new Promise<void>((resolve, reject) => {\n if (!this.#ws) return reject(new Error('WebSocket not initialized'));\n\n const onOpen = () => {\n this.#ws?.off('error', onError);\n resolve();\n };\n const onError = (err: Error) => {\n this.#ws?.off('open', onOpen);\n reject(err);\n };\n\n this.#ws.once('open', onOpen);\n this.#ws.once('error', onError);\n });\n\n // 2. Run Concurrent Tasks (Send & Receive)\n const sendPromise = this.#sendTask();\n const recvPromise = this.#recvTask();\n const reconnectWait = this.#reconnectEvent.wait();\n\n // 3. Race: Normal Completion vs Reconnect Signal\n const result = await Promise.race([\n Promise.all([sendPromise, recvPromise]),\n reconnectWait.then(() => 'RECONNECT'),\n ]);\n\n if (result === 'RECONNECT') {\n this.#logger.debug('Reconnecting stream due to option update...');\n // Close current socket; loop will restart and open a new one\n this.#ws.close();\n } else {\n // Normal finish (Stream ended or Error thrown)\n break;\n }\n } catch (error) {\n this.#logger.error('Deepgram stream error', { error });\n throw error; // Let Base Class handle retry logic\n } finally {\n if (this.#ws?.readyState === WebSocket.OPEN) {\n this.#ws.close();\n }\n }\n }\n this.close();\n }\n\n async #sendTask() {\n if (!this.#ws) return;\n\n // Buffer audio into 50ms chunks (Parity)\n const samples50ms = Math.floor(this.#opts.sampleRate / 20);\n const audioBstream = new AudioByteStream(this.#opts.sampleRate, 1, samples50ms);\n\n let hasEnded = false;\n\n // Manual Iterator to allow racing against Reconnect Signal\n const iterator = this.input[Symbol.asyncIterator]();\n\n while (true) {\n const nextPromise = iterator.next();\n // If reconnect signal fires, abort the wait\n const abortPromise = this.#reconnectEvent.wait().then(() => ({ abort: true }) as const);\n\n const result = await Promise.race([nextPromise, abortPromise]);\n\n // Check if we need to abort (Reconnect) or if stream is done\n if ('abort' in result || result.done) {\n if (!('abort' in result) && result.done) {\n // Normal stream end\n hasEnded = true;\n } else {\n // Reconnect triggered - break loop immediately\n break;\n }\n }\n\n // If we broke above, we don't process data. If not, 'result' is IteratorResult\n if (hasEnded && result.value === undefined) {\n // Process flush below\n } else if ('value' in result) {\n const data = result.value;\n const frames: AudioFrame[] = [];\n\n if (data === SpeechStreamv2.FLUSH_SENTINEL) {\n frames.push(...audioBstream.flush());\n hasEnded = true;\n } else {\n frames.push(...audioBstream.write((data as AudioFrame).data.buffer as ArrayBuffer));\n }\n\n for (const frame of frames) {\n this.#audioDurationCollector.push(calculateAudioDurationSeconds(frame));\n\n if (this.#ws!.readyState === WebSocket.OPEN) {\n this.#ws!.send(frame.data);\n }\n\n if (hasEnded) {\n this.#audioDurationCollector.flush();\n hasEnded = false;\n }\n }\n }\n\n if (hasEnded) break;\n }\n\n // Only send CloseStream if we are exiting normally (not reconnecting)\n if (!this.#reconnectEvent.isSet && this.#ws!.readyState === WebSocket.OPEN) {\n this.#logger.debug('Sending CloseStream message to Deepgram');\n this.#ws!.send(_CLOSE_MSG);\n }\n }\n\n async #recvTask() {\n if (!this.#ws) return;\n\n return new Promise<void>((resolve) => {\n if (!this.#ws) return resolve();\n\n this.#ws.on('message', (data: Buffer, isBinary: boolean) => {\n if (isBinary) {\n this.#logger.warn('Received unexpected binary message from Deepgram');\n return;\n }\n try {\n const msg = JSON.parse(data.toString());\n this.#processStreamEvent(msg);\n } catch (error) {\n this.#logger.error('Failed to parse Deepgram message', { error });\n }\n });\n\n this.#ws.on('close', (code, reason) => {\n this.#logger.debug(`Deepgram WebSocket closed: ${code} ${reason}`);\n resolve();\n });\n\n // Errors are caught by run() listener, resolve here to clean up task\n this.#ws.on('error', () => resolve());\n });\n }\n\n #processStreamEvent(data: Record<string, unknown>) {\n if (data.request_id) {\n this.#requestId = data.request_id as string;\n }\n\n if (data.type === 'TurnInfo') {\n const eventType = data.event;\n\n if (eventType === 'StartOfTurn') {\n if (this.#speaking) return;\n\n this.#speaking = true;\n this.queue.put({\n type: stt.SpeechEventType.START_OF_SPEECH,\n requestId: this.#requestId,\n });\n\n this.#sendTranscriptEvent(stt.SpeechEventType.INTERIM_TRANSCRIPT, data);\n } else if (eventType === 'Update') {\n if (!this.#speaking) return;\n this.#sendTranscriptEvent(stt.SpeechEventType.INTERIM_TRANSCRIPT, data);\n } else if (eventType === 'EagerEndOfTurn') {\n if (!this.#speaking) return;\n this.#sendTranscriptEvent(stt.SpeechEventType.PREFLIGHT_TRANSCRIPT, data);\n } else if (eventType === 'TurnResumed') {\n this.#sendTranscriptEvent(stt.SpeechEventType.INTERIM_TRANSCRIPT, data);\n } else if (eventType === 'EndOfTurn') {\n if (!this.#speaking) return;\n\n this.#speaking = false;\n this.#sendTranscriptEvent(stt.SpeechEventType.FINAL_TRANSCRIPT, data);\n\n this.queue.put({\n type: stt.SpeechEventType.END_OF_SPEECH,\n requestId: this.#requestId,\n });\n }\n } else if (data.type === 'Error') {\n this.#logger.warn('deepgram sent an error', { data });\n const desc = (data.description as string) || 'unknown error from deepgram';\n throw new Error(`Deepgram API Error: ${desc}`);\n }\n }\n\n #sendTranscriptEvent(eventType: stt.SpeechEventType, data: Record<string, unknown>) {\n const alts = parseTranscription(this.#opts.language || 'en', data, this.startTimeOffset);\n\n if (alts.length > 0) {\n this.queue.put({\n type: eventType,\n requestId: this.#requestId,\n alternatives: [alts[0]!, ...alts.slice(1)],\n });\n }\n }\n\n #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 #getDeepgramUrl(): string {\n const params: Record<string, string | string[]> = {\n model: this.#opts.model,\n sample_rate: this.#opts.sampleRate.toString(),\n encoding: 'linear16',\n mip_opt_out: String(this.#opts.mipOptOut),\n };\n\n // Note: v2 API does NOT include 'language' parameter\n if (this.#opts.eagerEotThreshold)\n params.eager_eot_threshold = this.#opts.eagerEotThreshold.toString();\n if (this.#opts.eotThreshold) params.eot_threshold = this.#opts.eotThreshold.toString();\n if (this.#opts.eotTimeoutMs) params.eot_timeout_ms = this.#opts.eotTimeoutMs.toString();\n\n if (this.#opts.keyterms.length > 0) params.keyterm = this.#opts.keyterms;\n if (this.#opts.tags && this.#opts.tags.length > 0) params.tag = this.#opts.tags;\n\n // Ref: python livekit-plugins/livekit-plugins-deepgram/livekit/plugins/deepgram/stt_v2.py - 480-481 lines\n if (this.#opts.languageHint && this.#opts.languageHint.length > 0) {\n params.language_hint = this.#opts.languageHint;\n }\n\n const baseUrl = this.#opts.endpointUrl.replace(/^http/, 'ws');\n const qs = queryString.stringify(params);\n return `${baseUrl}?${qs}`;\n }\n\n override close() {\n super.close();\n this.#ws?.close();\n }\n}\n\n// --- Helpers ---\n\nfunction parseTranscription(\n language: string,\n data: Record<string, unknown>,\n startTimeOffset: number,\n): stt.SpeechData[] {\n const transcript = data.transcript as string | undefined;\n const wordsData = (data.words as Array<Record<string, unknown>>) || [];\n\n if (!wordsData || wordsData.length === 0) {\n return [];\n }\n\n let confidence = 0;\n if (wordsData.length > 0) {\n const sum = wordsData.reduce((acc: number, w) => acc + ((w.confidence as number) || 0), 0);\n confidence = sum / wordsData.length;\n }\n\n // Ref: python livekit-plugins/livekit-plugins-deepgram/livekit/plugins/deepgram/stt_v2.py - 587-591 lines\n const detectedLanguagesRaw = Array.isArray(data.languages) ? (data.languages as string[]) : [];\n const detectedLanguages = detectedLanguagesRaw.map((lang) => normalizeLanguage(lang));\n const primaryLanguage =\n detectedLanguages.length > 0 ? detectedLanguages[0]! : normalizeLanguage(language);\n\n const sd: stt.SpeechData = {\n language: primaryLanguage,\n startTime: ((data.audio_window_start as number) || 0) + startTimeOffset,\n endTime: ((data.audio_window_end as number) || 0) + startTimeOffset,\n confidence: confidence,\n text: transcript || '',\n // Ref: python livekit-plugins/livekit-plugins-deepgram/livekit/plugins/deepgram/stt_v2.py - 598 line\n sourceLanguages: detectedLanguages.length > 0 ? detectedLanguages : undefined,\n // Note: Deepgram V2 (Flux) API does not provide word-level timing (start/end).\n // Words only contain 'word' and 'confidence' fields, so startTime/endTime will be 0.\n // See: https://developers.deepgram.com/docs/flux/nova-3-migration\n words: wordsData.map((word) =>\n createTimedString({\n text: (word.word as string) ?? '',\n startTime: ((word.start as number) ?? 0) + startTimeOffset,\n endTime: ((word.end as number) ?? 0) + startTimeOffset,\n confidence: (word.confidence as number) ?? 0.0,\n startTimeOffset,\n }),\n ),\n };\n\n return [sd];\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,oBASO;AAEP,kBAA6B;AAC7B,gBAA0B;AAC1B,mBAAkC;AAGlC,MAAM,aAAa,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AA2BzD,MAAM,sBAAoD;AAAA,EACxD,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,UAAU,CAAC;AAAA,EACX,aAAa;AAAA,EACb,UAAU;AAAA,EACV,WAAW;AACb;AAEA,SAAS,aAAa,MAA0B;AAC9C,aAAW,OAAO,MAAM;AACtB,QAAI,IAAI,SAAS,KAAK;AACpB,YAAM,IAAI,MAAM,yCAAyC;AAAA,IAC3D;AAAA,EACF;AACA,SAAO;AACT;AAiCO,MAAM,cAAc,kBAAI,IAAI;AAAA,EACxB,QAAQ;AAAA,EACjB;AAAA,EACA;AAAA,EACA,cAAU,mBAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBd,YAAY,OAA8B,CAAC,GAAG;AAC5C,UAAM;AAAA,MACJ,WAAW;AAAA,MACX,gBAAgB;AAAA,MAChB,mBAAmB;AAAA,IACrB,CAAC;AAED,SAAK,QAAQ;AAAA,MACX,GAAG;AAAA,MACH,GAAG;AAAA,MACH,UAAU,KAAK,eAAW,iCAAkB,KAAK,QAAQ,IAAI,oBAAoB;AAAA,IACnF;AAEA,UAAM,SAAS,KAAK,UAAU,QAAQ,IAAI;AAC1C,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI,MAAM,8BAA8B;AAAA,IAChD;AACA,SAAK,UAAU;AAEf,QAAI,KAAK,MAAM,MAAM;AACnB,WAAK,MAAM,OAAO,aAAa,KAAK,MAAM,IAAI;AAAA,IAChD;AAGA,QACE,KAAK,MAAM,gBACX,KAAK,MAAM,aAAa,SAAS,KACjC,KAAK,MAAM,UAAU,sBACrB;AACA,WAAK,QAAQ;AAAA,QACX,EAAE,OAAO,KAAK,MAAM,MAAM;AAAA,QAC1B;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGA,IAAI,QAAgB;AAClB,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA;AAAA,EAGA,IAAI,WAAmB;AACrB,WAAO;AAAA,EACT;AAAA,EAEA,MAAgB,WACd,QACA,cAC0B;AAC1B,UAAM,IAAI,MAAM,gEAAgE;AAAA,EAClF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,OAAO,SAAiE;AACtE,UAAM,aAAa,EAAE,GAAG,KAAK,OAAO,QAAQ,KAAK,QAAQ;AACzD,WAAO,IAAI,eAAe,MAAM,YAAY,mCAAS,WAAW;AAAA,EAClE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,cAAc,MAA6B;AACzC,SAAK,QAAQ;AAAA,MACX,GAAG,KAAK;AAAA,MACR,GAAG;AAAA,MACH,UACE,KAAK,aAAa,aAAY,iCAAkB,KAAK,QAAQ,IAAI,KAAK,MAAM;AAAA,IAChF;AACA,QAAI,KAAK,KAAM,MAAK,MAAM,OAAO,aAAa,KAAK,IAAI;AAEvD,QACE,KAAK,MAAM,gBACX,KAAK,MAAM,aAAa,SAAS,KACjC,KAAK,MAAM,UAAU,sBACrB;AACA,WAAK,QAAQ;AAAA,QACX,EAAE,OAAO,KAAK,MAAM,MAAM;AAAA,QAC1B;AAAA,MACF;AAAA,IACF;AACA,SAAK,QAAQ,MAAM,uBAAuB;AAAA,EAC5C;AACF;AAIA,MAAM,uBAAuB,kBAAI,aAAa;AAAA,EACnC,QAAQ;AAAA,EACjB;AAAA,EACA,cAAU,mBAAI;AAAA,EACd,MAAwB;AAAA,EAExB;AAAA,EACA,aAAa;AAAA,EACb,YAAY;AAAA;AAAA,EAGZ,kBAAkB,IAAI,oBAAM;AAAA,EAE5B,YACE,aACA,MACA,aACA;AACA,UAAM,aAAa,KAAK,YAAY,WAAW;AAC/C,SAAK,QAAQ;AAEb,SAAK,0BAA0B,IAAI;AAAA,MACjC,CAAC,aAAa,KAAK,uBAAuB,QAAQ;AAAA,MAClD,EAAE,UAAU,EAAI;AAAA,IAClB;AAAA,EACF;AAAA,EAEA,cAAc,MAA6B;AACzC,SAAK,QAAQ,MAAM,iCAAiC,IAAI;AACxD,SAAK,QAAQ;AAAA,MACX,GAAG,KAAK;AAAA,MACR,GAAG;AAAA,MACH,UACE,KAAK,aAAa,aAAY,iCAAkB,KAAK,QAAQ,IAAI,KAAK,MAAM;AAAA,IAChF;AACA,QAAI,KAAK,KAAM,MAAK,MAAM,OAAO,aAAa,KAAK,IAAI;AAGvD,SAAK,gBAAgB,IAAI;AAAA,EAC3B;AAAA,EAEA,MAAgB,MAAM;AA5PxB;AA8PI,WAAO,CAAC,KAAK,QAAQ;AACnB,UAAI;AACF,aAAK,gBAAgB,MAAM;AAE3B,cAAM,MAAM,KAAK,gBAAgB;AACjC,aAAK,QAAQ,MAAM,2BAA2B,GAAG,EAAE;AAEnD,aAAK,MAAM,IAAI,oBAAU,KAAK;AAAA,UAC5B,SAAS,EAAE,eAAe,SAAS,KAAK,MAAM,MAAM,GAAG;AAAA,QACzD,CAAC;AAGD,cAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,cAAI,CAAC,KAAK,IAAK,QAAO,OAAO,IAAI,MAAM,2BAA2B,CAAC;AAEnE,gBAAM,SAAS,MAAM;AA7Q/B,gBAAAA;AA8QY,aAAAA,MAAA,KAAK,QAAL,gBAAAA,IAAU,IAAI,SAAS;AACvB,oBAAQ;AAAA,UACV;AACA,gBAAM,UAAU,CAAC,QAAe;AAjR1C,gBAAAA;AAkRY,aAAAA,MAAA,KAAK,QAAL,gBAAAA,IAAU,IAAI,QAAQ;AACtB,mBAAO,GAAG;AAAA,UACZ;AAEA,eAAK,IAAI,KAAK,QAAQ,MAAM;AAC5B,eAAK,IAAI,KAAK,SAAS,OAAO;AAAA,QAChC,CAAC;AAGD,cAAM,cAAc,KAAK,UAAU;AACnC,cAAM,cAAc,KAAK,UAAU;AACnC,cAAM,gBAAgB,KAAK,gBAAgB,KAAK;AAGhD,cAAM,SAAS,MAAM,QAAQ,KAAK;AAAA,UAChC,QAAQ,IAAI,CAAC,aAAa,WAAW,CAAC;AAAA,UACtC,cAAc,KAAK,MAAM,WAAW;AAAA,QACtC,CAAC;AAED,YAAI,WAAW,aAAa;AAC1B,eAAK,QAAQ,MAAM,6CAA6C;AAEhE,eAAK,IAAI,MAAM;AAAA,QACjB,OAAO;AAEL;AAAA,QACF;AAAA,MACF,SAAS,OAAO;AACd,aAAK,QAAQ,MAAM,yBAAyB,EAAE,MAAM,CAAC;AACrD,cAAM;AAAA,MACR,UAAE;AACA,cAAI,UAAK,QAAL,mBAAU,gBAAe,oBAAU,MAAM;AAC3C,eAAK,IAAI,MAAM;AAAA,QACjB;AAAA,MACF;AAAA,IACF;AACA,SAAK,MAAM;AAAA,EACb;AAAA,EAEA,MAAM,YAAY;AAChB,QAAI,CAAC,KAAK,IAAK;AAGf,UAAM,cAAc,KAAK,MAAM,KAAK,MAAM,aAAa,EAAE;AACzD,UAAM,eAAe,IAAI,8BAAgB,KAAK,MAAM,YAAY,GAAG,WAAW;AAE9E,QAAI,WAAW;AAGf,UAAM,WAAW,KAAK,MAAM,OAAO,aAAa,EAAE;AAElD,WAAO,MAAM;AACX,YAAM,cAAc,SAAS,KAAK;AAElC,YAAM,eAAe,KAAK,gBAAgB,KAAK,EAAE,KAAK,OAAO,EAAE,OAAO,KAAK,EAAW;AAEtF,YAAM,SAAS,MAAM,QAAQ,KAAK,CAAC,aAAa,YAAY,CAAC;AAG7D,UAAI,WAAW,UAAU,OAAO,MAAM;AACpC,YAAI,EAAE,WAAW,WAAW,OAAO,MAAM;AAEvC,qBAAW;AAAA,QACb,OAAO;AAEL;AAAA,QACF;AAAA,MACF;AAGA,UAAI,YAAY,OAAO,UAAU,QAAW;AAAA,MAE5C,WAAW,WAAW,QAAQ;AAC5B,cAAM,OAAO,OAAO;AACpB,cAAM,SAAuB,CAAC;AAE9B,YAAI,SAAS,eAAe,gBAAgB;AAC1C,iBAAO,KAAK,GAAG,aAAa,MAAM,CAAC;AACnC,qBAAW;AAAA,QACb,OAAO;AACL,iBAAO,KAAK,GAAG,aAAa,MAAO,KAAoB,KAAK,MAAqB,CAAC;AAAA,QACpF;AAEA,mBAAW,SAAS,QAAQ;AAC1B,eAAK,wBAAwB,SAAK,6CAA8B,KAAK,CAAC;AAEtE,cAAI,KAAK,IAAK,eAAe,oBAAU,MAAM;AAC3C,iBAAK,IAAK,KAAK,MAAM,IAAI;AAAA,UAC3B;AAEA,cAAI,UAAU;AACZ,iBAAK,wBAAwB,MAAM;AACnC,uBAAW;AAAA,UACb;AAAA,QACF;AAAA,MACF;AAEA,UAAI,SAAU;AAAA,IAChB;AAGA,QAAI,CAAC,KAAK,gBAAgB,SAAS,KAAK,IAAK,eAAe,oBAAU,MAAM;AAC1E,WAAK,QAAQ,MAAM,yCAAyC;AAC5D,WAAK,IAAK,KAAK,UAAU;AAAA,IAC3B;AAAA,EACF;AAAA,EAEA,MAAM,YAAY;AAChB,QAAI,CAAC,KAAK,IAAK;AAEf,WAAO,IAAI,QAAc,CAAC,YAAY;AACpC,UAAI,CAAC,KAAK,IAAK,QAAO,QAAQ;AAE9B,WAAK,IAAI,GAAG,WAAW,CAAC,MAAc,aAAsB;AAC1D,YAAI,UAAU;AACZ,eAAK,QAAQ,KAAK,kDAAkD;AACpE;AAAA,QACF;AACA,YAAI;AACF,gBAAM,MAAM,KAAK,MAAM,KAAK,SAAS,CAAC;AACtC,eAAK,oBAAoB,GAAG;AAAA,QAC9B,SAAS,OAAO;AACd,eAAK,QAAQ,MAAM,oCAAoC,EAAE,MAAM,CAAC;AAAA,QAClE;AAAA,MACF,CAAC;AAED,WAAK,IAAI,GAAG,SAAS,CAAC,MAAM,WAAW;AACrC,aAAK,QAAQ,MAAM,8BAA8B,IAAI,IAAI,MAAM,EAAE;AACjE,gBAAQ;AAAA,MACV,CAAC;AAGD,WAAK,IAAI,GAAG,SAAS,MAAM,QAAQ,CAAC;AAAA,IACtC,CAAC;AAAA,EACH;AAAA,EAEA,oBAAoB,MAA+B;AACjD,QAAI,KAAK,YAAY;AACnB,WAAK,aAAa,KAAK;AAAA,IACzB;AAEA,QAAI,KAAK,SAAS,YAAY;AAC5B,YAAM,YAAY,KAAK;AAEvB,UAAI,cAAc,eAAe;AAC/B,YAAI,KAAK,UAAW;AAEpB,aAAK,YAAY;AACjB,aAAK,MAAM,IAAI;AAAA,UACb,MAAM,kBAAI,gBAAgB;AAAA,UAC1B,WAAW,KAAK;AAAA,QAClB,CAAC;AAED,aAAK,qBAAqB,kBAAI,gBAAgB,oBAAoB,IAAI;AAAA,MACxE,WAAW,cAAc,UAAU;AACjC,YAAI,CAAC,KAAK,UAAW;AACrB,aAAK,qBAAqB,kBAAI,gBAAgB,oBAAoB,IAAI;AAAA,MACxE,WAAW,cAAc,kBAAkB;AACzC,YAAI,CAAC,KAAK,UAAW;AACrB,aAAK,qBAAqB,kBAAI,gBAAgB,sBAAsB,IAAI;AAAA,MAC1E,WAAW,cAAc,eAAe;AACtC,aAAK,qBAAqB,kBAAI,gBAAgB,oBAAoB,IAAI;AAAA,MACxE,WAAW,cAAc,aAAa;AACpC,YAAI,CAAC,KAAK,UAAW;AAErB,aAAK,YAAY;AACjB,aAAK,qBAAqB,kBAAI,gBAAgB,kBAAkB,IAAI;AAEpE,aAAK,MAAM,IAAI;AAAA,UACb,MAAM,kBAAI,gBAAgB;AAAA,UAC1B,WAAW,KAAK;AAAA,QAClB,CAAC;AAAA,MACH;AAAA,IACF,WAAW,KAAK,SAAS,SAAS;AAChC,WAAK,QAAQ,KAAK,0BAA0B,EAAE,KAAK,CAAC;AACpD,YAAM,OAAQ,KAAK,eAA0B;AAC7C,YAAM,IAAI,MAAM,uBAAuB,IAAI,EAAE;AAAA,IAC/C;AAAA,EACF;AAAA,EAEA,qBAAqB,WAAgC,MAA+B;AAClF,UAAM,OAAO,mBAAmB,KAAK,MAAM,YAAY,MAAM,MAAM,KAAK,eAAe;AAEvF,QAAI,KAAK,SAAS,GAAG;AACnB,WAAK,MAAM,IAAI;AAAA,QACb,MAAM;AAAA,QACN,WAAW,KAAK;AAAA,QAChB,cAAc,CAAC,KAAK,CAAC,GAAI,GAAG,KAAK,MAAM,CAAC,CAAC;AAAA,MAC3C,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEA,uBAAuB,UAAkB;AACvC,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;AAAA,EAEA,kBAA0B;AACxB,UAAM,SAA4C;AAAA,MAChD,OAAO,KAAK,MAAM;AAAA,MAClB,aAAa,KAAK,MAAM,WAAW,SAAS;AAAA,MAC5C,UAAU;AAAA,MACV,aAAa,OAAO,KAAK,MAAM,SAAS;AAAA,IAC1C;AAGA,QAAI,KAAK,MAAM;AACb,aAAO,sBAAsB,KAAK,MAAM,kBAAkB,SAAS;AACrE,QAAI,KAAK,MAAM,aAAc,QAAO,gBAAgB,KAAK,MAAM,aAAa,SAAS;AACrF,QAAI,KAAK,MAAM,aAAc,QAAO,iBAAiB,KAAK,MAAM,aAAa,SAAS;AAEtF,QAAI,KAAK,MAAM,SAAS,SAAS,EAAG,QAAO,UAAU,KAAK,MAAM;AAChE,QAAI,KAAK,MAAM,QAAQ,KAAK,MAAM,KAAK,SAAS,EAAG,QAAO,MAAM,KAAK,MAAM;AAG3E,QAAI,KAAK,MAAM,gBAAgB,KAAK,MAAM,aAAa,SAAS,GAAG;AACjE,aAAO,gBAAgB,KAAK,MAAM;AAAA,IACpC;AAEA,UAAM,UAAU,KAAK,MAAM,YAAY,QAAQ,SAAS,IAAI;AAC5D,UAAM,KAAK,YAAY,UAAU,MAAM;AACvC,WAAO,GAAG,OAAO,IAAI,EAAE;AAAA,EACzB;AAAA,EAES,QAAQ;AAxfnB;AAyfI,UAAM,MAAM;AACZ,eAAK,QAAL,mBAAU;AAAA,EACZ;AACF;AAIA,SAAS,mBACP,UACA,MACA,iBACkB;AAClB,QAAM,aAAa,KAAK;AACxB,QAAM,YAAa,KAAK,SAA4C,CAAC;AAErE,MAAI,CAAC,aAAa,UAAU,WAAW,GAAG;AACxC,WAAO,CAAC;AAAA,EACV;AAEA,MAAI,aAAa;AACjB,MAAI,UAAU,SAAS,GAAG;AACxB,UAAM,MAAM,UAAU,OAAO,CAAC,KAAa,MAAM,OAAQ,EAAE,cAAyB,IAAI,CAAC;AACzF,iBAAa,MAAM,UAAU;AAAA,EAC/B;AAGA,QAAM,uBAAuB,MAAM,QAAQ,KAAK,SAAS,IAAK,KAAK,YAAyB,CAAC;AAC7F,QAAM,oBAAoB,qBAAqB,IAAI,CAAC,aAAS,iCAAkB,IAAI,CAAC;AACpF,QAAM,kBACJ,kBAAkB,SAAS,IAAI,kBAAkB,CAAC,QAAK,iCAAkB,QAAQ;AAEnF,QAAM,KAAqB;AAAA,IACzB,UAAU;AAAA,IACV,YAAa,KAAK,sBAAiC,KAAK;AAAA,IACxD,UAAW,KAAK,oBAA+B,KAAK;AAAA,IACpD;AAAA,IACA,MAAM,cAAc;AAAA;AAAA,IAEpB,iBAAiB,kBAAkB,SAAS,IAAI,oBAAoB;AAAA;AAAA;AAAA;AAAA,IAIpE,OAAO,UAAU;AAAA,MAAI,CAAC,aACpB,iCAAkB;AAAA,QAChB,MAAO,KAAK,QAAmB;AAAA,QAC/B,YAAa,KAAK,SAAoB,KAAK;AAAA,QAC3C,UAAW,KAAK,OAAkB,KAAK;AAAA,QACvC,YAAa,KAAK,cAAyB;AAAA,QAC3C;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO,CAAC,EAAE;AACZ;","names":["_a"]}
|
|
1
|
+
{"version":3,"sources":["../src/stt_v2.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport {\n type APIConnectOptions,\n AudioByteStream,\n Event,\n calculateAudioDurationSeconds,\n createTimedString,\n log,\n normalizeLanguage,\n stt,\n} from '@livekit/agents';\nimport type { AudioFrame } from '@livekit/rtc-node';\nimport * as queryString from 'node:querystring';\nimport { WebSocket } from 'ws';\nimport { PeriodicCollector } from './_utils.js';\nimport type { V2Models } from './models.js';\n\nconst _CLOSE_MSG = JSON.stringify({ type: 'CloseStream' });\n\n// --- Configuration ---\n\n/**\n * Configuration options for STTv2 (Deepgram Flux model).\n */\nexport interface STTv2Options {\n apiKey?: string;\n model: V2Models | string;\n sampleRate: number;\n keyterms: string[];\n endpointUrl: string;\n language?: string;\n eagerEotThreshold?: number;\n eotThreshold?: number;\n eotTimeoutMs?: number;\n mipOptOut?: boolean;\n tags?: string[];\n /**\n * List of language hints to bias the model for improved accuracy.\n * Only usable with `flux-general-multi`.\n */\n // Ref: python livekit-plugins/livekit-plugins-deepgram/livekit/plugins/deepgram/stt_v2.py - 61 line\n languageHint?: string[];\n}\n\nconst defaultSTTv2Options: Omit<STTv2Options, 'apiKey'> = {\n model: 'flux-general-en',\n sampleRate: 16000,\n keyterms: [],\n endpointUrl: 'wss://api.deepgram.com/v2/listen',\n language: 'en',\n mipOptOut: false,\n};\n\nfunction validateTags(tags: string[]): string[] {\n for (const tag of tags) {\n if (tag.length > 128) {\n throw new Error('tag must be no more than 128 characters');\n }\n }\n return tags;\n}\n\n/**\n * Deepgram STTv2 using the Flux model for streaming speech-to-text.\n *\n * This uses Deepgram's V2 API (`/v2/listen`) which provides turn-based\n * transcription with support for preemptive generation.\n *\n * @remarks\n * Key differences from STT (V1):\n * - Uses `TurnInfo` events instead of `SpeechStarted`/`Results`\n * - Supports `eagerEotThreshold` for preemptive LLM generation\n * - Sends `PREFLIGHT_TRANSCRIPT` events when eager end-of-turn is detected\n *\n * @example\n * ```typescript\n * import { STTv2 } from '@livekit/agents-plugin-deepgram';\n *\n * const stt = new STTv2({\n * model: 'flux-general-en',\n * eagerEotThreshold: 0.5, // Enable preemptive generation\n * });\n *\n * const stream = stt.stream();\n * stream.pushFrame(audioFrame);\n *\n * for await (const event of stream) {\n * if (event.type === SpeechEventType.FINAL_TRANSCRIPT) {\n * console.log(event.alternatives?.[0]?.text);\n * }\n * }\n * ```\n */\nexport class STTv2 extends stt.STT {\n readonly label = 'deepgram.STTv2';\n #opts: STTv2Options;\n #apiKey: string;\n #logger = log();\n // session keyterm propagation)\n #streams = new Set<WeakRef<SpeechStreamv2>>();\n #userKeyterms: string[];\n #sessionKeyterms: string[] = [];\n\n /**\n * Create a new Deepgram STTv2 instance.\n *\n * @param opts - Configuration options\n * @param opts.apiKey - Deepgram API key (defaults to `DEEPGRAM_API_KEY` env var)\n * @param opts.model - Model to use (default: `flux-general-en`)\n * @param opts.eagerEotThreshold - Threshold (0.3-0.9) for preemptive generation\n * @param opts.eotThreshold - End-of-turn detection threshold (default: 0.7)\n * @param opts.eotTimeoutMs - End-of-turn timeout in ms (default: 3000)\n * @param opts.keyterms - List of key terms to improve recognition\n * @param opts.tags - Tags for usage reporting (max 128 chars each)\n * @param opts.languageHint - List of language hints to bias the model for improved accuracy.\n * Only usable with `flux-general-multi`.\n *\n * @throws Error if no API key is provided\n */\n constructor(opts: Partial<STTv2Options> = {}) {\n super({\n streaming: true,\n interimResults: true,\n alignedTranscript: 'word',\n keyterms: true,\n });\n\n this.#opts = {\n ...defaultSTTv2Options,\n ...opts,\n language: opts.language ? normalizeLanguage(opts.language) : defaultSTTv2Options.language,\n };\n this.#userKeyterms = [...this.#opts.keyterms];\n\n const apiKey = opts.apiKey || process.env.DEEPGRAM_API_KEY;\n if (!apiKey) {\n throw new Error('Deepgram API key is required');\n }\n this.#apiKey = apiKey;\n\n if (this.#opts.tags) {\n this.#opts.tags = validateTags(this.#opts.tags);\n }\n\n // Ref: python livekit-plugins/livekit-plugins-deepgram/livekit/plugins/deepgram/stt_v2.py - 134-138 lines\n if (\n this.#opts.languageHint &&\n this.#opts.languageHint.length > 0 &&\n this.#opts.model !== 'flux-general-multi'\n ) {\n this.#logger.warn(\n { model: this.#opts.model },\n '`languageHint` is only supported by `flux-general-multi` and will be ignored for this model',\n );\n }\n }\n\n /** The model being used for transcription */\n get model(): string {\n return this.#opts.model;\n }\n\n /** The STT provider name */\n get provider(): string {\n return 'Deepgram';\n }\n\n protected async _recognize(\n _frame: AudioFrame | AudioFrame[],\n _abortSignal?: AbortSignal,\n ): Promise<stt.SpeechEvent> {\n throw new Error('V2 API does not support non-streaming recognize. Use .stream()');\n }\n\n /**\n * Create a new streaming transcription session.\n *\n * @param options - Stream options\n * @returns A SpeechStream that emits transcription events\n */\n stream(options?: { connOptions?: APIConnectOptions }): stt.SpeechStream {\n const streamOpts = { ...this.#opts, apiKey: this.#apiKey };\n const stream = new SpeechStreamv2(this, streamOpts, options?.connOptions);\n this.#streams.add(new WeakRef(stream));\n return stream;\n }\n\n /**\n * Update STT options. Changes will take effect on the next stream.\n *\n * @param opts - Partial options to update\n */\n updateOptions(opts: Partial<STTv2Options>) {\n const nextOpts = { ...opts };\n if (nextOpts.keyterms !== undefined) {\n this.#userKeyterms = [...nextOpts.keyterms];\n nextOpts.keyterms = [...new Set([...this.#userKeyterms, ...this.#sessionKeyterms])];\n }\n this.#opts = {\n ...this.#opts,\n ...nextOpts,\n language:\n opts.language !== undefined ? normalizeLanguage(opts.language) : this.#opts.language,\n };\n if (opts.tags) this.#opts.tags = validateTags(opts.tags);\n // Ref: python livekit-plugins/livekit-plugins-deepgram/livekit/plugins/deepgram/stt_v2.py - 244-249 lines\n if (\n this.#opts.languageHint &&\n this.#opts.languageHint.length > 0 &&\n this.#opts.model !== 'flux-general-multi'\n ) {\n this.#logger.warn(\n { model: this.#opts.model },\n '`languageHint` is only supported by `flux-general-multi` and will be ignored for this model',\n );\n }\n this.#logger.debug('Updated STTv2 options');\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.#userKeyterms, ...keyterms])];\n this.#opts.keyterms = 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({ keyterms: merged });\n }\n }\n }\n}\n\n// --- Stream Implementation ---\n\nclass SpeechStreamv2 extends stt.SpeechStream {\n readonly label = 'deepgram.SpeechStreamv2';\n #opts: STTv2Options & { apiKey: string };\n #logger = log();\n #ws: WebSocket | null = null;\n\n #audioDurationCollector: PeriodicCollector<number>;\n #requestId = '';\n #speaking = false;\n\n // Parity: _reconnect_event - using existing Event class from @livekit/agents\n #reconnectEvent = new Event();\n\n // keyterms set while the user is speaking; applied at END_OF_SPEECH (latest wins)\n /** @internal */\n _pendingKeyterm: string[] | null = null;\n\n constructor(\n sttInstance: STTv2,\n opts: STTv2Options & { apiKey: string },\n connOptions?: APIConnectOptions,\n ) {\n super(sttInstance, opts.sampleRate, connOptions);\n this.#opts = opts;\n\n this.#audioDurationCollector = new PeriodicCollector(\n (duration) => this.#onAudioDurationReport(duration),\n { duration: 5.0 },\n );\n }\n\n /** @internal */\n get _speaking(): boolean {\n return this.#speaking;\n }\n\n updateOptions(opts: Partial<STTv2Options>) {\n this.#logger.debug('Stream received option update', opts);\n this.#opts = {\n ...this.#opts,\n ...opts,\n language:\n opts.language !== undefined ? normalizeLanguage(opts.language) : this.#opts.language,\n };\n if (opts.tags) this.#opts.tags = validateTags(opts.tags);\n if (opts.keyterms !== undefined) {\n this._pendingKeyterm = null;\n }\n\n // Trigger reconnection loop\n this.#reconnectEvent.set();\n }\n\n #onEndOfSpeech() {\n if (this._pendingKeyterm !== null) {\n this.updateOptions({ keyterms: this._pendingKeyterm });\n this._pendingKeyterm = null;\n }\n }\n\n protected async run() {\n // Outer Loop: Handles reconnections (Configuration updates)\n while (!this.closed) {\n try {\n this.#reconnectEvent.clear();\n\n const url = this.#getDeepgramUrl();\n this.#logger.debug(`Connecting to Deepgram: ${url}`);\n\n this.#ws = new WebSocket(url, {\n headers: { Authorization: `Token ${this.#opts.apiKey}` },\n });\n\n // 1. Wait for Connection Open\n await new Promise<void>((resolve, reject) => {\n if (!this.#ws) return reject(new Error('WebSocket not initialized'));\n\n const onOpen = () => {\n this.#ws?.off('error', onError);\n resolve();\n };\n const onError = (err: Error) => {\n this.#ws?.off('open', onOpen);\n reject(err);\n };\n\n this.#ws.once('open', onOpen);\n this.#ws.once('error', onError);\n });\n\n // 2. Run Concurrent Tasks (Send & Receive)\n const sendPromise = this.#sendTask();\n const recvPromise = this.#recvTask();\n const reconnectWait = this.#reconnectEvent.wait();\n\n // 3. Race: Normal Completion vs Reconnect Signal\n const result = await Promise.race([\n Promise.all([sendPromise, recvPromise]),\n reconnectWait.then(() => 'RECONNECT'),\n ]);\n\n if (result === 'RECONNECT') {\n this.#logger.debug('Reconnecting stream due to option update...');\n // Close current socket; loop will restart and open a new one\n this.#ws.close();\n } else {\n // Normal finish (Stream ended or Error thrown)\n break;\n }\n } catch (error) {\n this.#logger.error('Deepgram stream error', { error });\n throw error; // Let Base Class handle retry logic\n } finally {\n if (this.#ws?.readyState === WebSocket.OPEN) {\n this.#ws.close();\n }\n }\n }\n this.close();\n }\n\n async #sendTask() {\n if (!this.#ws) return;\n\n // Buffer audio into 50ms chunks (Parity)\n const samples50ms = Math.floor(this.#opts.sampleRate / 20);\n const audioBstream = new AudioByteStream(this.#opts.sampleRate, 1, samples50ms);\n\n let hasEnded = false;\n\n // Manual Iterator to allow racing against Reconnect Signal\n const iterator = this.input[Symbol.asyncIterator]();\n\n while (true) {\n const nextPromise = iterator.next();\n // If reconnect signal fires, abort the wait\n const abortPromise = this.#reconnectEvent.wait().then(() => ({ abort: true }) as const);\n\n const result = await Promise.race([nextPromise, abortPromise]);\n\n // Check if we need to abort (Reconnect) or if stream is done\n if ('abort' in result || result.done) {\n if (!('abort' in result) && result.done) {\n // Normal stream end\n hasEnded = true;\n } else {\n // Reconnect triggered - break loop immediately\n break;\n }\n }\n\n // If we broke above, we don't process data. If not, 'result' is IteratorResult\n if (hasEnded && result.value === undefined) {\n // Process flush below\n } else if ('value' in result) {\n const data = result.value;\n const frames: AudioFrame[] = [];\n\n if (data === SpeechStreamv2.FLUSH_SENTINEL) {\n frames.push(...audioBstream.flush());\n hasEnded = true;\n } else {\n frames.push(...audioBstream.write((data as AudioFrame).data.buffer as ArrayBuffer));\n }\n\n for (const frame of frames) {\n this.#audioDurationCollector.push(calculateAudioDurationSeconds(frame));\n\n if (this.#ws!.readyState === WebSocket.OPEN) {\n this.#ws!.send(frame.data);\n }\n\n if (hasEnded) {\n this.#audioDurationCollector.flush();\n hasEnded = false;\n }\n }\n }\n\n if (hasEnded) break;\n }\n\n // Only send CloseStream if we are exiting normally (not reconnecting)\n if (!this.#reconnectEvent.isSet && this.#ws!.readyState === WebSocket.OPEN) {\n this.#logger.debug('Sending CloseStream message to Deepgram');\n this.#ws!.send(_CLOSE_MSG);\n }\n }\n\n async #recvTask() {\n if (!this.#ws) return;\n\n return new Promise<void>((resolve) => {\n if (!this.#ws) return resolve();\n\n this.#ws.on('message', (data: Buffer, isBinary: boolean) => {\n if (isBinary) {\n this.#logger.warn('Received unexpected binary message from Deepgram');\n return;\n }\n try {\n const msg = JSON.parse(data.toString());\n this.#processStreamEvent(msg);\n } catch (error) {\n this.#logger.error('Failed to parse Deepgram message', { error });\n }\n });\n\n this.#ws.on('close', (code, reason) => {\n this.#logger.debug(`Deepgram WebSocket closed: ${code} ${reason}`);\n resolve();\n });\n\n // Errors are caught by run() listener, resolve here to clean up task\n this.#ws.on('error', () => resolve());\n });\n }\n\n #processStreamEvent(data: Record<string, unknown>) {\n if (data.request_id) {\n this.#requestId = data.request_id as string;\n }\n\n if (data.type === 'TurnInfo') {\n const eventType = data.event;\n\n if (eventType === 'StartOfTurn') {\n if (this.#speaking) return;\n\n this.#speaking = true;\n this.queue.put({\n type: stt.SpeechEventType.START_OF_SPEECH,\n requestId: this.#requestId,\n });\n\n this.#sendTranscriptEvent(stt.SpeechEventType.INTERIM_TRANSCRIPT, data);\n } else if (eventType === 'Update') {\n if (!this.#speaking) return;\n this.#sendTranscriptEvent(stt.SpeechEventType.INTERIM_TRANSCRIPT, data);\n } else if (eventType === 'EagerEndOfTurn') {\n if (!this.#speaking) return;\n this.#sendTranscriptEvent(stt.SpeechEventType.PREFLIGHT_TRANSCRIPT, data);\n } else if (eventType === 'TurnResumed') {\n this.#sendTranscriptEvent(stt.SpeechEventType.INTERIM_TRANSCRIPT, data);\n } else if (eventType === 'EndOfTurn') {\n if (!this.#speaking) return;\n\n this.#speaking = false;\n this.#sendTranscriptEvent(stt.SpeechEventType.FINAL_TRANSCRIPT, data);\n\n this.queue.put({\n type: stt.SpeechEventType.END_OF_SPEECH,\n requestId: this.#requestId,\n });\n this.#onEndOfSpeech();\n }\n } else if (data.type === 'Error') {\n this.#logger.warn('deepgram sent an error', { data });\n const desc = (data.description as string) || 'unknown error from deepgram';\n throw new Error(`Deepgram API Error: ${desc}`);\n }\n }\n\n #sendTranscriptEvent(eventType: stt.SpeechEventType, data: Record<string, unknown>) {\n const alts = parseTranscription(this.#opts.language || 'en', data, this.startTimeOffset);\n\n if (alts.length > 0) {\n this.queue.put({\n type: eventType,\n requestId: this.#requestId,\n alternatives: [alts[0]!, ...alts.slice(1)],\n });\n }\n }\n\n #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 #getDeepgramUrl(): string {\n const params: Record<string, string | string[]> = {\n model: this.#opts.model,\n sample_rate: this.#opts.sampleRate.toString(),\n encoding: 'linear16',\n mip_opt_out: String(this.#opts.mipOptOut),\n };\n\n // Note: v2 API does NOT include 'language' parameter\n if (this.#opts.eagerEotThreshold)\n params.eager_eot_threshold = this.#opts.eagerEotThreshold.toString();\n if (this.#opts.eotThreshold) params.eot_threshold = this.#opts.eotThreshold.toString();\n if (this.#opts.eotTimeoutMs) params.eot_timeout_ms = this.#opts.eotTimeoutMs.toString();\n\n if (this.#opts.keyterms.length > 0) params.keyterm = this.#opts.keyterms;\n if (this.#opts.tags && this.#opts.tags.length > 0) params.tag = this.#opts.tags;\n\n // Ref: python livekit-plugins/livekit-plugins-deepgram/livekit/plugins/deepgram/stt_v2.py - 480-481 lines\n if (this.#opts.languageHint && this.#opts.languageHint.length > 0) {\n params.language_hint = this.#opts.languageHint;\n }\n\n const baseUrl = this.#opts.endpointUrl.replace(/^http/, 'ws');\n const qs = queryString.stringify(params);\n return `${baseUrl}?${qs}`;\n }\n\n override close() {\n super.close();\n this.#ws?.close();\n }\n}\n\n// --- Helpers ---\n\nfunction parseTranscription(\n language: string,\n data: Record<string, unknown>,\n startTimeOffset: number,\n): stt.SpeechData[] {\n const transcript = data.transcript as string | undefined;\n const wordsData = (data.words as Array<Record<string, unknown>>) || [];\n\n if (!wordsData || wordsData.length === 0) {\n return [];\n }\n\n let confidence = 0;\n if (wordsData.length > 0) {\n const sum = wordsData.reduce((acc: number, w) => acc + ((w.confidence as number) || 0), 0);\n confidence = sum / wordsData.length;\n }\n\n // Ref: python livekit-plugins/livekit-plugins-deepgram/livekit/plugins/deepgram/stt_v2.py - 587-591 lines\n const detectedLanguagesRaw = Array.isArray(data.languages) ? (data.languages as string[]) : [];\n const detectedLanguages = detectedLanguagesRaw.map((lang) => normalizeLanguage(lang));\n const primaryLanguage =\n detectedLanguages.length > 0 ? detectedLanguages[0]! : normalizeLanguage(language);\n\n const sd: stt.SpeechData = {\n language: primaryLanguage,\n startTime: ((data.audio_window_start as number) || 0) + startTimeOffset,\n endTime: ((data.audio_window_end as number) || 0) + startTimeOffset,\n confidence: confidence,\n text: transcript || '',\n // Ref: python livekit-plugins/livekit-plugins-deepgram/livekit/plugins/deepgram/stt_v2.py - 598 line\n sourceLanguages: detectedLanguages.length > 0 ? detectedLanguages : undefined,\n // Note: Deepgram V2 (Flux) API does not provide word-level timing (start/end).\n // Words only contain 'word' and 'confidence' fields, so startTime/endTime will be 0.\n // See: https://developers.deepgram.com/docs/flux/nova-3-migration\n words: wordsData.map((word) =>\n createTimedString({\n text: (word.word as string) ?? '',\n startTime: ((word.start as number) ?? 0) + startTimeOffset,\n endTime: ((word.end as number) ?? 0) + startTimeOffset,\n confidence: (word.confidence as number) ?? 0.0,\n startTimeOffset,\n }),\n ),\n };\n\n return [sd];\n}\n"],"mappings":"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","names":["_a"]}
|
package/dist/stt_v2.d.cts
CHANGED
package/dist/stt_v2.d.ts
CHANGED
package/dist/stt_v2.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stt_v2.d.ts","sourceRoot":"","sources":["../src/stt_v2.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,KAAK,iBAAiB,EAOtB,GAAG,EACJ,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAIpD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAM5C;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,QAAQ,GAAG,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB;;;OAGG;IAEH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAoBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,qBAAa,KAAM,SAAQ,GAAG,CAAC,GAAG;;IAChC,QAAQ,CAAC,KAAK,oBAAoB;
|
|
1
|
+
{"version":3,"file":"stt_v2.d.ts","sourceRoot":"","sources":["../src/stt_v2.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,KAAK,iBAAiB,EAOtB,GAAG,EACJ,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAIpD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAM5C;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,QAAQ,GAAG,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB;;;OAGG;IAEH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAoBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,qBAAa,KAAM,SAAQ,GAAG,CAAC,GAAG;;IAChC,QAAQ,CAAC,KAAK,oBAAoB;IASlC;;;;;;;;;;;;;;;OAeG;gBACS,IAAI,GAAE,OAAO,CAAC,YAAY,CAAM;IAsC5C,6CAA6C;IAC7C,IAAI,KAAK,IAAI,MAAM,CAElB;IAED,4BAA4B;IAC5B,IAAI,QAAQ,IAAI,MAAM,CAErB;cAEe,UAAU,CACxB,MAAM,EAAE,UAAU,GAAG,UAAU,EAAE,EACjC,YAAY,CAAC,EAAE,WAAW,GACzB,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC;IAI3B;;;;;OAKG;IACH,MAAM,CAAC,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,iBAAiB,CAAA;KAAE,GAAG,GAAG,CAAC,YAAY;IAOvE;;;;OAIG;IACH,aAAa,CAAC,IAAI,EAAE,OAAO,CAAC,YAAY,CAAC;IA2BhC,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,IAAI;CAwB1D"}
|
package/dist/stt_v2.js
CHANGED
|
@@ -32,6 +32,10 @@ class STTv2 extends stt.STT {
|
|
|
32
32
|
#opts;
|
|
33
33
|
#apiKey;
|
|
34
34
|
#logger = log();
|
|
35
|
+
// session keyterm propagation)
|
|
36
|
+
#streams = /* @__PURE__ */ new Set();
|
|
37
|
+
#userKeyterms;
|
|
38
|
+
#sessionKeyterms = [];
|
|
35
39
|
/**
|
|
36
40
|
* Create a new Deepgram STTv2 instance.
|
|
37
41
|
*
|
|
@@ -52,13 +56,15 @@ class STTv2 extends stt.STT {
|
|
|
52
56
|
super({
|
|
53
57
|
streaming: true,
|
|
54
58
|
interimResults: true,
|
|
55
|
-
alignedTranscript: "word"
|
|
59
|
+
alignedTranscript: "word",
|
|
60
|
+
keyterms: true
|
|
56
61
|
});
|
|
57
62
|
this.#opts = {
|
|
58
63
|
...defaultSTTv2Options,
|
|
59
64
|
...opts,
|
|
60
65
|
language: opts.language ? normalizeLanguage(opts.language) : defaultSTTv2Options.language
|
|
61
66
|
};
|
|
67
|
+
this.#userKeyterms = [...this.#opts.keyterms];
|
|
62
68
|
const apiKey = opts.apiKey || process.env.DEEPGRAM_API_KEY;
|
|
63
69
|
if (!apiKey) {
|
|
64
70
|
throw new Error("Deepgram API key is required");
|
|
@@ -93,7 +99,9 @@ class STTv2 extends stt.STT {
|
|
|
93
99
|
*/
|
|
94
100
|
stream(options) {
|
|
95
101
|
const streamOpts = { ...this.#opts, apiKey: this.#apiKey };
|
|
96
|
-
|
|
102
|
+
const stream = new SpeechStreamv2(this, streamOpts, options == null ? void 0 : options.connOptions);
|
|
103
|
+
this.#streams.add(new WeakRef(stream));
|
|
104
|
+
return stream;
|
|
97
105
|
}
|
|
98
106
|
/**
|
|
99
107
|
* Update STT options. Changes will take effect on the next stream.
|
|
@@ -101,9 +109,14 @@ class STTv2 extends stt.STT {
|
|
|
101
109
|
* @param opts - Partial options to update
|
|
102
110
|
*/
|
|
103
111
|
updateOptions(opts) {
|
|
112
|
+
const nextOpts = { ...opts };
|
|
113
|
+
if (nextOpts.keyterms !== void 0) {
|
|
114
|
+
this.#userKeyterms = [...nextOpts.keyterms];
|
|
115
|
+
nextOpts.keyterms = [.../* @__PURE__ */ new Set([...this.#userKeyterms, ...this.#sessionKeyterms])];
|
|
116
|
+
}
|
|
104
117
|
this.#opts = {
|
|
105
118
|
...this.#opts,
|
|
106
|
-
...
|
|
119
|
+
...nextOpts,
|
|
107
120
|
language: opts.language !== void 0 ? normalizeLanguage(opts.language) : this.#opts.language
|
|
108
121
|
};
|
|
109
122
|
if (opts.tags) this.#opts.tags = validateTags(opts.tags);
|
|
@@ -115,6 +128,26 @@ class STTv2 extends stt.STT {
|
|
|
115
128
|
}
|
|
116
129
|
this.#logger.debug("Updated STTv2 options");
|
|
117
130
|
}
|
|
131
|
+
_updateSessionKeyterms(keyterms) {
|
|
132
|
+
if (keyterms.length === this.#sessionKeyterms.length && keyterms.every((t, i) => t === this.#sessionKeyterms[i])) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
this.#sessionKeyterms = [...keyterms];
|
|
136
|
+
const merged = [.../* @__PURE__ */ new Set([...this.#userKeyterms, ...keyterms])];
|
|
137
|
+
this.#opts.keyterms = merged;
|
|
138
|
+
for (const ref of this.#streams) {
|
|
139
|
+
const stream = ref.deref();
|
|
140
|
+
if (!stream) {
|
|
141
|
+
this.#streams.delete(ref);
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (stream._speaking) {
|
|
145
|
+
stream._pendingKeyterm = merged;
|
|
146
|
+
} else {
|
|
147
|
+
stream.updateOptions({ keyterms: merged });
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
118
151
|
}
|
|
119
152
|
class SpeechStreamv2 extends stt.SpeechStream {
|
|
120
153
|
label = "deepgram.SpeechStreamv2";
|
|
@@ -126,6 +159,9 @@ class SpeechStreamv2 extends stt.SpeechStream {
|
|
|
126
159
|
#speaking = false;
|
|
127
160
|
// Parity: _reconnect_event - using existing Event class from @livekit/agents
|
|
128
161
|
#reconnectEvent = new Event();
|
|
162
|
+
// keyterms set while the user is speaking; applied at END_OF_SPEECH (latest wins)
|
|
163
|
+
/** @internal */
|
|
164
|
+
_pendingKeyterm = null;
|
|
129
165
|
constructor(sttInstance, opts, connOptions) {
|
|
130
166
|
super(sttInstance, opts.sampleRate, connOptions);
|
|
131
167
|
this.#opts = opts;
|
|
@@ -134,6 +170,10 @@ class SpeechStreamv2 extends stt.SpeechStream {
|
|
|
134
170
|
{ duration: 5 }
|
|
135
171
|
);
|
|
136
172
|
}
|
|
173
|
+
/** @internal */
|
|
174
|
+
get _speaking() {
|
|
175
|
+
return this.#speaking;
|
|
176
|
+
}
|
|
137
177
|
updateOptions(opts) {
|
|
138
178
|
this.#logger.debug("Stream received option update", opts);
|
|
139
179
|
this.#opts = {
|
|
@@ -142,8 +182,17 @@ class SpeechStreamv2 extends stt.SpeechStream {
|
|
|
142
182
|
language: opts.language !== void 0 ? normalizeLanguage(opts.language) : this.#opts.language
|
|
143
183
|
};
|
|
144
184
|
if (opts.tags) this.#opts.tags = validateTags(opts.tags);
|
|
185
|
+
if (opts.keyterms !== void 0) {
|
|
186
|
+
this._pendingKeyterm = null;
|
|
187
|
+
}
|
|
145
188
|
this.#reconnectEvent.set();
|
|
146
189
|
}
|
|
190
|
+
#onEndOfSpeech() {
|
|
191
|
+
if (this._pendingKeyterm !== null) {
|
|
192
|
+
this.updateOptions({ keyterms: this._pendingKeyterm });
|
|
193
|
+
this._pendingKeyterm = null;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
147
196
|
async run() {
|
|
148
197
|
var _a;
|
|
149
198
|
while (!this.closed) {
|
|
@@ -291,6 +340,7 @@ class SpeechStreamv2 extends stt.SpeechStream {
|
|
|
291
340
|
type: stt.SpeechEventType.END_OF_SPEECH,
|
|
292
341
|
requestId: this.#requestId
|
|
293
342
|
});
|
|
343
|
+
this.#onEndOfSpeech();
|
|
294
344
|
}
|
|
295
345
|
} else if (data.type === "Error") {
|
|
296
346
|
this.#logger.warn("deepgram sent an error", { data });
|