@livekit/agents-plugin-assemblyai 1.4.6 → 1.4.8
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/models.cjs.map +1 -1
- package/dist/models.d.cts +2 -1
- package/dist/models.d.ts +2 -1
- package/dist/models.d.ts.map +1 -1
- package/dist/stt.cjs +20 -6
- package/dist/stt.cjs.map +1 -1
- package/dist/stt.d.cts +10 -1
- package/dist/stt.d.ts +10 -1
- package/dist/stt.d.ts.map +1 -1
- package/dist/stt.js +20 -6
- package/dist/stt.js.map +1 -1
- package/package.json +5 -5
- package/src/models.ts +4 -0
- package/src/stt.ts +33 -8
package/dist/index.cjs
CHANGED
package/dist/index.js
CHANGED
package/dist/models.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/models.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\n\nexport type STTModels =\n | 'universal-streaming-english'\n | 'universal-streaming-multilingual'\n | 'u3-rt-pro'\n // Deprecated alias — AssemblyAI maps this to `u3-rt-pro` server-side, but the\n // Python plugin emits a warning and rewrites it. Kept here so TS users don't\n // break if they already pass it.\n | 'u3-pro';\n\nexport type STTEncoding = 'pcm_s16le' | 'pcm_mulaw';\n"],"mappings":";;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/models.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\n\nexport type STTModels =\n | 'universal-streaming-english'\n | 'universal-streaming-multilingual'\n | 'u3-rt-pro'\n | 'u3-rt-pro-beta-1'\n | 'universal-3-5-pro'\n // Deprecated alias — AssemblyAI maps this to `u3-rt-pro` server-side, but the\n // Python plugin emits a warning and rewrites it. Kept here so TS users don't\n // break if they already pass it.\n | 'u3-pro';\n\nexport type STTEncoding = 'pcm_s16le' | 'pcm_mulaw';\n\nexport type VoiceFocus = 'near-field' | 'far-field';\n"],"mappings":";;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
|
package/dist/models.d.cts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
export type STTModels = 'universal-streaming-english' | 'universal-streaming-multilingual' | 'u3-rt-pro' | 'u3-pro';
|
|
1
|
+
export type STTModels = 'universal-streaming-english' | 'universal-streaming-multilingual' | 'u3-rt-pro' | 'u3-rt-pro-beta-1' | 'universal-3-5-pro' | 'u3-pro';
|
|
2
2
|
export type STTEncoding = 'pcm_s16le' | 'pcm_mulaw';
|
|
3
|
+
export type VoiceFocus = 'near-field' | 'far-field';
|
|
3
4
|
//# sourceMappingURL=models.d.ts.map
|
package/dist/models.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
export type STTModels = 'universal-streaming-english' | 'universal-streaming-multilingual' | 'u3-rt-pro' | 'u3-pro';
|
|
1
|
+
export type STTModels = 'universal-streaming-english' | 'universal-streaming-multilingual' | 'u3-rt-pro' | 'u3-rt-pro-beta-1' | 'universal-3-5-pro' | 'u3-pro';
|
|
2
2
|
export type STTEncoding = 'pcm_s16le' | 'pcm_mulaw';
|
|
3
|
+
export type VoiceFocus = 'near-field' | 'far-field';
|
|
3
4
|
//# sourceMappingURL=models.d.ts.map
|
package/dist/models.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,SAAS,GACjB,6BAA6B,GAC7B,kCAAkC,GAClC,WAAW,
|
|
1
|
+
{"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,SAAS,GACjB,6BAA6B,GAC7B,kCAAkC,GAClC,WAAW,GACX,kBAAkB,GAClB,mBAAmB,GAInB,QAAQ,CAAC;AAEb,MAAM,MAAM,WAAW,GAAG,WAAW,GAAG,WAAW,CAAC;AAEpD,MAAM,MAAM,UAAU,GAAG,YAAY,GAAG,WAAW,CAAC"}
|
package/dist/stt.cjs
CHANGED
|
@@ -24,12 +24,16 @@ __export(stt_exports, {
|
|
|
24
24
|
module.exports = __toCommonJS(stt_exports);
|
|
25
25
|
var import_agents = require("@livekit/agents");
|
|
26
26
|
var import_ws = require("ws");
|
|
27
|
+
const U3_PRO_MODELS = ["u3-rt-pro", "u3-rt-pro-beta-1", "universal-3-5-pro"];
|
|
28
|
+
function isU3ProModel(model) {
|
|
29
|
+
return U3_PRO_MODELS.includes(model);
|
|
30
|
+
}
|
|
27
31
|
const defaultSTTOptions = {
|
|
28
32
|
apiKey: process.env.ASSEMBLYAI_API_KEY,
|
|
29
33
|
sampleRate: 16e3,
|
|
30
34
|
bufferSizeMs: 50,
|
|
31
35
|
encoding: "pcm_s16le",
|
|
32
|
-
speechModel: "universal-
|
|
36
|
+
speechModel: "universal-3-5-pro",
|
|
33
37
|
baseUrl: "wss://streaming.assemblyai.com"
|
|
34
38
|
};
|
|
35
39
|
class STT extends import_agents.stt.STT {
|
|
@@ -52,8 +56,15 @@ class STT extends import_agents.stt.STT {
|
|
|
52
56
|
(0, import_agents.log)().warn("'u3-pro' is deprecated, use 'u3-rt-pro' instead.");
|
|
53
57
|
opts.speechModel = "u3-rt-pro";
|
|
54
58
|
}
|
|
55
|
-
|
|
56
|
-
|
|
59
|
+
const speechModel = opts.speechModel ?? defaultSTTOptions.speechModel;
|
|
60
|
+
if (!isU3ProModel(speechModel)) {
|
|
61
|
+
for (const param of ["prompt", "voiceFocus", "voiceFocusThreshold", "mode"]) {
|
|
62
|
+
if (opts[param] !== void 0) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
`The '${param}' parameter is only supported with the ${U3_PRO_MODELS.join(", ")} models.`
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
57
68
|
}
|
|
58
69
|
const apiKey = opts.apiKey ?? defaultSTTOptions.apiKey;
|
|
59
70
|
if (!apiKey) {
|
|
@@ -176,11 +187,11 @@ class SpeechStream extends import_agents.stt.SpeechStream {
|
|
|
176
187
|
async #connectWS() {
|
|
177
188
|
let minSilence = this.#opts.minTurnSilence;
|
|
178
189
|
let maxSilence = this.#opts.maxTurnSilence;
|
|
179
|
-
if (this.#opts.speechModel
|
|
190
|
+
if (isU3ProModel(this.#opts.speechModel)) {
|
|
180
191
|
if (minSilence === void 0) minSilence = 100;
|
|
181
192
|
if (maxSilence === void 0) maxSilence = minSilence;
|
|
182
193
|
}
|
|
183
|
-
const defaultLanguageDetection = this.#opts.speechModel.includes("multilingual") || this.#opts.speechModel
|
|
194
|
+
const defaultLanguageDetection = this.#opts.speechModel.includes("multilingual") || isU3ProModel(this.#opts.speechModel);
|
|
184
195
|
const languageDetection = this.#opts.languageDetection ?? defaultLanguageDetection;
|
|
185
196
|
const liveConfig = {
|
|
186
197
|
sample_rate: this.#opts.sampleRate,
|
|
@@ -196,7 +207,10 @@ class SpeechStream extends import_agents.stt.SpeechStream {
|
|
|
196
207
|
vad_threshold: this.#opts.vadThreshold,
|
|
197
208
|
speaker_labels: this.#opts.speakerLabels,
|
|
198
209
|
max_speakers: this.#opts.maxSpeakers,
|
|
199
|
-
domain: this.#opts.domain
|
|
210
|
+
domain: this.#opts.domain,
|
|
211
|
+
voice_focus: this.#opts.voiceFocus,
|
|
212
|
+
voice_focus_threshold: this.#opts.voiceFocusThreshold,
|
|
213
|
+
mode: this.#opts.mode
|
|
200
214
|
};
|
|
201
215
|
const url = new URL(`${this.#opts.baseUrl}/v3/ws`);
|
|
202
216
|
for (const [key, value] of Object.entries(liveConfig)) {
|
package/dist/stt.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/stt.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\n//\nimport {\n type APIConnectOptions,\n type AudioBuffer,\n AudioByteStream,\n Future,\n Task,\n createTimedString,\n delay,\n log,\n normalizeLanguage,\n stt,\n waitForAbort,\n} from '@livekit/agents';\nimport type { AudioFrame } from '@livekit/rtc-node';\nimport type { RawData } from 'ws';\nimport { WebSocket } from 'ws';\nimport type { STTEncoding, STTModels } from './models.js';\n\n// AssemblyAI Universal-Streaming (v3) message envelope. All fields are optional\n// since we narrow on `type` before reading anything else.\ninterface StreamEventMessage {\n type?: 'Begin' | 'SpeechStarted' | 'Turn' | 'Termination' | string;\n // Begin\n id?: string;\n expires_at?: number;\n // Turn\n transcript?: string;\n utterance?: string;\n end_of_turn?: boolean;\n end_of_turn_confidence?: number;\n turn_is_formatted?: boolean;\n language_code?: string;\n speaker_label?: string;\n words?: Array<{\n text?: string;\n start?: number;\n end?: number;\n confidence?: number;\n speaker?: string;\n }>;\n // Termination\n audio_duration_seconds?: number;\n session_duration_seconds?: number;\n}\n\nexport interface STTOptions {\n apiKey?: string;\n sampleRate: number;\n /**\n * How large each chunk of audio is before being sent to AssemblyAI, in\n * milliseconds. Corresponds to Python's `buffer_size_seconds` (seconds there,\n * ms here per this repo's time-unit convention).\n */\n bufferSizeMs: number;\n encoding: STTEncoding;\n speechModel: STTModels;\n languageDetection?: boolean;\n endOfTurnConfidenceThreshold?: number;\n /** Minimum silence (ms) before a confident end-of-turn is finalized. */\n minTurnSilence?: number;\n /** Maximum silence (ms) before end-of-turn is forced regardless of confidence. */\n maxTurnSilence?: number;\n formatTurns?: boolean;\n keytermsPrompt?: string[];\n /** Only supported with the `u3-rt-pro` model. */\n prompt?: string;\n vadThreshold?: number;\n /**\n * Enable speaker diarization. Note: AssemblyAI will return per-word speaker\n * labels, but the JS framework's `stt.SpeechData` type does not yet expose\n * a `speakerId` field (unlike the Python framework), so the labels are not\n * currently surfaced on emitted events. Setting this to `true` still has\n * effect server-side. Once the base `SpeechData` interface gains speaker\n * support, `#processStreamEvent` should forward `data.words[].speaker` too.\n */\n speakerLabels?: boolean;\n maxSpeakers?: number;\n domain?: string;\n baseUrl: string;\n}\n\nconst defaultSTTOptions: STTOptions = {\n apiKey: process.env.ASSEMBLYAI_API_KEY,\n sampleRate: 16000,\n bufferSizeMs: 50,\n encoding: 'pcm_s16le',\n speechModel: 'universal-streaming-english',\n baseUrl: 'wss://streaming.assemblyai.com',\n};\n\nexport class STT extends stt.STT {\n #opts: STTOptions;\n #streams = new Set<WeakRef<SpeechStream>>();\n label = 'assemblyai.STT';\n\n get model(): string {\n return this.#opts.speechModel;\n }\n\n get provider(): string {\n return 'AssemblyAI';\n }\n\n constructor(opts: Partial<STTOptions> = {}) {\n super({\n streaming: true,\n interimResults: true,\n alignedTranscript: 'word',\n });\n\n if (opts.speechModel === 'u3-pro') {\n log().warn(\"'u3-pro' is deprecated, use 'u3-rt-pro' instead.\");\n opts.speechModel = 'u3-rt-pro';\n }\n\n if (opts.prompt !== undefined && opts.speechModel !== 'u3-rt-pro') {\n throw new Error(\"The 'prompt' parameter is only supported with the 'u3-rt-pro' model.\");\n }\n\n const apiKey = opts.apiKey ?? defaultSTTOptions.apiKey;\n if (!apiKey) {\n throw new Error(\n 'AssemblyAI API key is required. Pass one in via the `apiKey` parameter, or set it as the `ASSEMBLYAI_API_KEY` environment variable',\n );\n }\n\n // Minimize latency; matches LK's end-of-turn detector well.\n const minTurnSilence = opts.minTurnSilence ?? 100;\n\n this.#opts = {\n ...defaultSTTOptions,\n ...opts,\n apiKey,\n minTurnSilence,\n };\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n async _recognize(_: AudioBuffer): Promise<stt.SpeechEvent> {\n throw new Error('Non-streaming recognize is not supported on AssemblyAI STT');\n }\n\n updateOptions(opts: Partial<STTOptions>) {\n this.#opts = { ...this.#opts, ...opts };\n for (const ref of this.#streams) {\n const stream = ref.deref();\n if (stream) {\n stream.updateOptions(opts);\n } else {\n this.#streams.delete(ref);\n }\n }\n }\n\n stream(options?: { connOptions?: APIConnectOptions }): SpeechStream {\n const stream = new SpeechStream(this, this.#opts, options?.connOptions);\n this.#streams.add(new WeakRef(stream));\n return stream;\n }\n}\n\nexport class SpeechStream extends stt.SpeechStream {\n static readonly CLOSE_MSG = JSON.stringify({ type: 'Terminate' });\n\n #opts: STTOptions;\n #logger = log();\n #speechDurationInS = 0;\n #lastPreflightStartTime = 0;\n #pendingConfigMessages: Record<string, unknown>[] = [];\n #configMessagePending = new Future();\n #sessionId: string | null = null;\n #expiresAt: number | null = null;\n label = 'assemblyai.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 }\n\n /**\n * The AssemblyAI session ID. Set when the WebSocket connection is established\n * (before any speech events). Null until the connection completes.\n * Share this with the AssemblyAI team when reporting issues.\n */\n get sessionId(): string | null {\n return this.#sessionId;\n }\n\n /**\n * Unix timestamp when the AssemblyAI session expires. Set alongside\n * {@link sessionId} when the WebSocket connection is established.\n */\n get expiresAt(): number | null {\n return this.#expiresAt;\n }\n\n updateOptions(opts: Partial<STTOptions>) {\n this.#opts = { ...this.#opts, ...opts };\n\n const configMsg: Record<string, unknown> = { type: 'UpdateConfiguration' };\n if (opts.prompt !== undefined) configMsg.prompt = opts.prompt;\n if (opts.keytermsPrompt !== undefined) configMsg.keyterms_prompt = opts.keytermsPrompt;\n if (opts.maxTurnSilence !== undefined) configMsg.max_turn_silence = opts.maxTurnSilence;\n if (opts.minTurnSilence !== undefined) configMsg.min_turn_silence = opts.minTurnSilence;\n if (opts.endOfTurnConfidenceThreshold !== undefined) {\n configMsg.end_of_turn_confidence_threshold = opts.endOfTurnConfidenceThreshold;\n }\n if (opts.vadThreshold !== undefined) configMsg.vad_threshold = opts.vadThreshold;\n\n // Only send if any actual fields (besides `type`) were specified.\n if (Object.keys(configMsg).length > 1) {\n this.#pendingConfigMessages.push(configMsg);\n if (!this.#configMessagePending.done) this.#configMessagePending.resolve();\n }\n }\n\n /**\n * Force-finalize the current turn immediately.\n */\n forceEndpoint() {\n this.#pendingConfigMessages.push({ type: 'ForceEndpoint' });\n if (!this.#configMessagePending.done) this.#configMessagePending.resolve();\n }\n\n // Deepgram-style reconnect loop around a single websocket lifetime.\n protected async run() {\n const maxRetry = 32;\n let retries = 0;\n\n while (!this.input.closed && !this.closed) {\n try {\n const ws = await this.#connectWS();\n await this.#runWS(ws);\n retries = 0;\n } catch (e) {\n if (!this.closed && !this.input.closed) {\n if (retries >= maxRetry) {\n throw new Error(`failed to connect to AssemblyAI after ${retries} attempts: ${e}`);\n }\n\n const retryDelaySeconds = Math.min(retries * 5, 10);\n retries++;\n\n this.#logger.warn(\n `failed to connect to AssemblyAI, retrying in ${retryDelaySeconds} seconds: ${e} (${retries}/${maxRetry})`,\n );\n await delay(retryDelaySeconds * 1000);\n } else {\n this.#logger.warn(\n `AssemblyAI disconnected, connection is closed: ${e} (inputClosed: ${this.input.closed}, isClosed: ${this.closed})`,\n );\n }\n }\n }\n\n this.closed = true;\n }\n\n async #connectWS(): Promise<WebSocket> {\n // u3-rt-pro has different silence defaults — if unset, both min and max default to 100ms.\n let minSilence = this.#opts.minTurnSilence;\n let maxSilence = this.#opts.maxTurnSilence;\n if (this.#opts.speechModel === 'u3-rt-pro') {\n if (minSilence === undefined) minSilence = 100;\n if (maxSilence === undefined) maxSilence = minSilence;\n }\n\n // Default language_detection to true for multilingual / u3-rt-pro models, false otherwise.\n const defaultLanguageDetection =\n this.#opts.speechModel.includes('multilingual') || this.#opts.speechModel === 'u3-rt-pro';\n const languageDetection = this.#opts.languageDetection ?? defaultLanguageDetection;\n\n const liveConfig: Record<string, unknown> = {\n sample_rate: this.#opts.sampleRate,\n encoding: this.#opts.encoding,\n speech_model: this.#opts.speechModel,\n format_turns: this.#opts.formatTurns,\n end_of_turn_confidence_threshold: this.#opts.endOfTurnConfidenceThreshold,\n min_turn_silence: minSilence,\n max_turn_silence: maxSilence,\n keyterms_prompt:\n this.#opts.keytermsPrompt !== undefined\n ? JSON.stringify(this.#opts.keytermsPrompt)\n : undefined,\n language_detection: languageDetection,\n prompt: this.#opts.prompt,\n vad_threshold: this.#opts.vadThreshold,\n speaker_labels: this.#opts.speakerLabels,\n max_speakers: this.#opts.maxSpeakers,\n domain: this.#opts.domain,\n };\n\n const url = new URL(`${this.#opts.baseUrl}/v3/ws`);\n // Python serializes booleans as the strings \"true\"/\"false\", so we mirror that.\n for (const [key, value] of Object.entries(liveConfig)) {\n if (value === undefined || value === null) continue;\n if (typeof value === 'boolean') {\n url.searchParams.append(key, value ? 'true' : 'false');\n } else {\n url.searchParams.append(key, String(value));\n }\n }\n\n const ws = new WebSocket(url, {\n headers: {\n Authorization: this.#opts.apiKey!,\n 'Content-Type': 'application/json',\n 'User-Agent': 'AssemblyAI/1.0 (integration=Livekit)',\n },\n });\n\n await new Promise<void>((resolve, reject) => {\n ws.on('open', () => resolve());\n ws.on('error', (error) => reject(error));\n ws.on('close', (code) => reject(new Error(`WebSocket returned ${code}`)));\n });\n\n return ws;\n }\n\n async #runWS(ws: WebSocket) {\n let closing = false;\n const sessionController = new AbortController();\n\n // gets cancelled also when sendTask is complete\n const wsMonitor = Task.from(async (controller) => {\n const closed = new Promise<void>((_, 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 samplesPerBuffer = Math.floor((this.#opts.sampleRate * this.#opts.bufferSizeMs) / 1000);\n const audioStream = new AudioByteStream(this.#opts.sampleRate, 1, samplesPerBuffer);\n\n const abortPromise = waitForAbort(this.abortSignal);\n const sessionAbort = waitForAbort(sessionController.signal);\n\n try {\n while (!this.closed) {\n const result = await Promise.race([this.input.next(), abortPromise, sessionAbort]);\n\n if (result === undefined) return; // aborted\n if (result.done) break;\n\n const data = result.value;\n\n let frames: AudioFrame[];\n if (data === SpeechStream.FLUSH_SENTINEL) {\n frames = audioStream.flush();\n } else if (data.sampleRate === this.#opts.sampleRate && data.channels === 1) {\n // AssemblyAI expects mono PCM. The base SpeechStream only resamples\n // sample rate, so reject any frame that is not already downmixed.\n frames = audioStream.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 (const frame of frames) {\n this.#speechDurationInS += frame.samplesPerChannel / frame.sampleRate;\n ws.send(frame.data.buffer);\n }\n }\n } finally {\n closing = true;\n try {\n ws.send(SpeechStream.CLOSE_MSG);\n } catch {\n // ignore — socket may already be closing\n }\n wsMonitor.cancel();\n }\n };\n\n let messageHandler: ((msg: RawData, isBinary: boolean) => void) | null = null;\n const listenTask = Task.from(async (controller) => {\n const listenMessage = new Promise<void>((resolve, reject) => {\n messageHandler = (msg, isBinary) => {\n if (isBinary) {\n this.#logger.error('unexpected binary message from AssemblyAI');\n return;\n }\n try {\n const json = JSON.parse(msg.toString()) as StreamEventMessage;\n this.#processStreamEvent(json);\n if (this.closed || closing) {\n resolve();\n }\n } catch (err) {\n this.#logger.error(`AssemblyAI: error processing message: ${msg}`);\n reject(err);\n }\n };\n ws.on('message', messageHandler);\n });\n\n await Promise.race([listenMessage, waitForAbort(controller.signal)]);\n });\n\n const configTask = Task.from(async (controller) => {\n // Drain any messages queued while the socket was reconnecting.\n while (this.#pendingConfigMessages.length > 0) {\n const msg = this.#pendingConfigMessages.shift()!;\n ws.send(JSON.stringify(msg));\n }\n\n while (!controller.signal.aborted) {\n await Promise.race([this.#configMessagePending.await, waitForAbort(controller.signal)]);\n if (controller.signal.aborted) return;\n\n this.#configMessagePending = new Future();\n while (this.#pendingConfigMessages.length > 0) {\n const msg = this.#pendingConfigMessages.shift()!;\n ws.send(JSON.stringify(msg));\n }\n }\n });\n\n try {\n await Promise.all([sendTask(), listenTask.result, wsMonitor.result]);\n } finally {\n closing = true;\n sessionController.abort();\n listenTask.cancel();\n configTask.cancel();\n if (messageHandler) ws.off('message', messageHandler);\n try {\n ws.close();\n } catch {\n // ignore\n }\n }\n }\n\n #averageConfidence(words: Array<{ confidence?: number }>): number {\n if (words.length === 0) return 0;\n return words.reduce((sum, w) => sum + (w.confidence ?? 0), 0) / words.length;\n }\n\n #processStreamEvent(data: StreamEventMessage) {\n const messageType = data.type;\n\n if (messageType === 'Begin') {\n this.#sessionId = data.id ?? null;\n this.#expiresAt = data.expires_at ?? null;\n this.#logger.info(\n `AssemblyAI session started id=${this.#sessionId} expires_at=${this.#expiresAt}`,\n );\n return;\n }\n\n if (messageType === 'SpeechStarted') {\n this.queue.put({ type: stt.SpeechEventType.START_OF_SPEECH });\n return;\n }\n\n if (messageType === 'Termination') {\n this.#logger.debug(\n `AssemblyAI session terminated audio_duration=${data.audio_duration_seconds}s session_duration=${data.session_duration_seconds}s`,\n );\n return;\n }\n\n if (messageType !== 'Turn') {\n return;\n }\n\n const words = data.words ?? [];\n const endOfTurn = Boolean(data.end_of_turn);\n const turnIsFormatted = Boolean(data.turn_is_formatted);\n const utterance = data.utterance ?? '';\n const transcript = data.transcript ?? '';\n const language = normalizeLanguage(data.language_code ?? 'en');\n\n // Word timestamps are in milliseconds:\n // https://www.assemblyai.com/docs/api-reference/streaming-api/streaming-api#receive.receiveTurn.words\n const timedWords = words.map((word) =>\n createTimedString({\n text: word.text ?? '',\n startTime: (word.start ?? 0) / 1000 + this.startTimeOffset,\n endTime: (word.end ?? 0) / 1000 + this.startTimeOffset,\n confidence: word.confidence ?? 0,\n startTimeOffset: this.startTimeOffset,\n }),\n );\n\n let startTime = 0;\n let endTime = 0;\n let confidence = 0;\n\n // `words` are cumulative for the turn — emit as an interim transcript.\n if (timedWords.length > 0) {\n const interimText = timedWords.map((w) => w.text).join(' ');\n startTime = timedWords[0]!.startTime ?? 0;\n endTime = timedWords[timedWords.length - 1]!.endTime ?? 0;\n confidence = this.#averageConfidence(timedWords);\n\n this.queue.put({\n type: stt.SpeechEventType.INTERIM_TRANSCRIPT,\n alternatives: [\n {\n language,\n text: interimText,\n startTime,\n endTime,\n confidence,\n words: timedWords,\n },\n ],\n });\n }\n\n // `utterance` is chunk-based (not cumulative) — emit as a preflight transcript\n // covering only the words since the last preflight.\n if (utterance) {\n if (this.#lastPreflightStartTime === 0) {\n this.#lastPreflightStartTime = startTime;\n }\n\n const utteranceWords = timedWords.filter(\n (w) => w.startTime !== undefined && w.startTime >= this.#lastPreflightStartTime,\n );\n const utteranceConfidence = this.#averageConfidence(utteranceWords);\n\n this.queue.put({\n type: stt.SpeechEventType.PREFLIGHT_TRANSCRIPT,\n alternatives: [\n {\n language,\n text: utterance,\n startTime: this.#lastPreflightStartTime,\n endTime,\n confidence: utteranceConfidence,\n words: utteranceWords,\n },\n ],\n });\n this.#lastPreflightStartTime = endTime;\n }\n\n // End-of-turn: emit FINAL_TRANSCRIPT + END_OF_SPEECH.\n // If the user asked for formatted turns, wait for a formatted final.\n const waitingForFormatted = this.#opts.formatTurns === true && !turnIsFormatted;\n if (endOfTurn && !waitingForFormatted) {\n this.queue.put({\n type: stt.SpeechEventType.FINAL_TRANSCRIPT,\n alternatives: [\n {\n language,\n text: transcript,\n startTime,\n endTime,\n confidence,\n words: timedWords,\n },\n ],\n });\n\n this.queue.put({ type: stt.SpeechEventType.END_OF_SPEECH });\n if (this.#speechDurationInS > 0) {\n this.queue.put({\n type: stt.SpeechEventType.RECOGNITION_USAGE,\n // Propagate the AssemblyAI session id as the request id so metrics\n // can be correlated back to a specific connection, mirroring how\n // Deepgram surfaces its `request_id`.\n requestId: this.#sessionId ?? undefined,\n recognitionUsage: {\n audioDuration: this.#speechDurationInS,\n },\n });\n this.#speechDurationInS = 0;\n this.#lastPreflightStartTime = 0;\n }\n }\n }\n}\n"],"mappings":"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","names":["stt"]}
|
|
1
|
+
{"version":3,"sources":["../src/stt.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\n//\nimport {\n type APIConnectOptions,\n type AudioBuffer,\n AudioByteStream,\n Future,\n Task,\n createTimedString,\n delay,\n log,\n normalizeLanguage,\n stt,\n waitForAbort,\n} from '@livekit/agents';\nimport type { AudioFrame } from '@livekit/rtc-node';\nimport type { RawData } from 'ws';\nimport { WebSocket } from 'ws';\nimport type { STTEncoding, STTModels, VoiceFocus } from './models.js';\n\nconst U3_PRO_MODELS = ['u3-rt-pro', 'u3-rt-pro-beta-1', 'universal-3-5-pro'] as const;\n\nfunction isU3ProModel(model: STTModels): boolean {\n return U3_PRO_MODELS.includes(model as (typeof U3_PRO_MODELS)[number]);\n}\n\n// AssemblyAI Universal-Streaming (v3) message envelope. All fields are optional\n// since we narrow on `type` before reading anything else.\ninterface StreamEventMessage {\n type?: 'Begin' | 'SpeechStarted' | 'Turn' | 'Termination' | string;\n // Begin\n id?: string;\n expires_at?: number;\n // Turn\n transcript?: string;\n utterance?: string;\n end_of_turn?: boolean;\n end_of_turn_confidence?: number;\n turn_is_formatted?: boolean;\n language_code?: string;\n speaker_label?: string;\n words?: Array<{\n text?: string;\n start?: number;\n end?: number;\n confidence?: number;\n speaker?: string;\n }>;\n // Termination\n audio_duration_seconds?: number;\n session_duration_seconds?: number;\n}\n\nexport interface STTOptions {\n apiKey?: string;\n sampleRate: number;\n /**\n * How large each chunk of audio is before being sent to AssemblyAI, in\n * milliseconds. Corresponds to Python's `buffer_size_seconds` (seconds there,\n * ms here per this repo's time-unit convention).\n */\n bufferSizeMs: number;\n encoding: STTEncoding;\n speechModel: STTModels;\n languageDetection?: boolean;\n endOfTurnConfidenceThreshold?: number;\n /** Minimum silence (ms) before a confident end-of-turn is finalized. */\n minTurnSilence?: number;\n /** Maximum silence (ms) before end-of-turn is forced regardless of confidence. */\n maxTurnSilence?: number;\n formatTurns?: boolean;\n keytermsPrompt?: string[];\n /** Only supported with the `u3-rt-pro` model. */\n prompt?: string;\n vadThreshold?: number;\n /**\n * Enable speaker diarization. Note: AssemblyAI will return per-word speaker\n * labels, but the JS framework's `stt.SpeechData` type does not yet expose\n * a `speakerId` field (unlike the Python framework), so the labels are not\n * currently surfaced on emitted events. Setting this to `true` still has\n * effect server-side. Once the base `SpeechData` interface gains speaker\n * support, `#processStreamEvent` should forward `data.words[].speaker` too.\n */\n speakerLabels?: boolean;\n maxSpeakers?: number;\n domain?: string;\n /** Isolate the primary voice and suppress background noise. Connect-time only. */\n voiceFocus?: VoiceFocus;\n /** Background audio suppression aggressiveness, from 0.0 to 1.0. Connect-time only. */\n voiceFocusThreshold?: number;\n /**\n * Accuracy/latency preset for u3-rt-pro: `min_latency`, `balanced`, or `max_accuracy`.\n * Explicit silence, partials, or VAD options still take precedence over mode defaults.\n */\n mode?: 'min_latency' | 'balanced' | 'max_accuracy';\n baseUrl: string;\n}\n\nconst defaultSTTOptions: STTOptions = {\n apiKey: process.env.ASSEMBLYAI_API_KEY,\n sampleRate: 16000,\n bufferSizeMs: 50,\n encoding: 'pcm_s16le',\n speechModel: 'universal-3-5-pro',\n baseUrl: 'wss://streaming.assemblyai.com',\n};\n\nexport class STT extends stt.STT {\n #opts: STTOptions;\n #streams = new Set<WeakRef<SpeechStream>>();\n label = 'assemblyai.STT';\n\n get model(): string {\n return this.#opts.speechModel;\n }\n\n get provider(): string {\n return 'AssemblyAI';\n }\n\n constructor(opts: Partial<STTOptions> = {}) {\n super({\n streaming: true,\n interimResults: true,\n alignedTranscript: 'word',\n });\n\n if (opts.speechModel === 'u3-pro') {\n log().warn(\"'u3-pro' is deprecated, use 'u3-rt-pro' instead.\");\n opts.speechModel = 'u3-rt-pro';\n }\n\n const speechModel = opts.speechModel ?? defaultSTTOptions.speechModel;\n if (!isU3ProModel(speechModel)) {\n for (const param of ['prompt', 'voiceFocus', 'voiceFocusThreshold', 'mode'] as const) {\n if (opts[param] !== undefined) {\n throw new Error(\n `The '${param}' parameter is only supported with the ${U3_PRO_MODELS.join(', ')} models.`,\n );\n }\n }\n }\n\n const apiKey = opts.apiKey ?? defaultSTTOptions.apiKey;\n if (!apiKey) {\n throw new Error(\n 'AssemblyAI API key is required. Pass one in via the `apiKey` parameter, or set it as the `ASSEMBLYAI_API_KEY` environment variable',\n );\n }\n\n // Minimize latency; matches LK's end-of-turn detector well.\n const minTurnSilence = opts.minTurnSilence ?? 100;\n\n this.#opts = {\n ...defaultSTTOptions,\n ...opts,\n apiKey,\n minTurnSilence,\n };\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n async _recognize(_: AudioBuffer): Promise<stt.SpeechEvent> {\n throw new Error('Non-streaming recognize is not supported on AssemblyAI STT');\n }\n\n updateOptions(opts: Partial<STTOptions>) {\n this.#opts = { ...this.#opts, ...opts };\n for (const ref of this.#streams) {\n const stream = ref.deref();\n if (stream) {\n stream.updateOptions(opts);\n } else {\n this.#streams.delete(ref);\n }\n }\n }\n\n stream(options?: { connOptions?: APIConnectOptions }): SpeechStream {\n const stream = new SpeechStream(this, this.#opts, options?.connOptions);\n this.#streams.add(new WeakRef(stream));\n return stream;\n }\n}\n\nexport class SpeechStream extends stt.SpeechStream {\n static readonly CLOSE_MSG = JSON.stringify({ type: 'Terminate' });\n\n #opts: STTOptions;\n #logger = log();\n #speechDurationInS = 0;\n #lastPreflightStartTime = 0;\n #pendingConfigMessages: Record<string, unknown>[] = [];\n #configMessagePending = new Future();\n #sessionId: string | null = null;\n #expiresAt: number | null = null;\n label = 'assemblyai.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 }\n\n /**\n * The AssemblyAI session ID. Set when the WebSocket connection is established\n * (before any speech events). Null until the connection completes.\n * Share this with the AssemblyAI team when reporting issues.\n */\n get sessionId(): string | null {\n return this.#sessionId;\n }\n\n /**\n * Unix timestamp when the AssemblyAI session expires. Set alongside\n * {@link sessionId} when the WebSocket connection is established.\n */\n get expiresAt(): number | null {\n return this.#expiresAt;\n }\n\n updateOptions(opts: Partial<STTOptions>) {\n this.#opts = { ...this.#opts, ...opts };\n\n const configMsg: Record<string, unknown> = { type: 'UpdateConfiguration' };\n if (opts.prompt !== undefined) configMsg.prompt = opts.prompt;\n if (opts.keytermsPrompt !== undefined) configMsg.keyterms_prompt = opts.keytermsPrompt;\n if (opts.maxTurnSilence !== undefined) configMsg.max_turn_silence = opts.maxTurnSilence;\n if (opts.minTurnSilence !== undefined) configMsg.min_turn_silence = opts.minTurnSilence;\n if (opts.endOfTurnConfidenceThreshold !== undefined) {\n configMsg.end_of_turn_confidence_threshold = opts.endOfTurnConfidenceThreshold;\n }\n if (opts.vadThreshold !== undefined) configMsg.vad_threshold = opts.vadThreshold;\n\n // Only send if any actual fields (besides `type`) were specified.\n if (Object.keys(configMsg).length > 1) {\n this.#pendingConfigMessages.push(configMsg);\n if (!this.#configMessagePending.done) this.#configMessagePending.resolve();\n }\n }\n\n /**\n * Force-finalize the current turn immediately.\n */\n forceEndpoint() {\n this.#pendingConfigMessages.push({ type: 'ForceEndpoint' });\n if (!this.#configMessagePending.done) this.#configMessagePending.resolve();\n }\n\n // Deepgram-style reconnect loop around a single websocket lifetime.\n protected async run() {\n const maxRetry = 32;\n let retries = 0;\n\n while (!this.input.closed && !this.closed) {\n try {\n const ws = await this.#connectWS();\n await this.#runWS(ws);\n retries = 0;\n } catch (e) {\n if (!this.closed && !this.input.closed) {\n if (retries >= maxRetry) {\n throw new Error(`failed to connect to AssemblyAI after ${retries} attempts: ${e}`);\n }\n\n const retryDelaySeconds = Math.min(retries * 5, 10);\n retries++;\n\n this.#logger.warn(\n `failed to connect to AssemblyAI, retrying in ${retryDelaySeconds} seconds: ${e} (${retries}/${maxRetry})`,\n );\n await delay(retryDelaySeconds * 1000);\n } else {\n this.#logger.warn(\n `AssemblyAI disconnected, connection is closed: ${e} (inputClosed: ${this.input.closed}, isClosed: ${this.closed})`,\n );\n }\n }\n }\n\n this.closed = true;\n }\n\n async #connectWS(): Promise<WebSocket> {\n // u3-rt-pro family models default both min and max silence to 100ms when unset.\n let minSilence = this.#opts.minTurnSilence;\n let maxSilence = this.#opts.maxTurnSilence;\n if (isU3ProModel(this.#opts.speechModel)) {\n if (minSilence === undefined) minSilence = 100;\n if (maxSilence === undefined) maxSilence = minSilence;\n }\n\n // Default language_detection to true for multilingual / u3-rt-pro-family models, false otherwise.\n const defaultLanguageDetection =\n this.#opts.speechModel.includes('multilingual') || isU3ProModel(this.#opts.speechModel);\n const languageDetection = this.#opts.languageDetection ?? defaultLanguageDetection;\n\n const liveConfig: Record<string, unknown> = {\n sample_rate: this.#opts.sampleRate,\n encoding: this.#opts.encoding,\n speech_model: this.#opts.speechModel,\n format_turns: this.#opts.formatTurns,\n end_of_turn_confidence_threshold: this.#opts.endOfTurnConfidenceThreshold,\n min_turn_silence: minSilence,\n max_turn_silence: maxSilence,\n keyterms_prompt:\n this.#opts.keytermsPrompt !== undefined\n ? JSON.stringify(this.#opts.keytermsPrompt)\n : undefined,\n language_detection: languageDetection,\n prompt: this.#opts.prompt,\n vad_threshold: this.#opts.vadThreshold,\n speaker_labels: this.#opts.speakerLabels,\n max_speakers: this.#opts.maxSpeakers,\n domain: this.#opts.domain,\n voice_focus: this.#opts.voiceFocus,\n voice_focus_threshold: this.#opts.voiceFocusThreshold,\n mode: this.#opts.mode,\n };\n\n const url = new URL(`${this.#opts.baseUrl}/v3/ws`);\n // Python serializes booleans as the strings \"true\"/\"false\", so we mirror that.\n for (const [key, value] of Object.entries(liveConfig)) {\n if (value === undefined || value === null) continue;\n if (typeof value === 'boolean') {\n url.searchParams.append(key, value ? 'true' : 'false');\n } else {\n url.searchParams.append(key, String(value));\n }\n }\n\n const ws = new WebSocket(url, {\n headers: {\n Authorization: this.#opts.apiKey!,\n 'Content-Type': 'application/json',\n 'User-Agent': 'AssemblyAI/1.0 (integration=Livekit)',\n },\n });\n\n await new Promise<void>((resolve, reject) => {\n ws.on('open', () => resolve());\n ws.on('error', (error) => reject(error));\n ws.on('close', (code) => reject(new Error(`WebSocket returned ${code}`)));\n });\n\n return ws;\n }\n\n async #runWS(ws: WebSocket) {\n let closing = false;\n const sessionController = new AbortController();\n\n // gets cancelled also when sendTask is complete\n const wsMonitor = Task.from(async (controller) => {\n const closed = new Promise<void>((_, 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 samplesPerBuffer = Math.floor((this.#opts.sampleRate * this.#opts.bufferSizeMs) / 1000);\n const audioStream = new AudioByteStream(this.#opts.sampleRate, 1, samplesPerBuffer);\n\n const abortPromise = waitForAbort(this.abortSignal);\n const sessionAbort = waitForAbort(sessionController.signal);\n\n try {\n while (!this.closed) {\n const result = await Promise.race([this.input.next(), abortPromise, sessionAbort]);\n\n if (result === undefined) return; // aborted\n if (result.done) break;\n\n const data = result.value;\n\n let frames: AudioFrame[];\n if (data === SpeechStream.FLUSH_SENTINEL) {\n frames = audioStream.flush();\n } else if (data.sampleRate === this.#opts.sampleRate && data.channels === 1) {\n // AssemblyAI expects mono PCM. The base SpeechStream only resamples\n // sample rate, so reject any frame that is not already downmixed.\n frames = audioStream.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 (const frame of frames) {\n this.#speechDurationInS += frame.samplesPerChannel / frame.sampleRate;\n ws.send(frame.data.buffer);\n }\n }\n } finally {\n closing = true;\n try {\n ws.send(SpeechStream.CLOSE_MSG);\n } catch {\n // ignore — socket may already be closing\n }\n wsMonitor.cancel();\n }\n };\n\n let messageHandler: ((msg: RawData, isBinary: boolean) => void) | null = null;\n const listenTask = Task.from(async (controller) => {\n const listenMessage = new Promise<void>((resolve, reject) => {\n messageHandler = (msg, isBinary) => {\n if (isBinary) {\n this.#logger.error('unexpected binary message from AssemblyAI');\n return;\n }\n try {\n const json = JSON.parse(msg.toString()) as StreamEventMessage;\n this.#processStreamEvent(json);\n if (this.closed || closing) {\n resolve();\n }\n } catch (err) {\n this.#logger.error(`AssemblyAI: error processing message: ${msg}`);\n reject(err);\n }\n };\n ws.on('message', messageHandler);\n });\n\n await Promise.race([listenMessage, waitForAbort(controller.signal)]);\n });\n\n const configTask = Task.from(async (controller) => {\n // Drain any messages queued while the socket was reconnecting.\n while (this.#pendingConfigMessages.length > 0) {\n const msg = this.#pendingConfigMessages.shift()!;\n ws.send(JSON.stringify(msg));\n }\n\n while (!controller.signal.aborted) {\n await Promise.race([this.#configMessagePending.await, waitForAbort(controller.signal)]);\n if (controller.signal.aborted) return;\n\n this.#configMessagePending = new Future();\n while (this.#pendingConfigMessages.length > 0) {\n const msg = this.#pendingConfigMessages.shift()!;\n ws.send(JSON.stringify(msg));\n }\n }\n });\n\n try {\n await Promise.all([sendTask(), listenTask.result, wsMonitor.result]);\n } finally {\n closing = true;\n sessionController.abort();\n listenTask.cancel();\n configTask.cancel();\n if (messageHandler) ws.off('message', messageHandler);\n try {\n ws.close();\n } catch {\n // ignore\n }\n }\n }\n\n #averageConfidence(words: Array<{ confidence?: number }>): number {\n if (words.length === 0) return 0;\n return words.reduce((sum, w) => sum + (w.confidence ?? 0), 0) / words.length;\n }\n\n #processStreamEvent(data: StreamEventMessage) {\n const messageType = data.type;\n\n if (messageType === 'Begin') {\n this.#sessionId = data.id ?? null;\n this.#expiresAt = data.expires_at ?? null;\n this.#logger.info(\n `AssemblyAI session started id=${this.#sessionId} expires_at=${this.#expiresAt}`,\n );\n return;\n }\n\n if (messageType === 'SpeechStarted') {\n this.queue.put({ type: stt.SpeechEventType.START_OF_SPEECH });\n return;\n }\n\n if (messageType === 'Termination') {\n this.#logger.debug(\n `AssemblyAI session terminated audio_duration=${data.audio_duration_seconds}s session_duration=${data.session_duration_seconds}s`,\n );\n return;\n }\n\n if (messageType !== 'Turn') {\n return;\n }\n\n const words = data.words ?? [];\n const endOfTurn = Boolean(data.end_of_turn);\n const turnIsFormatted = Boolean(data.turn_is_formatted);\n const utterance = data.utterance ?? '';\n const transcript = data.transcript ?? '';\n const language = normalizeLanguage(data.language_code ?? 'en');\n\n // Word timestamps are in milliseconds:\n // https://www.assemblyai.com/docs/api-reference/streaming-api/streaming-api#receive.receiveTurn.words\n const timedWords = words.map((word) =>\n createTimedString({\n text: word.text ?? '',\n startTime: (word.start ?? 0) / 1000 + this.startTimeOffset,\n endTime: (word.end ?? 0) / 1000 + this.startTimeOffset,\n confidence: word.confidence ?? 0,\n startTimeOffset: this.startTimeOffset,\n }),\n );\n\n let startTime = 0;\n let endTime = 0;\n let confidence = 0;\n\n // `words` are cumulative for the turn — emit as an interim transcript.\n if (timedWords.length > 0) {\n const interimText = timedWords.map((w) => w.text).join(' ');\n startTime = timedWords[0]!.startTime ?? 0;\n endTime = timedWords[timedWords.length - 1]!.endTime ?? 0;\n confidence = this.#averageConfidence(timedWords);\n\n this.queue.put({\n type: stt.SpeechEventType.INTERIM_TRANSCRIPT,\n alternatives: [\n {\n language,\n text: interimText,\n startTime,\n endTime,\n confidence,\n words: timedWords,\n },\n ],\n });\n }\n\n // `utterance` is chunk-based (not cumulative) — emit as a preflight transcript\n // covering only the words since the last preflight.\n if (utterance) {\n if (this.#lastPreflightStartTime === 0) {\n this.#lastPreflightStartTime = startTime;\n }\n\n const utteranceWords = timedWords.filter(\n (w) => w.startTime !== undefined && w.startTime >= this.#lastPreflightStartTime,\n );\n const utteranceConfidence = this.#averageConfidence(utteranceWords);\n\n this.queue.put({\n type: stt.SpeechEventType.PREFLIGHT_TRANSCRIPT,\n alternatives: [\n {\n language,\n text: utterance,\n startTime: this.#lastPreflightStartTime,\n endTime,\n confidence: utteranceConfidence,\n words: utteranceWords,\n },\n ],\n });\n this.#lastPreflightStartTime = endTime;\n }\n\n // End-of-turn: emit FINAL_TRANSCRIPT + END_OF_SPEECH.\n // If the user asked for formatted turns, wait for a formatted final.\n const waitingForFormatted = this.#opts.formatTurns === true && !turnIsFormatted;\n if (endOfTurn && !waitingForFormatted) {\n this.queue.put({\n type: stt.SpeechEventType.FINAL_TRANSCRIPT,\n alternatives: [\n {\n language,\n text: transcript,\n startTime,\n endTime,\n confidence,\n words: timedWords,\n },\n ],\n });\n\n this.queue.put({ type: stt.SpeechEventType.END_OF_SPEECH });\n if (this.#speechDurationInS > 0) {\n this.queue.put({\n type: stt.SpeechEventType.RECOGNITION_USAGE,\n // Propagate the AssemblyAI session id as the request id so metrics\n // can be correlated back to a specific connection, mirroring how\n // Deepgram surfaces its `request_id`.\n requestId: this.#sessionId ?? undefined,\n recognitionUsage: {\n audioDuration: this.#speechDurationInS,\n },\n });\n this.#speechDurationInS = 0;\n this.#lastPreflightStartTime = 0;\n }\n }\n }\n}\n"],"mappings":"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","names":["stt"]}
|
package/dist/stt.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type APIConnectOptions, type AudioBuffer, stt } from '@livekit/agents';
|
|
2
|
-
import type { STTEncoding, STTModels } from './models.js';
|
|
2
|
+
import type { STTEncoding, STTModels, VoiceFocus } from './models.js';
|
|
3
3
|
export interface STTOptions {
|
|
4
4
|
apiKey?: string;
|
|
5
5
|
sampleRate: number;
|
|
@@ -33,6 +33,15 @@ export interface STTOptions {
|
|
|
33
33
|
speakerLabels?: boolean;
|
|
34
34
|
maxSpeakers?: number;
|
|
35
35
|
domain?: string;
|
|
36
|
+
/** Isolate the primary voice and suppress background noise. Connect-time only. */
|
|
37
|
+
voiceFocus?: VoiceFocus;
|
|
38
|
+
/** Background audio suppression aggressiveness, from 0.0 to 1.0. Connect-time only. */
|
|
39
|
+
voiceFocusThreshold?: number;
|
|
40
|
+
/**
|
|
41
|
+
* Accuracy/latency preset for u3-rt-pro: `min_latency`, `balanced`, or `max_accuracy`.
|
|
42
|
+
* Explicit silence, partials, or VAD options still take precedence over mode defaults.
|
|
43
|
+
*/
|
|
44
|
+
mode?: 'min_latency' | 'balanced' | 'max_accuracy';
|
|
36
45
|
baseUrl: string;
|
|
37
46
|
}
|
|
38
47
|
export declare class STT extends stt.STT {
|
package/dist/stt.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type APIConnectOptions, type AudioBuffer, stt } from '@livekit/agents';
|
|
2
|
-
import type { STTEncoding, STTModels } from './models.js';
|
|
2
|
+
import type { STTEncoding, STTModels, VoiceFocus } from './models.js';
|
|
3
3
|
export interface STTOptions {
|
|
4
4
|
apiKey?: string;
|
|
5
5
|
sampleRate: number;
|
|
@@ -33,6 +33,15 @@ export interface STTOptions {
|
|
|
33
33
|
speakerLabels?: boolean;
|
|
34
34
|
maxSpeakers?: number;
|
|
35
35
|
domain?: string;
|
|
36
|
+
/** Isolate the primary voice and suppress background noise. Connect-time only. */
|
|
37
|
+
voiceFocus?: VoiceFocus;
|
|
38
|
+
/** Background audio suppression aggressiveness, from 0.0 to 1.0. Connect-time only. */
|
|
39
|
+
voiceFocusThreshold?: number;
|
|
40
|
+
/**
|
|
41
|
+
* Accuracy/latency preset for u3-rt-pro: `min_latency`, `balanced`, or `max_accuracy`.
|
|
42
|
+
* Explicit silence, partials, or VAD options still take precedence over mode defaults.
|
|
43
|
+
*/
|
|
44
|
+
mode?: 'min_latency' | 'balanced' | 'max_accuracy';
|
|
36
45
|
baseUrl: string;
|
|
37
46
|
}
|
|
38
47
|
export declare class STT extends stt.STT {
|
package/dist/stt.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stt.d.ts","sourceRoot":"","sources":["../src/stt.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAQhB,GAAG,EAEJ,MAAM,iBAAiB,CAAC;AAIzB,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"stt.d.ts","sourceRoot":"","sources":["../src/stt.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAQhB,GAAG,EAEJ,MAAM,iBAAiB,CAAC;AAIzB,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAmCtE,MAAM,WAAW,UAAU;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,WAAW,CAAC;IACtB,WAAW,EAAE,SAAS,CAAC;IACvB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,4BAA4B,CAAC,EAAE,MAAM,CAAC;IACtC,wEAAwE;IACxE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,kFAAkF;IAClF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,iDAAiD;IACjD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kFAAkF;IAClF,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,uFAAuF;IACvF,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;OAGG;IACH,IAAI,CAAC,EAAE,aAAa,GAAG,UAAU,GAAG,cAAc,CAAC;IACnD,OAAO,EAAE,MAAM,CAAC;CACjB;AAWD,qBAAa,GAAI,SAAQ,GAAG,CAAC,GAAG;;IAG9B,KAAK,SAAoB;IAEzB,IAAI,KAAK,IAAI,MAAM,CAElB;IAED,IAAI,QAAQ,IAAI,MAAM,CAErB;gBAEW,IAAI,GAAE,OAAO,CAAC,UAAU,CAAM;IA0CpC,UAAU,CAAC,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC;IAI1D,aAAa,CAAC,IAAI,EAAE,OAAO,CAAC,UAAU,CAAC;IAYvC,MAAM,CAAC,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,iBAAiB,CAAA;KAAE,GAAG,YAAY;CAKpE;AAED,qBAAa,YAAa,SAAQ,GAAG,CAAC,YAAY;;IAChD,MAAM,CAAC,QAAQ,CAAC,SAAS,SAAyC;IAUlE,KAAK,SAA6B;gBAEtB,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,WAAW,CAAC,EAAE,iBAAiB;IAMvE;;;;OAIG;IACH,IAAI,SAAS,IAAI,MAAM,GAAG,IAAI,CAE7B;IAED;;;OAGG;IACH,IAAI,SAAS,IAAI,MAAM,GAAG,IAAI,CAE7B;IAED,aAAa,CAAC,IAAI,EAAE,OAAO,CAAC,UAAU,CAAC;IAoBvC;;OAEG;IACH,aAAa;cAMG,GAAG;CAwWpB"}
|
package/dist/stt.js
CHANGED
|
@@ -10,12 +10,16 @@ import {
|
|
|
10
10
|
waitForAbort
|
|
11
11
|
} from "@livekit/agents";
|
|
12
12
|
import { WebSocket } from "ws";
|
|
13
|
+
const U3_PRO_MODELS = ["u3-rt-pro", "u3-rt-pro-beta-1", "universal-3-5-pro"];
|
|
14
|
+
function isU3ProModel(model) {
|
|
15
|
+
return U3_PRO_MODELS.includes(model);
|
|
16
|
+
}
|
|
13
17
|
const defaultSTTOptions = {
|
|
14
18
|
apiKey: process.env.ASSEMBLYAI_API_KEY,
|
|
15
19
|
sampleRate: 16e3,
|
|
16
20
|
bufferSizeMs: 50,
|
|
17
21
|
encoding: "pcm_s16le",
|
|
18
|
-
speechModel: "universal-
|
|
22
|
+
speechModel: "universal-3-5-pro",
|
|
19
23
|
baseUrl: "wss://streaming.assemblyai.com"
|
|
20
24
|
};
|
|
21
25
|
class STT extends stt.STT {
|
|
@@ -38,8 +42,15 @@ class STT extends stt.STT {
|
|
|
38
42
|
log().warn("'u3-pro' is deprecated, use 'u3-rt-pro' instead.");
|
|
39
43
|
opts.speechModel = "u3-rt-pro";
|
|
40
44
|
}
|
|
41
|
-
|
|
42
|
-
|
|
45
|
+
const speechModel = opts.speechModel ?? defaultSTTOptions.speechModel;
|
|
46
|
+
if (!isU3ProModel(speechModel)) {
|
|
47
|
+
for (const param of ["prompt", "voiceFocus", "voiceFocusThreshold", "mode"]) {
|
|
48
|
+
if (opts[param] !== void 0) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
`The '${param}' parameter is only supported with the ${U3_PRO_MODELS.join(", ")} models.`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
43
54
|
}
|
|
44
55
|
const apiKey = opts.apiKey ?? defaultSTTOptions.apiKey;
|
|
45
56
|
if (!apiKey) {
|
|
@@ -162,11 +173,11 @@ class SpeechStream extends stt.SpeechStream {
|
|
|
162
173
|
async #connectWS() {
|
|
163
174
|
let minSilence = this.#opts.minTurnSilence;
|
|
164
175
|
let maxSilence = this.#opts.maxTurnSilence;
|
|
165
|
-
if (this.#opts.speechModel
|
|
176
|
+
if (isU3ProModel(this.#opts.speechModel)) {
|
|
166
177
|
if (minSilence === void 0) minSilence = 100;
|
|
167
178
|
if (maxSilence === void 0) maxSilence = minSilence;
|
|
168
179
|
}
|
|
169
|
-
const defaultLanguageDetection = this.#opts.speechModel.includes("multilingual") || this.#opts.speechModel
|
|
180
|
+
const defaultLanguageDetection = this.#opts.speechModel.includes("multilingual") || isU3ProModel(this.#opts.speechModel);
|
|
170
181
|
const languageDetection = this.#opts.languageDetection ?? defaultLanguageDetection;
|
|
171
182
|
const liveConfig = {
|
|
172
183
|
sample_rate: this.#opts.sampleRate,
|
|
@@ -182,7 +193,10 @@ class SpeechStream extends stt.SpeechStream {
|
|
|
182
193
|
vad_threshold: this.#opts.vadThreshold,
|
|
183
194
|
speaker_labels: this.#opts.speakerLabels,
|
|
184
195
|
max_speakers: this.#opts.maxSpeakers,
|
|
185
|
-
domain: this.#opts.domain
|
|
196
|
+
domain: this.#opts.domain,
|
|
197
|
+
voice_focus: this.#opts.voiceFocus,
|
|
198
|
+
voice_focus_threshold: this.#opts.voiceFocusThreshold,
|
|
199
|
+
mode: this.#opts.mode
|
|
186
200
|
};
|
|
187
201
|
const url = new URL(`${this.#opts.baseUrl}/v3/ws`);
|
|
188
202
|
for (const [key, value] of Object.entries(liveConfig)) {
|
package/dist/stt.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/stt.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\n//\nimport {\n type APIConnectOptions,\n type AudioBuffer,\n AudioByteStream,\n Future,\n Task,\n createTimedString,\n delay,\n log,\n normalizeLanguage,\n stt,\n waitForAbort,\n} from '@livekit/agents';\nimport type { AudioFrame } from '@livekit/rtc-node';\nimport type { RawData } from 'ws';\nimport { WebSocket } from 'ws';\nimport type { STTEncoding, STTModels } from './models.js';\n\n// AssemblyAI Universal-Streaming (v3) message envelope. All fields are optional\n// since we narrow on `type` before reading anything else.\ninterface StreamEventMessage {\n type?: 'Begin' | 'SpeechStarted' | 'Turn' | 'Termination' | string;\n // Begin\n id?: string;\n expires_at?: number;\n // Turn\n transcript?: string;\n utterance?: string;\n end_of_turn?: boolean;\n end_of_turn_confidence?: number;\n turn_is_formatted?: boolean;\n language_code?: string;\n speaker_label?: string;\n words?: Array<{\n text?: string;\n start?: number;\n end?: number;\n confidence?: number;\n speaker?: string;\n }>;\n // Termination\n audio_duration_seconds?: number;\n session_duration_seconds?: number;\n}\n\nexport interface STTOptions {\n apiKey?: string;\n sampleRate: number;\n /**\n * How large each chunk of audio is before being sent to AssemblyAI, in\n * milliseconds. Corresponds to Python's `buffer_size_seconds` (seconds there,\n * ms here per this repo's time-unit convention).\n */\n bufferSizeMs: number;\n encoding: STTEncoding;\n speechModel: STTModels;\n languageDetection?: boolean;\n endOfTurnConfidenceThreshold?: number;\n /** Minimum silence (ms) before a confident end-of-turn is finalized. */\n minTurnSilence?: number;\n /** Maximum silence (ms) before end-of-turn is forced regardless of confidence. */\n maxTurnSilence?: number;\n formatTurns?: boolean;\n keytermsPrompt?: string[];\n /** Only supported with the `u3-rt-pro` model. */\n prompt?: string;\n vadThreshold?: number;\n /**\n * Enable speaker diarization. Note: AssemblyAI will return per-word speaker\n * labels, but the JS framework's `stt.SpeechData` type does not yet expose\n * a `speakerId` field (unlike the Python framework), so the labels are not\n * currently surfaced on emitted events. Setting this to `true` still has\n * effect server-side. Once the base `SpeechData` interface gains speaker\n * support, `#processStreamEvent` should forward `data.words[].speaker` too.\n */\n speakerLabels?: boolean;\n maxSpeakers?: number;\n domain?: string;\n baseUrl: string;\n}\n\nconst defaultSTTOptions: STTOptions = {\n apiKey: process.env.ASSEMBLYAI_API_KEY,\n sampleRate: 16000,\n bufferSizeMs: 50,\n encoding: 'pcm_s16le',\n speechModel: 'universal-streaming-english',\n baseUrl: 'wss://streaming.assemblyai.com',\n};\n\nexport class STT extends stt.STT {\n #opts: STTOptions;\n #streams = new Set<WeakRef<SpeechStream>>();\n label = 'assemblyai.STT';\n\n get model(): string {\n return this.#opts.speechModel;\n }\n\n get provider(): string {\n return 'AssemblyAI';\n }\n\n constructor(opts: Partial<STTOptions> = {}) {\n super({\n streaming: true,\n interimResults: true,\n alignedTranscript: 'word',\n });\n\n if (opts.speechModel === 'u3-pro') {\n log().warn(\"'u3-pro' is deprecated, use 'u3-rt-pro' instead.\");\n opts.speechModel = 'u3-rt-pro';\n }\n\n if (opts.prompt !== undefined && opts.speechModel !== 'u3-rt-pro') {\n throw new Error(\"The 'prompt' parameter is only supported with the 'u3-rt-pro' model.\");\n }\n\n const apiKey = opts.apiKey ?? defaultSTTOptions.apiKey;\n if (!apiKey) {\n throw new Error(\n 'AssemblyAI API key is required. Pass one in via the `apiKey` parameter, or set it as the `ASSEMBLYAI_API_KEY` environment variable',\n );\n }\n\n // Minimize latency; matches LK's end-of-turn detector well.\n const minTurnSilence = opts.minTurnSilence ?? 100;\n\n this.#opts = {\n ...defaultSTTOptions,\n ...opts,\n apiKey,\n minTurnSilence,\n };\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n async _recognize(_: AudioBuffer): Promise<stt.SpeechEvent> {\n throw new Error('Non-streaming recognize is not supported on AssemblyAI STT');\n }\n\n updateOptions(opts: Partial<STTOptions>) {\n this.#opts = { ...this.#opts, ...opts };\n for (const ref of this.#streams) {\n const stream = ref.deref();\n if (stream) {\n stream.updateOptions(opts);\n } else {\n this.#streams.delete(ref);\n }\n }\n }\n\n stream(options?: { connOptions?: APIConnectOptions }): SpeechStream {\n const stream = new SpeechStream(this, this.#opts, options?.connOptions);\n this.#streams.add(new WeakRef(stream));\n return stream;\n }\n}\n\nexport class SpeechStream extends stt.SpeechStream {\n static readonly CLOSE_MSG = JSON.stringify({ type: 'Terminate' });\n\n #opts: STTOptions;\n #logger = log();\n #speechDurationInS = 0;\n #lastPreflightStartTime = 0;\n #pendingConfigMessages: Record<string, unknown>[] = [];\n #configMessagePending = new Future();\n #sessionId: string | null = null;\n #expiresAt: number | null = null;\n label = 'assemblyai.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 }\n\n /**\n * The AssemblyAI session ID. Set when the WebSocket connection is established\n * (before any speech events). Null until the connection completes.\n * Share this with the AssemblyAI team when reporting issues.\n */\n get sessionId(): string | null {\n return this.#sessionId;\n }\n\n /**\n * Unix timestamp when the AssemblyAI session expires. Set alongside\n * {@link sessionId} when the WebSocket connection is established.\n */\n get expiresAt(): number | null {\n return this.#expiresAt;\n }\n\n updateOptions(opts: Partial<STTOptions>) {\n this.#opts = { ...this.#opts, ...opts };\n\n const configMsg: Record<string, unknown> = { type: 'UpdateConfiguration' };\n if (opts.prompt !== undefined) configMsg.prompt = opts.prompt;\n if (opts.keytermsPrompt !== undefined) configMsg.keyterms_prompt = opts.keytermsPrompt;\n if (opts.maxTurnSilence !== undefined) configMsg.max_turn_silence = opts.maxTurnSilence;\n if (opts.minTurnSilence !== undefined) configMsg.min_turn_silence = opts.minTurnSilence;\n if (opts.endOfTurnConfidenceThreshold !== undefined) {\n configMsg.end_of_turn_confidence_threshold = opts.endOfTurnConfidenceThreshold;\n }\n if (opts.vadThreshold !== undefined) configMsg.vad_threshold = opts.vadThreshold;\n\n // Only send if any actual fields (besides `type`) were specified.\n if (Object.keys(configMsg).length > 1) {\n this.#pendingConfigMessages.push(configMsg);\n if (!this.#configMessagePending.done) this.#configMessagePending.resolve();\n }\n }\n\n /**\n * Force-finalize the current turn immediately.\n */\n forceEndpoint() {\n this.#pendingConfigMessages.push({ type: 'ForceEndpoint' });\n if (!this.#configMessagePending.done) this.#configMessagePending.resolve();\n }\n\n // Deepgram-style reconnect loop around a single websocket lifetime.\n protected async run() {\n const maxRetry = 32;\n let retries = 0;\n\n while (!this.input.closed && !this.closed) {\n try {\n const ws = await this.#connectWS();\n await this.#runWS(ws);\n retries = 0;\n } catch (e) {\n if (!this.closed && !this.input.closed) {\n if (retries >= maxRetry) {\n throw new Error(`failed to connect to AssemblyAI after ${retries} attempts: ${e}`);\n }\n\n const retryDelaySeconds = Math.min(retries * 5, 10);\n retries++;\n\n this.#logger.warn(\n `failed to connect to AssemblyAI, retrying in ${retryDelaySeconds} seconds: ${e} (${retries}/${maxRetry})`,\n );\n await delay(retryDelaySeconds * 1000);\n } else {\n this.#logger.warn(\n `AssemblyAI disconnected, connection is closed: ${e} (inputClosed: ${this.input.closed}, isClosed: ${this.closed})`,\n );\n }\n }\n }\n\n this.closed = true;\n }\n\n async #connectWS(): Promise<WebSocket> {\n // u3-rt-pro has different silence defaults — if unset, both min and max default to 100ms.\n let minSilence = this.#opts.minTurnSilence;\n let maxSilence = this.#opts.maxTurnSilence;\n if (this.#opts.speechModel === 'u3-rt-pro') {\n if (minSilence === undefined) minSilence = 100;\n if (maxSilence === undefined) maxSilence = minSilence;\n }\n\n // Default language_detection to true for multilingual / u3-rt-pro models, false otherwise.\n const defaultLanguageDetection =\n this.#opts.speechModel.includes('multilingual') || this.#opts.speechModel === 'u3-rt-pro';\n const languageDetection = this.#opts.languageDetection ?? defaultLanguageDetection;\n\n const liveConfig: Record<string, unknown> = {\n sample_rate: this.#opts.sampleRate,\n encoding: this.#opts.encoding,\n speech_model: this.#opts.speechModel,\n format_turns: this.#opts.formatTurns,\n end_of_turn_confidence_threshold: this.#opts.endOfTurnConfidenceThreshold,\n min_turn_silence: minSilence,\n max_turn_silence: maxSilence,\n keyterms_prompt:\n this.#opts.keytermsPrompt !== undefined\n ? JSON.stringify(this.#opts.keytermsPrompt)\n : undefined,\n language_detection: languageDetection,\n prompt: this.#opts.prompt,\n vad_threshold: this.#opts.vadThreshold,\n speaker_labels: this.#opts.speakerLabels,\n max_speakers: this.#opts.maxSpeakers,\n domain: this.#opts.domain,\n };\n\n const url = new URL(`${this.#opts.baseUrl}/v3/ws`);\n // Python serializes booleans as the strings \"true\"/\"false\", so we mirror that.\n for (const [key, value] of Object.entries(liveConfig)) {\n if (value === undefined || value === null) continue;\n if (typeof value === 'boolean') {\n url.searchParams.append(key, value ? 'true' : 'false');\n } else {\n url.searchParams.append(key, String(value));\n }\n }\n\n const ws = new WebSocket(url, {\n headers: {\n Authorization: this.#opts.apiKey!,\n 'Content-Type': 'application/json',\n 'User-Agent': 'AssemblyAI/1.0 (integration=Livekit)',\n },\n });\n\n await new Promise<void>((resolve, reject) => {\n ws.on('open', () => resolve());\n ws.on('error', (error) => reject(error));\n ws.on('close', (code) => reject(new Error(`WebSocket returned ${code}`)));\n });\n\n return ws;\n }\n\n async #runWS(ws: WebSocket) {\n let closing = false;\n const sessionController = new AbortController();\n\n // gets cancelled also when sendTask is complete\n const wsMonitor = Task.from(async (controller) => {\n const closed = new Promise<void>((_, 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 samplesPerBuffer = Math.floor((this.#opts.sampleRate * this.#opts.bufferSizeMs) / 1000);\n const audioStream = new AudioByteStream(this.#opts.sampleRate, 1, samplesPerBuffer);\n\n const abortPromise = waitForAbort(this.abortSignal);\n const sessionAbort = waitForAbort(sessionController.signal);\n\n try {\n while (!this.closed) {\n const result = await Promise.race([this.input.next(), abortPromise, sessionAbort]);\n\n if (result === undefined) return; // aborted\n if (result.done) break;\n\n const data = result.value;\n\n let frames: AudioFrame[];\n if (data === SpeechStream.FLUSH_SENTINEL) {\n frames = audioStream.flush();\n } else if (data.sampleRate === this.#opts.sampleRate && data.channels === 1) {\n // AssemblyAI expects mono PCM. The base SpeechStream only resamples\n // sample rate, so reject any frame that is not already downmixed.\n frames = audioStream.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 (const frame of frames) {\n this.#speechDurationInS += frame.samplesPerChannel / frame.sampleRate;\n ws.send(frame.data.buffer);\n }\n }\n } finally {\n closing = true;\n try {\n ws.send(SpeechStream.CLOSE_MSG);\n } catch {\n // ignore — socket may already be closing\n }\n wsMonitor.cancel();\n }\n };\n\n let messageHandler: ((msg: RawData, isBinary: boolean) => void) | null = null;\n const listenTask = Task.from(async (controller) => {\n const listenMessage = new Promise<void>((resolve, reject) => {\n messageHandler = (msg, isBinary) => {\n if (isBinary) {\n this.#logger.error('unexpected binary message from AssemblyAI');\n return;\n }\n try {\n const json = JSON.parse(msg.toString()) as StreamEventMessage;\n this.#processStreamEvent(json);\n if (this.closed || closing) {\n resolve();\n }\n } catch (err) {\n this.#logger.error(`AssemblyAI: error processing message: ${msg}`);\n reject(err);\n }\n };\n ws.on('message', messageHandler);\n });\n\n await Promise.race([listenMessage, waitForAbort(controller.signal)]);\n });\n\n const configTask = Task.from(async (controller) => {\n // Drain any messages queued while the socket was reconnecting.\n while (this.#pendingConfigMessages.length > 0) {\n const msg = this.#pendingConfigMessages.shift()!;\n ws.send(JSON.stringify(msg));\n }\n\n while (!controller.signal.aborted) {\n await Promise.race([this.#configMessagePending.await, waitForAbort(controller.signal)]);\n if (controller.signal.aborted) return;\n\n this.#configMessagePending = new Future();\n while (this.#pendingConfigMessages.length > 0) {\n const msg = this.#pendingConfigMessages.shift()!;\n ws.send(JSON.stringify(msg));\n }\n }\n });\n\n try {\n await Promise.all([sendTask(), listenTask.result, wsMonitor.result]);\n } finally {\n closing = true;\n sessionController.abort();\n listenTask.cancel();\n configTask.cancel();\n if (messageHandler) ws.off('message', messageHandler);\n try {\n ws.close();\n } catch {\n // ignore\n }\n }\n }\n\n #averageConfidence(words: Array<{ confidence?: number }>): number {\n if (words.length === 0) return 0;\n return words.reduce((sum, w) => sum + (w.confidence ?? 0), 0) / words.length;\n }\n\n #processStreamEvent(data: StreamEventMessage) {\n const messageType = data.type;\n\n if (messageType === 'Begin') {\n this.#sessionId = data.id ?? null;\n this.#expiresAt = data.expires_at ?? null;\n this.#logger.info(\n `AssemblyAI session started id=${this.#sessionId} expires_at=${this.#expiresAt}`,\n );\n return;\n }\n\n if (messageType === 'SpeechStarted') {\n this.queue.put({ type: stt.SpeechEventType.START_OF_SPEECH });\n return;\n }\n\n if (messageType === 'Termination') {\n this.#logger.debug(\n `AssemblyAI session terminated audio_duration=${data.audio_duration_seconds}s session_duration=${data.session_duration_seconds}s`,\n );\n return;\n }\n\n if (messageType !== 'Turn') {\n return;\n }\n\n const words = data.words ?? [];\n const endOfTurn = Boolean(data.end_of_turn);\n const turnIsFormatted = Boolean(data.turn_is_formatted);\n const utterance = data.utterance ?? '';\n const transcript = data.transcript ?? '';\n const language = normalizeLanguage(data.language_code ?? 'en');\n\n // Word timestamps are in milliseconds:\n // https://www.assemblyai.com/docs/api-reference/streaming-api/streaming-api#receive.receiveTurn.words\n const timedWords = words.map((word) =>\n createTimedString({\n text: word.text ?? '',\n startTime: (word.start ?? 0) / 1000 + this.startTimeOffset,\n endTime: (word.end ?? 0) / 1000 + this.startTimeOffset,\n confidence: word.confidence ?? 0,\n startTimeOffset: this.startTimeOffset,\n }),\n );\n\n let startTime = 0;\n let endTime = 0;\n let confidence = 0;\n\n // `words` are cumulative for the turn — emit as an interim transcript.\n if (timedWords.length > 0) {\n const interimText = timedWords.map((w) => w.text).join(' ');\n startTime = timedWords[0]!.startTime ?? 0;\n endTime = timedWords[timedWords.length - 1]!.endTime ?? 0;\n confidence = this.#averageConfidence(timedWords);\n\n this.queue.put({\n type: stt.SpeechEventType.INTERIM_TRANSCRIPT,\n alternatives: [\n {\n language,\n text: interimText,\n startTime,\n endTime,\n confidence,\n words: timedWords,\n },\n ],\n });\n }\n\n // `utterance` is chunk-based (not cumulative) — emit as a preflight transcript\n // covering only the words since the last preflight.\n if (utterance) {\n if (this.#lastPreflightStartTime === 0) {\n this.#lastPreflightStartTime = startTime;\n }\n\n const utteranceWords = timedWords.filter(\n (w) => w.startTime !== undefined && w.startTime >= this.#lastPreflightStartTime,\n );\n const utteranceConfidence = this.#averageConfidence(utteranceWords);\n\n this.queue.put({\n type: stt.SpeechEventType.PREFLIGHT_TRANSCRIPT,\n alternatives: [\n {\n language,\n text: utterance,\n startTime: this.#lastPreflightStartTime,\n endTime,\n confidence: utteranceConfidence,\n words: utteranceWords,\n },\n ],\n });\n this.#lastPreflightStartTime = endTime;\n }\n\n // End-of-turn: emit FINAL_TRANSCRIPT + END_OF_SPEECH.\n // If the user asked for formatted turns, wait for a formatted final.\n const waitingForFormatted = this.#opts.formatTurns === true && !turnIsFormatted;\n if (endOfTurn && !waitingForFormatted) {\n this.queue.put({\n type: stt.SpeechEventType.FINAL_TRANSCRIPT,\n alternatives: [\n {\n language,\n text: transcript,\n startTime,\n endTime,\n confidence,\n words: timedWords,\n },\n ],\n });\n\n this.queue.put({ type: stt.SpeechEventType.END_OF_SPEECH });\n if (this.#speechDurationInS > 0) {\n this.queue.put({\n type: stt.SpeechEventType.RECOGNITION_USAGE,\n // Propagate the AssemblyAI session id as the request id so metrics\n // can be correlated back to a specific connection, mirroring how\n // Deepgram surfaces its `request_id`.\n requestId: this.#sessionId ?? undefined,\n recognitionUsage: {\n audioDuration: this.#speechDurationInS,\n },\n });\n this.#speechDurationInS = 0;\n this.#lastPreflightStartTime = 0;\n }\n }\n }\n}\n"],"mappings":"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","names":["stt"]}
|
|
1
|
+
{"version":3,"sources":["../src/stt.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\n//\nimport {\n type APIConnectOptions,\n type AudioBuffer,\n AudioByteStream,\n Future,\n Task,\n createTimedString,\n delay,\n log,\n normalizeLanguage,\n stt,\n waitForAbort,\n} from '@livekit/agents';\nimport type { AudioFrame } from '@livekit/rtc-node';\nimport type { RawData } from 'ws';\nimport { WebSocket } from 'ws';\nimport type { STTEncoding, STTModels, VoiceFocus } from './models.js';\n\nconst U3_PRO_MODELS = ['u3-rt-pro', 'u3-rt-pro-beta-1', 'universal-3-5-pro'] as const;\n\nfunction isU3ProModel(model: STTModels): boolean {\n return U3_PRO_MODELS.includes(model as (typeof U3_PRO_MODELS)[number]);\n}\n\n// AssemblyAI Universal-Streaming (v3) message envelope. All fields are optional\n// since we narrow on `type` before reading anything else.\ninterface StreamEventMessage {\n type?: 'Begin' | 'SpeechStarted' | 'Turn' | 'Termination' | string;\n // Begin\n id?: string;\n expires_at?: number;\n // Turn\n transcript?: string;\n utterance?: string;\n end_of_turn?: boolean;\n end_of_turn_confidence?: number;\n turn_is_formatted?: boolean;\n language_code?: string;\n speaker_label?: string;\n words?: Array<{\n text?: string;\n start?: number;\n end?: number;\n confidence?: number;\n speaker?: string;\n }>;\n // Termination\n audio_duration_seconds?: number;\n session_duration_seconds?: number;\n}\n\nexport interface STTOptions {\n apiKey?: string;\n sampleRate: number;\n /**\n * How large each chunk of audio is before being sent to AssemblyAI, in\n * milliseconds. Corresponds to Python's `buffer_size_seconds` (seconds there,\n * ms here per this repo's time-unit convention).\n */\n bufferSizeMs: number;\n encoding: STTEncoding;\n speechModel: STTModels;\n languageDetection?: boolean;\n endOfTurnConfidenceThreshold?: number;\n /** Minimum silence (ms) before a confident end-of-turn is finalized. */\n minTurnSilence?: number;\n /** Maximum silence (ms) before end-of-turn is forced regardless of confidence. */\n maxTurnSilence?: number;\n formatTurns?: boolean;\n keytermsPrompt?: string[];\n /** Only supported with the `u3-rt-pro` model. */\n prompt?: string;\n vadThreshold?: number;\n /**\n * Enable speaker diarization. Note: AssemblyAI will return per-word speaker\n * labels, but the JS framework's `stt.SpeechData` type does not yet expose\n * a `speakerId` field (unlike the Python framework), so the labels are not\n * currently surfaced on emitted events. Setting this to `true` still has\n * effect server-side. Once the base `SpeechData` interface gains speaker\n * support, `#processStreamEvent` should forward `data.words[].speaker` too.\n */\n speakerLabels?: boolean;\n maxSpeakers?: number;\n domain?: string;\n /** Isolate the primary voice and suppress background noise. Connect-time only. */\n voiceFocus?: VoiceFocus;\n /** Background audio suppression aggressiveness, from 0.0 to 1.0. Connect-time only. */\n voiceFocusThreshold?: number;\n /**\n * Accuracy/latency preset for u3-rt-pro: `min_latency`, `balanced`, or `max_accuracy`.\n * Explicit silence, partials, or VAD options still take precedence over mode defaults.\n */\n mode?: 'min_latency' | 'balanced' | 'max_accuracy';\n baseUrl: string;\n}\n\nconst defaultSTTOptions: STTOptions = {\n apiKey: process.env.ASSEMBLYAI_API_KEY,\n sampleRate: 16000,\n bufferSizeMs: 50,\n encoding: 'pcm_s16le',\n speechModel: 'universal-3-5-pro',\n baseUrl: 'wss://streaming.assemblyai.com',\n};\n\nexport class STT extends stt.STT {\n #opts: STTOptions;\n #streams = new Set<WeakRef<SpeechStream>>();\n label = 'assemblyai.STT';\n\n get model(): string {\n return this.#opts.speechModel;\n }\n\n get provider(): string {\n return 'AssemblyAI';\n }\n\n constructor(opts: Partial<STTOptions> = {}) {\n super({\n streaming: true,\n interimResults: true,\n alignedTranscript: 'word',\n });\n\n if (opts.speechModel === 'u3-pro') {\n log().warn(\"'u3-pro' is deprecated, use 'u3-rt-pro' instead.\");\n opts.speechModel = 'u3-rt-pro';\n }\n\n const speechModel = opts.speechModel ?? defaultSTTOptions.speechModel;\n if (!isU3ProModel(speechModel)) {\n for (const param of ['prompt', 'voiceFocus', 'voiceFocusThreshold', 'mode'] as const) {\n if (opts[param] !== undefined) {\n throw new Error(\n `The '${param}' parameter is only supported with the ${U3_PRO_MODELS.join(', ')} models.`,\n );\n }\n }\n }\n\n const apiKey = opts.apiKey ?? defaultSTTOptions.apiKey;\n if (!apiKey) {\n throw new Error(\n 'AssemblyAI API key is required. Pass one in via the `apiKey` parameter, or set it as the `ASSEMBLYAI_API_KEY` environment variable',\n );\n }\n\n // Minimize latency; matches LK's end-of-turn detector well.\n const minTurnSilence = opts.minTurnSilence ?? 100;\n\n this.#opts = {\n ...defaultSTTOptions,\n ...opts,\n apiKey,\n minTurnSilence,\n };\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n async _recognize(_: AudioBuffer): Promise<stt.SpeechEvent> {\n throw new Error('Non-streaming recognize is not supported on AssemblyAI STT');\n }\n\n updateOptions(opts: Partial<STTOptions>) {\n this.#opts = { ...this.#opts, ...opts };\n for (const ref of this.#streams) {\n const stream = ref.deref();\n if (stream) {\n stream.updateOptions(opts);\n } else {\n this.#streams.delete(ref);\n }\n }\n }\n\n stream(options?: { connOptions?: APIConnectOptions }): SpeechStream {\n const stream = new SpeechStream(this, this.#opts, options?.connOptions);\n this.#streams.add(new WeakRef(stream));\n return stream;\n }\n}\n\nexport class SpeechStream extends stt.SpeechStream {\n static readonly CLOSE_MSG = JSON.stringify({ type: 'Terminate' });\n\n #opts: STTOptions;\n #logger = log();\n #speechDurationInS = 0;\n #lastPreflightStartTime = 0;\n #pendingConfigMessages: Record<string, unknown>[] = [];\n #configMessagePending = new Future();\n #sessionId: string | null = null;\n #expiresAt: number | null = null;\n label = 'assemblyai.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 }\n\n /**\n * The AssemblyAI session ID. Set when the WebSocket connection is established\n * (before any speech events). Null until the connection completes.\n * Share this with the AssemblyAI team when reporting issues.\n */\n get sessionId(): string | null {\n return this.#sessionId;\n }\n\n /**\n * Unix timestamp when the AssemblyAI session expires. Set alongside\n * {@link sessionId} when the WebSocket connection is established.\n */\n get expiresAt(): number | null {\n return this.#expiresAt;\n }\n\n updateOptions(opts: Partial<STTOptions>) {\n this.#opts = { ...this.#opts, ...opts };\n\n const configMsg: Record<string, unknown> = { type: 'UpdateConfiguration' };\n if (opts.prompt !== undefined) configMsg.prompt = opts.prompt;\n if (opts.keytermsPrompt !== undefined) configMsg.keyterms_prompt = opts.keytermsPrompt;\n if (opts.maxTurnSilence !== undefined) configMsg.max_turn_silence = opts.maxTurnSilence;\n if (opts.minTurnSilence !== undefined) configMsg.min_turn_silence = opts.minTurnSilence;\n if (opts.endOfTurnConfidenceThreshold !== undefined) {\n configMsg.end_of_turn_confidence_threshold = opts.endOfTurnConfidenceThreshold;\n }\n if (opts.vadThreshold !== undefined) configMsg.vad_threshold = opts.vadThreshold;\n\n // Only send if any actual fields (besides `type`) were specified.\n if (Object.keys(configMsg).length > 1) {\n this.#pendingConfigMessages.push(configMsg);\n if (!this.#configMessagePending.done) this.#configMessagePending.resolve();\n }\n }\n\n /**\n * Force-finalize the current turn immediately.\n */\n forceEndpoint() {\n this.#pendingConfigMessages.push({ type: 'ForceEndpoint' });\n if (!this.#configMessagePending.done) this.#configMessagePending.resolve();\n }\n\n // Deepgram-style reconnect loop around a single websocket lifetime.\n protected async run() {\n const maxRetry = 32;\n let retries = 0;\n\n while (!this.input.closed && !this.closed) {\n try {\n const ws = await this.#connectWS();\n await this.#runWS(ws);\n retries = 0;\n } catch (e) {\n if (!this.closed && !this.input.closed) {\n if (retries >= maxRetry) {\n throw new Error(`failed to connect to AssemblyAI after ${retries} attempts: ${e}`);\n }\n\n const retryDelaySeconds = Math.min(retries * 5, 10);\n retries++;\n\n this.#logger.warn(\n `failed to connect to AssemblyAI, retrying in ${retryDelaySeconds} seconds: ${e} (${retries}/${maxRetry})`,\n );\n await delay(retryDelaySeconds * 1000);\n } else {\n this.#logger.warn(\n `AssemblyAI disconnected, connection is closed: ${e} (inputClosed: ${this.input.closed}, isClosed: ${this.closed})`,\n );\n }\n }\n }\n\n this.closed = true;\n }\n\n async #connectWS(): Promise<WebSocket> {\n // u3-rt-pro family models default both min and max silence to 100ms when unset.\n let minSilence = this.#opts.minTurnSilence;\n let maxSilence = this.#opts.maxTurnSilence;\n if (isU3ProModel(this.#opts.speechModel)) {\n if (minSilence === undefined) minSilence = 100;\n if (maxSilence === undefined) maxSilence = minSilence;\n }\n\n // Default language_detection to true for multilingual / u3-rt-pro-family models, false otherwise.\n const defaultLanguageDetection =\n this.#opts.speechModel.includes('multilingual') || isU3ProModel(this.#opts.speechModel);\n const languageDetection = this.#opts.languageDetection ?? defaultLanguageDetection;\n\n const liveConfig: Record<string, unknown> = {\n sample_rate: this.#opts.sampleRate,\n encoding: this.#opts.encoding,\n speech_model: this.#opts.speechModel,\n format_turns: this.#opts.formatTurns,\n end_of_turn_confidence_threshold: this.#opts.endOfTurnConfidenceThreshold,\n min_turn_silence: minSilence,\n max_turn_silence: maxSilence,\n keyterms_prompt:\n this.#opts.keytermsPrompt !== undefined\n ? JSON.stringify(this.#opts.keytermsPrompt)\n : undefined,\n language_detection: languageDetection,\n prompt: this.#opts.prompt,\n vad_threshold: this.#opts.vadThreshold,\n speaker_labels: this.#opts.speakerLabels,\n max_speakers: this.#opts.maxSpeakers,\n domain: this.#opts.domain,\n voice_focus: this.#opts.voiceFocus,\n voice_focus_threshold: this.#opts.voiceFocusThreshold,\n mode: this.#opts.mode,\n };\n\n const url = new URL(`${this.#opts.baseUrl}/v3/ws`);\n // Python serializes booleans as the strings \"true\"/\"false\", so we mirror that.\n for (const [key, value] of Object.entries(liveConfig)) {\n if (value === undefined || value === null) continue;\n if (typeof value === 'boolean') {\n url.searchParams.append(key, value ? 'true' : 'false');\n } else {\n url.searchParams.append(key, String(value));\n }\n }\n\n const ws = new WebSocket(url, {\n headers: {\n Authorization: this.#opts.apiKey!,\n 'Content-Type': 'application/json',\n 'User-Agent': 'AssemblyAI/1.0 (integration=Livekit)',\n },\n });\n\n await new Promise<void>((resolve, reject) => {\n ws.on('open', () => resolve());\n ws.on('error', (error) => reject(error));\n ws.on('close', (code) => reject(new Error(`WebSocket returned ${code}`)));\n });\n\n return ws;\n }\n\n async #runWS(ws: WebSocket) {\n let closing = false;\n const sessionController = new AbortController();\n\n // gets cancelled also when sendTask is complete\n const wsMonitor = Task.from(async (controller) => {\n const closed = new Promise<void>((_, 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 samplesPerBuffer = Math.floor((this.#opts.sampleRate * this.#opts.bufferSizeMs) / 1000);\n const audioStream = new AudioByteStream(this.#opts.sampleRate, 1, samplesPerBuffer);\n\n const abortPromise = waitForAbort(this.abortSignal);\n const sessionAbort = waitForAbort(sessionController.signal);\n\n try {\n while (!this.closed) {\n const result = await Promise.race([this.input.next(), abortPromise, sessionAbort]);\n\n if (result === undefined) return; // aborted\n if (result.done) break;\n\n const data = result.value;\n\n let frames: AudioFrame[];\n if (data === SpeechStream.FLUSH_SENTINEL) {\n frames = audioStream.flush();\n } else if (data.sampleRate === this.#opts.sampleRate && data.channels === 1) {\n // AssemblyAI expects mono PCM. The base SpeechStream only resamples\n // sample rate, so reject any frame that is not already downmixed.\n frames = audioStream.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 (const frame of frames) {\n this.#speechDurationInS += frame.samplesPerChannel / frame.sampleRate;\n ws.send(frame.data.buffer);\n }\n }\n } finally {\n closing = true;\n try {\n ws.send(SpeechStream.CLOSE_MSG);\n } catch {\n // ignore — socket may already be closing\n }\n wsMonitor.cancel();\n }\n };\n\n let messageHandler: ((msg: RawData, isBinary: boolean) => void) | null = null;\n const listenTask = Task.from(async (controller) => {\n const listenMessage = new Promise<void>((resolve, reject) => {\n messageHandler = (msg, isBinary) => {\n if (isBinary) {\n this.#logger.error('unexpected binary message from AssemblyAI');\n return;\n }\n try {\n const json = JSON.parse(msg.toString()) as StreamEventMessage;\n this.#processStreamEvent(json);\n if (this.closed || closing) {\n resolve();\n }\n } catch (err) {\n this.#logger.error(`AssemblyAI: error processing message: ${msg}`);\n reject(err);\n }\n };\n ws.on('message', messageHandler);\n });\n\n await Promise.race([listenMessage, waitForAbort(controller.signal)]);\n });\n\n const configTask = Task.from(async (controller) => {\n // Drain any messages queued while the socket was reconnecting.\n while (this.#pendingConfigMessages.length > 0) {\n const msg = this.#pendingConfigMessages.shift()!;\n ws.send(JSON.stringify(msg));\n }\n\n while (!controller.signal.aborted) {\n await Promise.race([this.#configMessagePending.await, waitForAbort(controller.signal)]);\n if (controller.signal.aborted) return;\n\n this.#configMessagePending = new Future();\n while (this.#pendingConfigMessages.length > 0) {\n const msg = this.#pendingConfigMessages.shift()!;\n ws.send(JSON.stringify(msg));\n }\n }\n });\n\n try {\n await Promise.all([sendTask(), listenTask.result, wsMonitor.result]);\n } finally {\n closing = true;\n sessionController.abort();\n listenTask.cancel();\n configTask.cancel();\n if (messageHandler) ws.off('message', messageHandler);\n try {\n ws.close();\n } catch {\n // ignore\n }\n }\n }\n\n #averageConfidence(words: Array<{ confidence?: number }>): number {\n if (words.length === 0) return 0;\n return words.reduce((sum, w) => sum + (w.confidence ?? 0), 0) / words.length;\n }\n\n #processStreamEvent(data: StreamEventMessage) {\n const messageType = data.type;\n\n if (messageType === 'Begin') {\n this.#sessionId = data.id ?? null;\n this.#expiresAt = data.expires_at ?? null;\n this.#logger.info(\n `AssemblyAI session started id=${this.#sessionId} expires_at=${this.#expiresAt}`,\n );\n return;\n }\n\n if (messageType === 'SpeechStarted') {\n this.queue.put({ type: stt.SpeechEventType.START_OF_SPEECH });\n return;\n }\n\n if (messageType === 'Termination') {\n this.#logger.debug(\n `AssemblyAI session terminated audio_duration=${data.audio_duration_seconds}s session_duration=${data.session_duration_seconds}s`,\n );\n return;\n }\n\n if (messageType !== 'Turn') {\n return;\n }\n\n const words = data.words ?? [];\n const endOfTurn = Boolean(data.end_of_turn);\n const turnIsFormatted = Boolean(data.turn_is_formatted);\n const utterance = data.utterance ?? '';\n const transcript = data.transcript ?? '';\n const language = normalizeLanguage(data.language_code ?? 'en');\n\n // Word timestamps are in milliseconds:\n // https://www.assemblyai.com/docs/api-reference/streaming-api/streaming-api#receive.receiveTurn.words\n const timedWords = words.map((word) =>\n createTimedString({\n text: word.text ?? '',\n startTime: (word.start ?? 0) / 1000 + this.startTimeOffset,\n endTime: (word.end ?? 0) / 1000 + this.startTimeOffset,\n confidence: word.confidence ?? 0,\n startTimeOffset: this.startTimeOffset,\n }),\n );\n\n let startTime = 0;\n let endTime = 0;\n let confidence = 0;\n\n // `words` are cumulative for the turn — emit as an interim transcript.\n if (timedWords.length > 0) {\n const interimText = timedWords.map((w) => w.text).join(' ');\n startTime = timedWords[0]!.startTime ?? 0;\n endTime = timedWords[timedWords.length - 1]!.endTime ?? 0;\n confidence = this.#averageConfidence(timedWords);\n\n this.queue.put({\n type: stt.SpeechEventType.INTERIM_TRANSCRIPT,\n alternatives: [\n {\n language,\n text: interimText,\n startTime,\n endTime,\n confidence,\n words: timedWords,\n },\n ],\n });\n }\n\n // `utterance` is chunk-based (not cumulative) — emit as a preflight transcript\n // covering only the words since the last preflight.\n if (utterance) {\n if (this.#lastPreflightStartTime === 0) {\n this.#lastPreflightStartTime = startTime;\n }\n\n const utteranceWords = timedWords.filter(\n (w) => w.startTime !== undefined && w.startTime >= this.#lastPreflightStartTime,\n );\n const utteranceConfidence = this.#averageConfidence(utteranceWords);\n\n this.queue.put({\n type: stt.SpeechEventType.PREFLIGHT_TRANSCRIPT,\n alternatives: [\n {\n language,\n text: utterance,\n startTime: this.#lastPreflightStartTime,\n endTime,\n confidence: utteranceConfidence,\n words: utteranceWords,\n },\n ],\n });\n this.#lastPreflightStartTime = endTime;\n }\n\n // End-of-turn: emit FINAL_TRANSCRIPT + END_OF_SPEECH.\n // If the user asked for formatted turns, wait for a formatted final.\n const waitingForFormatted = this.#opts.formatTurns === true && !turnIsFormatted;\n if (endOfTurn && !waitingForFormatted) {\n this.queue.put({\n type: stt.SpeechEventType.FINAL_TRANSCRIPT,\n alternatives: [\n {\n language,\n text: transcript,\n startTime,\n endTime,\n confidence,\n words: timedWords,\n },\n ],\n });\n\n this.queue.put({ type: stt.SpeechEventType.END_OF_SPEECH });\n if (this.#speechDurationInS > 0) {\n this.queue.put({\n type: stt.SpeechEventType.RECOGNITION_USAGE,\n // Propagate the AssemblyAI session id as the request id so metrics\n // can be correlated back to a specific connection, mirroring how\n // Deepgram surfaces its `request_id`.\n requestId: this.#sessionId ?? undefined,\n recognitionUsage: {\n audioDuration: this.#speechDurationInS,\n },\n });\n this.#speechDurationInS = 0;\n this.#lastPreflightStartTime = 0;\n }\n }\n }\n}\n"],"mappings":"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","names":["stt"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@livekit/agents-plugin-assemblyai",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.8",
|
|
4
4
|
"description": "AssemblyAI plugin for LiveKit Agents for Node.js",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"require": "dist/index.cjs",
|
|
@@ -30,16 +30,16 @@
|
|
|
30
30
|
"@types/ws": "^8.5.10",
|
|
31
31
|
"tsup": "^8.3.5",
|
|
32
32
|
"typescript": "^5.0.0",
|
|
33
|
-
"@livekit/agents": "1.4.
|
|
34
|
-
"@livekit/agents-plugin-silero": "1.4.
|
|
35
|
-
"@livekit/agents-plugins-test": "1.4.
|
|
33
|
+
"@livekit/agents": "1.4.8",
|
|
34
|
+
"@livekit/agents-plugin-silero": "1.4.8",
|
|
35
|
+
"@livekit/agents-plugins-test": "1.4.8"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"ws": "^8.16.0"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
41
|
"@livekit/rtc-node": "^0.13.29",
|
|
42
|
-
"@livekit/agents": "1.4.
|
|
42
|
+
"@livekit/agents": "1.4.8"
|
|
43
43
|
},
|
|
44
44
|
"scripts": {
|
|
45
45
|
"build": "tsup --onSuccess \"pnpm build:types\"",
|
package/src/models.ts
CHANGED
|
@@ -6,9 +6,13 @@ export type STTModels =
|
|
|
6
6
|
| 'universal-streaming-english'
|
|
7
7
|
| 'universal-streaming-multilingual'
|
|
8
8
|
| 'u3-rt-pro'
|
|
9
|
+
| 'u3-rt-pro-beta-1'
|
|
10
|
+
| 'universal-3-5-pro'
|
|
9
11
|
// Deprecated alias — AssemblyAI maps this to `u3-rt-pro` server-side, but the
|
|
10
12
|
// Python plugin emits a warning and rewrites it. Kept here so TS users don't
|
|
11
13
|
// break if they already pass it.
|
|
12
14
|
| 'u3-pro';
|
|
13
15
|
|
|
14
16
|
export type STTEncoding = 'pcm_s16le' | 'pcm_mulaw';
|
|
17
|
+
|
|
18
|
+
export type VoiceFocus = 'near-field' | 'far-field';
|
package/src/stt.ts
CHANGED
|
@@ -18,7 +18,13 @@ import {
|
|
|
18
18
|
import type { AudioFrame } from '@livekit/rtc-node';
|
|
19
19
|
import type { RawData } from 'ws';
|
|
20
20
|
import { WebSocket } from 'ws';
|
|
21
|
-
import type { STTEncoding, STTModels } from './models.js';
|
|
21
|
+
import type { STTEncoding, STTModels, VoiceFocus } from './models.js';
|
|
22
|
+
|
|
23
|
+
const U3_PRO_MODELS = ['u3-rt-pro', 'u3-rt-pro-beta-1', 'universal-3-5-pro'] as const;
|
|
24
|
+
|
|
25
|
+
function isU3ProModel(model: STTModels): boolean {
|
|
26
|
+
return U3_PRO_MODELS.includes(model as (typeof U3_PRO_MODELS)[number]);
|
|
27
|
+
}
|
|
22
28
|
|
|
23
29
|
// AssemblyAI Universal-Streaming (v3) message envelope. All fields are optional
|
|
24
30
|
// since we narrow on `type` before reading anything else.
|
|
@@ -80,6 +86,15 @@ export interface STTOptions {
|
|
|
80
86
|
speakerLabels?: boolean;
|
|
81
87
|
maxSpeakers?: number;
|
|
82
88
|
domain?: string;
|
|
89
|
+
/** Isolate the primary voice and suppress background noise. Connect-time only. */
|
|
90
|
+
voiceFocus?: VoiceFocus;
|
|
91
|
+
/** Background audio suppression aggressiveness, from 0.0 to 1.0. Connect-time only. */
|
|
92
|
+
voiceFocusThreshold?: number;
|
|
93
|
+
/**
|
|
94
|
+
* Accuracy/latency preset for u3-rt-pro: `min_latency`, `balanced`, or `max_accuracy`.
|
|
95
|
+
* Explicit silence, partials, or VAD options still take precedence over mode defaults.
|
|
96
|
+
*/
|
|
97
|
+
mode?: 'min_latency' | 'balanced' | 'max_accuracy';
|
|
83
98
|
baseUrl: string;
|
|
84
99
|
}
|
|
85
100
|
|
|
@@ -88,7 +103,7 @@ const defaultSTTOptions: STTOptions = {
|
|
|
88
103
|
sampleRate: 16000,
|
|
89
104
|
bufferSizeMs: 50,
|
|
90
105
|
encoding: 'pcm_s16le',
|
|
91
|
-
speechModel: 'universal-
|
|
106
|
+
speechModel: 'universal-3-5-pro',
|
|
92
107
|
baseUrl: 'wss://streaming.assemblyai.com',
|
|
93
108
|
};
|
|
94
109
|
|
|
@@ -117,8 +132,15 @@ export class STT extends stt.STT {
|
|
|
117
132
|
opts.speechModel = 'u3-rt-pro';
|
|
118
133
|
}
|
|
119
134
|
|
|
120
|
-
|
|
121
|
-
|
|
135
|
+
const speechModel = opts.speechModel ?? defaultSTTOptions.speechModel;
|
|
136
|
+
if (!isU3ProModel(speechModel)) {
|
|
137
|
+
for (const param of ['prompt', 'voiceFocus', 'voiceFocusThreshold', 'mode'] as const) {
|
|
138
|
+
if (opts[param] !== undefined) {
|
|
139
|
+
throw new Error(
|
|
140
|
+
`The '${param}' parameter is only supported with the ${U3_PRO_MODELS.join(', ')} models.`,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
122
144
|
}
|
|
123
145
|
|
|
124
146
|
const apiKey = opts.apiKey ?? defaultSTTOptions.apiKey;
|
|
@@ -262,17 +284,17 @@ export class SpeechStream extends stt.SpeechStream {
|
|
|
262
284
|
}
|
|
263
285
|
|
|
264
286
|
async #connectWS(): Promise<WebSocket> {
|
|
265
|
-
// u3-rt-pro
|
|
287
|
+
// u3-rt-pro family models default both min and max silence to 100ms when unset.
|
|
266
288
|
let minSilence = this.#opts.minTurnSilence;
|
|
267
289
|
let maxSilence = this.#opts.maxTurnSilence;
|
|
268
|
-
if (this.#opts.speechModel
|
|
290
|
+
if (isU3ProModel(this.#opts.speechModel)) {
|
|
269
291
|
if (minSilence === undefined) minSilence = 100;
|
|
270
292
|
if (maxSilence === undefined) maxSilence = minSilence;
|
|
271
293
|
}
|
|
272
294
|
|
|
273
|
-
// Default language_detection to true for multilingual / u3-rt-pro models, false otherwise.
|
|
295
|
+
// Default language_detection to true for multilingual / u3-rt-pro-family models, false otherwise.
|
|
274
296
|
const defaultLanguageDetection =
|
|
275
|
-
this.#opts.speechModel.includes('multilingual') || this.#opts.speechModel
|
|
297
|
+
this.#opts.speechModel.includes('multilingual') || isU3ProModel(this.#opts.speechModel);
|
|
276
298
|
const languageDetection = this.#opts.languageDetection ?? defaultLanguageDetection;
|
|
277
299
|
|
|
278
300
|
const liveConfig: Record<string, unknown> = {
|
|
@@ -293,6 +315,9 @@ export class SpeechStream extends stt.SpeechStream {
|
|
|
293
315
|
speaker_labels: this.#opts.speakerLabels,
|
|
294
316
|
max_speakers: this.#opts.maxSpeakers,
|
|
295
317
|
domain: this.#opts.domain,
|
|
318
|
+
voice_focus: this.#opts.voiceFocus,
|
|
319
|
+
voice_focus_threshold: this.#opts.voiceFocusThreshold,
|
|
320
|
+
mode: this.#opts.mode,
|
|
296
321
|
};
|
|
297
322
|
|
|
298
323
|
const url = new URL(`${this.#opts.baseUrl}/v3/ws`);
|