@livekit/agents 1.1.0-dev.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (292) hide show
  1. package/dist/cli.cjs +2 -0
  2. package/dist/cli.cjs.map +1 -1
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +2 -0
  5. package/dist/cli.js.map +1 -1
  6. package/dist/constants.cjs +3 -0
  7. package/dist/constants.cjs.map +1 -1
  8. package/dist/constants.d.cts +1 -0
  9. package/dist/constants.d.ts +1 -0
  10. package/dist/constants.d.ts.map +1 -1
  11. package/dist/constants.js +2 -0
  12. package/dist/constants.js.map +1 -1
  13. package/dist/cpu.cjs +189 -0
  14. package/dist/cpu.cjs.map +1 -0
  15. package/dist/cpu.d.cts +24 -0
  16. package/dist/cpu.d.ts +24 -0
  17. package/dist/cpu.d.ts.map +1 -0
  18. package/dist/cpu.js +152 -0
  19. package/dist/cpu.js.map +1 -0
  20. package/dist/cpu.test.cjs +227 -0
  21. package/dist/cpu.test.cjs.map +1 -0
  22. package/dist/cpu.test.js +204 -0
  23. package/dist/cpu.test.js.map +1 -0
  24. package/dist/index.cjs +12 -10
  25. package/dist/index.cjs.map +1 -1
  26. package/dist/index.d.cts +13 -13
  27. package/dist/index.d.ts +13 -13
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +11 -10
  30. package/dist/index.js.map +1 -1
  31. package/dist/inference/interruption/defaults.cjs +1 -1
  32. package/dist/inference/interruption/defaults.cjs.map +1 -1
  33. package/dist/inference/interruption/defaults.d.cts +1 -1
  34. package/dist/inference/interruption/defaults.d.ts +1 -1
  35. package/dist/inference/interruption/defaults.d.ts.map +1 -1
  36. package/dist/inference/interruption/defaults.js +1 -1
  37. package/dist/inference/interruption/defaults.js.map +1 -1
  38. package/dist/inference/interruption/http_transport.cjs +44 -28
  39. package/dist/inference/interruption/http_transport.cjs.map +1 -1
  40. package/dist/inference/interruption/http_transport.d.ts.map +1 -1
  41. package/dist/inference/interruption/http_transport.js +45 -29
  42. package/dist/inference/interruption/http_transport.js.map +1 -1
  43. package/dist/inference/interruption/interruption_detector.cjs +22 -5
  44. package/dist/inference/interruption/interruption_detector.cjs.map +1 -1
  45. package/dist/inference/interruption/interruption_detector.d.cts +2 -2
  46. package/dist/inference/interruption/interruption_detector.d.ts +2 -2
  47. package/dist/inference/interruption/interruption_detector.d.ts.map +1 -1
  48. package/dist/inference/interruption/interruption_detector.js +22 -5
  49. package/dist/inference/interruption/interruption_detector.js.map +1 -1
  50. package/dist/inference/interruption/interruption_stream.cjs +4 -4
  51. package/dist/inference/interruption/interruption_stream.cjs.map +1 -1
  52. package/dist/inference/interruption/interruption_stream.js +4 -4
  53. package/dist/inference/interruption/interruption_stream.js.map +1 -1
  54. package/dist/inference/interruption/types.cjs.map +1 -1
  55. package/dist/inference/interruption/types.d.cts +2 -2
  56. package/dist/inference/interruption/types.d.ts +2 -2
  57. package/dist/inference/interruption/types.d.ts.map +1 -1
  58. package/dist/inference/interruption/ws_transport.cjs +60 -47
  59. package/dist/inference/interruption/ws_transport.cjs.map +1 -1
  60. package/dist/inference/interruption/ws_transport.d.ts.map +1 -1
  61. package/dist/inference/interruption/ws_transport.js +60 -47
  62. package/dist/inference/interruption/ws_transport.js.map +1 -1
  63. package/dist/inference/llm.cjs.map +1 -1
  64. package/dist/inference/llm.d.cts +1 -1
  65. package/dist/inference/llm.d.ts +1 -1
  66. package/dist/inference/llm.d.ts.map +1 -1
  67. package/dist/inference/llm.js.map +1 -1
  68. package/dist/inference/stt.cjs +20 -12
  69. package/dist/inference/stt.cjs.map +1 -1
  70. package/dist/inference/stt.d.cts +3 -2
  71. package/dist/inference/stt.d.ts +3 -2
  72. package/dist/inference/stt.d.ts.map +1 -1
  73. package/dist/inference/stt.js +20 -12
  74. package/dist/inference/stt.js.map +1 -1
  75. package/dist/inference/stt.test.cjs +14 -0
  76. package/dist/inference/stt.test.cjs.map +1 -1
  77. package/dist/inference/stt.test.js +14 -0
  78. package/dist/inference/stt.test.js.map +1 -1
  79. package/dist/inference/tts.cjs +13 -4
  80. package/dist/inference/tts.cjs.map +1 -1
  81. package/dist/inference/tts.d.cts +8 -1
  82. package/dist/inference/tts.d.ts +8 -1
  83. package/dist/inference/tts.d.ts.map +1 -1
  84. package/dist/inference/tts.js +13 -4
  85. package/dist/inference/tts.js.map +1 -1
  86. package/dist/inference/tts.test.cjs +10 -0
  87. package/dist/inference/tts.test.cjs.map +1 -1
  88. package/dist/inference/tts.test.js +10 -0
  89. package/dist/inference/tts.test.js.map +1 -1
  90. package/dist/ipc/job_proc_lazy_main.cjs +41 -23
  91. package/dist/ipc/job_proc_lazy_main.cjs.map +1 -1
  92. package/dist/ipc/job_proc_lazy_main.js +41 -23
  93. package/dist/ipc/job_proc_lazy_main.js.map +1 -1
  94. package/dist/job.cjs +1 -1
  95. package/dist/job.cjs.map +1 -1
  96. package/dist/job.js +1 -1
  97. package/dist/job.js.map +1 -1
  98. package/dist/language.cjs +394 -0
  99. package/dist/language.cjs.map +1 -0
  100. package/dist/language.d.cts +15 -0
  101. package/dist/language.d.ts +15 -0
  102. package/dist/language.d.ts.map +1 -0
  103. package/dist/language.js +363 -0
  104. package/dist/language.js.map +1 -0
  105. package/dist/language.test.cjs +43 -0
  106. package/dist/language.test.cjs.map +1 -0
  107. package/dist/language.test.js +49 -0
  108. package/dist/language.test.js.map +1 -0
  109. package/dist/llm/index.cjs +2 -0
  110. package/dist/llm/index.cjs.map +1 -1
  111. package/dist/llm/index.d.cts +1 -1
  112. package/dist/llm/index.d.ts +1 -1
  113. package/dist/llm/index.d.ts.map +1 -1
  114. package/dist/llm/index.js +2 -0
  115. package/dist/llm/index.js.map +1 -1
  116. package/dist/stream/deferred_stream.cjs +6 -2
  117. package/dist/stream/deferred_stream.cjs.map +1 -1
  118. package/dist/stream/deferred_stream.d.ts.map +1 -1
  119. package/dist/stream/deferred_stream.js +6 -2
  120. package/dist/stream/deferred_stream.js.map +1 -1
  121. package/dist/stt/stt.cjs.map +1 -1
  122. package/dist/stt/stt.d.cts +2 -1
  123. package/dist/stt/stt.d.ts +2 -1
  124. package/dist/stt/stt.d.ts.map +1 -1
  125. package/dist/stt/stt.js.map +1 -1
  126. package/dist/utils.cjs +15 -0
  127. package/dist/utils.cjs.map +1 -1
  128. package/dist/utils.d.cts +8 -0
  129. package/dist/utils.d.ts +8 -0
  130. package/dist/utils.d.ts.map +1 -1
  131. package/dist/utils.js +13 -0
  132. package/dist/utils.js.map +1 -1
  133. package/dist/version.cjs +1 -1
  134. package/dist/version.js +1 -1
  135. package/dist/voice/agent.cjs +14 -17
  136. package/dist/voice/agent.cjs.map +1 -1
  137. package/dist/voice/agent.d.cts +10 -11
  138. package/dist/voice/agent.d.ts +10 -11
  139. package/dist/voice/agent.d.ts.map +1 -1
  140. package/dist/voice/agent.js +15 -18
  141. package/dist/voice/agent.js.map +1 -1
  142. package/dist/voice/agent.test.cjs +194 -0
  143. package/dist/voice/agent.test.cjs.map +1 -1
  144. package/dist/voice/agent.test.js +195 -1
  145. package/dist/voice/agent.test.js.map +1 -1
  146. package/dist/voice/agent_activity.cjs +116 -39
  147. package/dist/voice/agent_activity.cjs.map +1 -1
  148. package/dist/voice/agent_activity.d.cts +2 -0
  149. package/dist/voice/agent_activity.d.ts +2 -0
  150. package/dist/voice/agent_activity.d.ts.map +1 -1
  151. package/dist/voice/agent_activity.js +117 -40
  152. package/dist/voice/agent_activity.js.map +1 -1
  153. package/dist/voice/agent_activity.test.cjs +135 -0
  154. package/dist/voice/agent_activity.test.cjs.map +1 -0
  155. package/dist/voice/agent_activity.test.js +134 -0
  156. package/dist/voice/agent_activity.test.js.map +1 -0
  157. package/dist/voice/agent_session.cjs +38 -38
  158. package/dist/voice/agent_session.cjs.map +1 -1
  159. package/dist/voice/agent_session.d.cts +65 -56
  160. package/dist/voice/agent_session.d.ts +65 -56
  161. package/dist/voice/agent_session.d.ts.map +1 -1
  162. package/dist/voice/agent_session.js +37 -37
  163. package/dist/voice/agent_session.js.map +1 -1
  164. package/dist/voice/audio_recognition.cjs +106 -52
  165. package/dist/voice/audio_recognition.cjs.map +1 -1
  166. package/dist/voice/audio_recognition.d.cts +4 -2
  167. package/dist/voice/audio_recognition.d.ts +4 -2
  168. package/dist/voice/audio_recognition.d.ts.map +1 -1
  169. package/dist/voice/audio_recognition.js +106 -52
  170. package/dist/voice/audio_recognition.js.map +1 -1
  171. package/dist/voice/audio_recognition_span.test.cjs +84 -22
  172. package/dist/voice/audio_recognition_span.test.cjs.map +1 -1
  173. package/dist/voice/audio_recognition_span.test.js +90 -23
  174. package/dist/voice/audio_recognition_span.test.js.map +1 -1
  175. package/dist/voice/events.cjs +1 -1
  176. package/dist/voice/events.cjs.map +1 -1
  177. package/dist/voice/events.d.cts +4 -3
  178. package/dist/voice/events.d.ts +4 -3
  179. package/dist/voice/events.d.ts.map +1 -1
  180. package/dist/voice/events.js +1 -1
  181. package/dist/voice/events.js.map +1 -1
  182. package/dist/voice/index.cjs +9 -1
  183. package/dist/voice/index.cjs.map +1 -1
  184. package/dist/voice/index.d.cts +1 -1
  185. package/dist/voice/index.d.ts +1 -1
  186. package/dist/voice/index.d.ts.map +1 -1
  187. package/dist/voice/index.js +10 -1
  188. package/dist/voice/index.js.map +1 -1
  189. package/dist/voice/remote_session.cjs +922 -0
  190. package/dist/voice/remote_session.cjs.map +1 -0
  191. package/dist/voice/remote_session.d.cts +108 -0
  192. package/dist/voice/remote_session.d.ts +108 -0
  193. package/dist/voice/remote_session.d.ts.map +1 -0
  194. package/dist/voice/remote_session.js +887 -0
  195. package/dist/voice/remote_session.js.map +1 -0
  196. package/dist/voice/report.cjs +11 -10
  197. package/dist/voice/report.cjs.map +1 -1
  198. package/dist/voice/report.d.cts +5 -3
  199. package/dist/voice/report.d.ts +5 -3
  200. package/dist/voice/report.d.ts.map +1 -1
  201. package/dist/voice/report.js +11 -10
  202. package/dist/voice/report.js.map +1 -1
  203. package/dist/voice/report.test.cjs +15 -0
  204. package/dist/voice/report.test.cjs.map +1 -1
  205. package/dist/voice/report.test.js +15 -0
  206. package/dist/voice/report.test.js.map +1 -1
  207. package/dist/voice/room_io/room_io.cjs +39 -0
  208. package/dist/voice/room_io/room_io.cjs.map +1 -1
  209. package/dist/voice/room_io/room_io.d.cts +3 -1
  210. package/dist/voice/room_io/room_io.d.ts +3 -1
  211. package/dist/voice/room_io/room_io.d.ts.map +1 -1
  212. package/dist/voice/room_io/room_io.js +40 -1
  213. package/dist/voice/room_io/room_io.js.map +1 -1
  214. package/dist/voice/turn_config/interruption.cjs.map +1 -1
  215. package/dist/voice/turn_config/interruption.d.cts +1 -1
  216. package/dist/voice/turn_config/interruption.d.ts +1 -1
  217. package/dist/voice/turn_config/interruption.d.ts.map +1 -1
  218. package/dist/voice/turn_config/interruption.js.map +1 -1
  219. package/dist/voice/turn_config/utils.cjs +95 -35
  220. package/dist/voice/turn_config/utils.cjs.map +1 -1
  221. package/dist/voice/turn_config/utils.d.cts +17 -5
  222. package/dist/voice/turn_config/utils.d.ts +17 -5
  223. package/dist/voice/turn_config/utils.d.ts.map +1 -1
  224. package/dist/voice/turn_config/utils.js +93 -35
  225. package/dist/voice/turn_config/utils.js.map +1 -1
  226. package/dist/voice/turn_config/utils.test.cjs +83 -41
  227. package/dist/voice/turn_config/utils.test.cjs.map +1 -1
  228. package/dist/voice/turn_config/utils.test.js +84 -42
  229. package/dist/voice/turn_config/utils.test.js.map +1 -1
  230. package/dist/worker.cjs +6 -29
  231. package/dist/worker.cjs.map +1 -1
  232. package/dist/worker.d.ts.map +1 -1
  233. package/dist/worker.js +6 -19
  234. package/dist/worker.js.map +1 -1
  235. package/package.json +3 -2
  236. package/src/cli.ts +2 -0
  237. package/src/constants.ts +1 -0
  238. package/src/cpu.test.ts +239 -0
  239. package/src/cpu.ts +173 -0
  240. package/src/index.ts +13 -15
  241. package/src/inference/interruption/defaults.ts +1 -1
  242. package/src/inference/interruption/http_transport.ts +49 -30
  243. package/src/inference/interruption/interruption_detector.ts +22 -6
  244. package/src/inference/interruption/interruption_stream.ts +4 -4
  245. package/src/inference/interruption/types.ts +2 -2
  246. package/src/inference/interruption/ws_transport.ts +63 -59
  247. package/src/inference/llm.ts +3 -1
  248. package/src/inference/stt.test.ts +17 -0
  249. package/src/inference/stt.ts +22 -14
  250. package/src/inference/tts.test.ts +12 -0
  251. package/src/inference/tts.ts +22 -6
  252. package/src/ipc/job_proc_lazy_main.ts +44 -24
  253. package/src/job.ts +1 -1
  254. package/src/language.test.ts +62 -0
  255. package/src/language.ts +380 -0
  256. package/src/llm/index.ts +2 -0
  257. package/src/stream/deferred_stream.ts +5 -1
  258. package/src/stt/stt.ts +2 -1
  259. package/src/utils.ts +20 -0
  260. package/src/voice/agent.test.ts +208 -1
  261. package/src/voice/agent.ts +21 -22
  262. package/src/voice/agent_activity.test.ts +194 -0
  263. package/src/voice/agent_activity.ts +161 -43
  264. package/src/voice/agent_session.ts +103 -92
  265. package/src/voice/audio_recognition.ts +124 -61
  266. package/src/voice/audio_recognition_span.test.ts +115 -35
  267. package/src/voice/events.ts +4 -3
  268. package/src/voice/index.ts +10 -1
  269. package/src/voice/remote_session.ts +1083 -0
  270. package/src/voice/report.test.ts +22 -3
  271. package/src/voice/report.ts +31 -14
  272. package/src/voice/room_io/room_io.ts +52 -2
  273. package/src/voice/turn_config/interruption.ts +1 -1
  274. package/src/voice/turn_config/utils.test.ts +91 -43
  275. package/src/voice/turn_config/utils.ts +120 -56
  276. package/src/worker.ts +34 -50
  277. package/dist/voice/client_events.cjs +0 -554
  278. package/dist/voice/client_events.cjs.map +0 -1
  279. package/dist/voice/client_events.d.cts +0 -195
  280. package/dist/voice/client_events.d.ts +0 -195
  281. package/dist/voice/client_events.d.ts.map +0 -1
  282. package/dist/voice/client_events.js +0 -548
  283. package/dist/voice/client_events.js.map +0 -1
  284. package/dist/voice/wire_format.cjs +0 -798
  285. package/dist/voice/wire_format.cjs.map +0 -1
  286. package/dist/voice/wire_format.d.cts +0 -5503
  287. package/dist/voice/wire_format.d.ts +0 -5503
  288. package/dist/voice/wire_format.d.ts.map +0 -1
  289. package/dist/voice/wire_format.js +0 -728
  290. package/dist/voice/wire_format.js.map +0 -1
  291. package/src/voice/client_events.ts +0 -838
  292. package/src/voice/wire_format.ts +0 -827
@@ -1,6 +1,7 @@
1
1
  import {} from "@livekit/rtc-node";
2
2
  import { APIError, APIStatusError } from "../_exceptions.js";
3
3
  import { AudioByteStream } from "../audio.js";
4
+ import { areLanguagesEquivalent, normalizeLanguage } from "../language.js";
4
5
  import { log } from "../log.js";
5
6
  import { createStreamChannel } from "../stream/stream_channel.js";
6
7
  import {
@@ -18,7 +19,7 @@ import { connectWs, createAccessToken, getDefaultInferenceUrl } from "./utils.js
18
19
  function parseSTTModelString(model) {
19
20
  const idx = model.lastIndexOf(":");
20
21
  if (idx !== -1) {
21
- return [model.slice(0, idx), model.slice(idx + 1)];
22
+ return [model.slice(0, idx), normalizeLanguage(model.slice(idx + 1))];
22
23
  }
23
24
  return [model, void 0];
24
25
  }
@@ -68,24 +69,23 @@ class STT extends BaseSTT {
68
69
  let nextModel = model;
69
70
  let nextLanguage = language;
70
71
  if (typeof nextModel === "string") {
71
- const idx = nextModel.lastIndexOf(":");
72
- if (idx !== -1) {
73
- const languageFromModel = nextModel.slice(idx + 1);
74
- if (nextLanguage && nextLanguage !== languageFromModel) {
72
+ const [parsedModel, parsedLanguage] = parseSTTModelString(nextModel);
73
+ if (parsedLanguage !== void 0) {
74
+ if (nextLanguage && !areLanguagesEquivalent(nextLanguage, parsedLanguage)) {
75
75
  this.#logger.warn(
76
76
  "`language` is provided via both argument and model, using the one from the argument",
77
77
  { language: nextLanguage, model: nextModel }
78
78
  );
79
79
  } else {
80
- nextLanguage = languageFromModel;
80
+ nextLanguage = parsedLanguage;
81
81
  }
82
- nextModel = nextModel.slice(0, idx);
82
+ nextModel = parsedModel;
83
83
  }
84
84
  }
85
85
  const normalizedFallback = fallback ? normalizeSTTFallback(fallback) : void 0;
86
86
  this.opts = {
87
87
  model: nextModel,
88
- language: nextLanguage,
88
+ language: nextLanguage ? normalizeLanguage(nextLanguage) : void 0,
89
89
  encoding,
90
90
  sampleRate,
91
91
  baseURL: lkBaseURL,
@@ -113,7 +113,11 @@ class STT extends BaseSTT {
113
113
  throw new Error("LiveKit STT does not support batch recognition, use stream() instead");
114
114
  }
115
115
  updateOptions(opts) {
116
- this.opts = { ...this.opts, ...opts };
116
+ this.opts = {
117
+ ...this.opts,
118
+ ...opts,
119
+ language: opts.language !== void 0 ? normalizeLanguage(opts.language) : this.opts.language
120
+ };
117
121
  for (const stream of this.streams) {
118
122
  stream.updateOptions(opts);
119
123
  }
@@ -122,7 +126,7 @@ class STT extends BaseSTT {
122
126
  const { language, connOptions = this.opts.connOptions ?? DEFAULT_API_CONNECT_OPTIONS } = options || {};
123
127
  const streamOpts = {
124
128
  ...this.opts,
125
- language: language ?? this.opts.language
129
+ language: language !== void 0 ? normalizeLanguage(language) : this.opts.language
126
130
  };
127
131
  const stream = new SpeechStream(this, streamOpts, connOptions);
128
132
  this.streams.add(stream);
@@ -189,7 +193,11 @@ class SpeechStream extends BaseSpeechStream {
189
193
  return "inference.SpeechStream";
190
194
  }
191
195
  updateOptions(opts) {
192
- this.opts = { ...this.opts, ...opts };
196
+ this.opts = {
197
+ ...this.opts,
198
+ ...opts,
199
+ language: opts.language !== void 0 ? normalizeLanguage(opts.language) : this.opts.language
200
+ };
193
201
  this.reconnectEvent.set();
194
202
  }
195
203
  async run() {
@@ -356,7 +364,7 @@ class SpeechStream extends BaseSpeechStream {
356
364
  if (this.queue.closed) return;
357
365
  const requestId = data.session_id || this.requestId;
358
366
  const text = data.transcript;
359
- const language = data.language || this.opts.language || "en";
367
+ const language = normalizeLanguage(data.language || this.opts.language || "en");
360
368
  if (!text && !isFinal) return;
361
369
  try {
362
370
  if (!this.speaking) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/inference/stt.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { type AudioFrame } from '@livekit/rtc-node';\nimport type { WebSocket } from 'ws';\nimport { APIError, APIStatusError } from '../_exceptions.js';\nimport { AudioByteStream } from '../audio.js';\nimport { log } from '../log.js';\nimport { createStreamChannel } from '../stream/stream_channel.js';\nimport {\n STT as BaseSTT,\n SpeechStream as BaseSpeechStream,\n type SpeechData,\n type SpeechEvent,\n SpeechEventType,\n} from '../stt/index.js';\nimport { type APIConnectOptions, DEFAULT_API_CONNECT_OPTIONS } from '../types.js';\nimport { type AudioBuffer, Event, Task, cancelAndWait, shortuuid, waitForAbort } from '../utils.js';\nimport { type TimedString, createTimedString } from '../voice/io.js';\nimport {\n type SttServerEvent,\n type SttTranscriptEvent,\n sttServerEventSchema,\n} from './api_protos.js';\nimport { type AnyString, connectWs, createAccessToken, getDefaultInferenceUrl } from './utils.js';\n\nexport type DeepgramModels =\n | 'deepgram/flux-general'\n | 'deepgram/nova-3'\n | 'deepgram/nova-3-medical'\n | 'deepgram/nova-2'\n | 'deepgram/nova-2-medical'\n | 'deepgram/nova-2-conversationalai'\n | 'deepgram/nova-2-phonecall';\n\nexport type CartesiaModels = 'cartesia/ink-whisper';\n\nexport type AssemblyaiModels =\n | 'assemblyai/universal-streaming'\n | 'assemblyai/universal-streaming-multilingual';\n\nexport type ElevenlabsSTTModels = 'elevenlabs/scribe_v2_realtime';\n\nexport interface CartesiaOptions {\n /** Minimum volume threshold. Default: not specified. */\n min_volume?: number;\n /** Maximum silence duration in seconds. Default: not specified. */\n max_silence_duration_secs?: number;\n}\n\nexport interface DeepgramOptions {\n /** Enable filler words. Default: true. */\n filler_words?: boolean;\n /** Enable interim results. Default: true. */\n interim_results?: boolean;\n /** Endpointing timeout in milliseconds. Default: 25. */\n endpointing?: number;\n /** Enable punctuation. Default: false. */\n punctuate?: boolean;\n /** Enable smart formatting. */\n smart_format?: boolean;\n /** Keywords with boost values. */\n keywords?: Array<[string, number]>;\n /** Key terms for recognition. */\n keyterms?: string[];\n /** Enable profanity filter. */\n profanity_filter?: boolean;\n /** Convert spoken numbers to numerals. */\n numerals?: boolean;\n /** Opt out of model improvement program. */\n mip_opt_out?: boolean;\n}\n\nexport interface AssemblyAIOptions {\n /** Enable turn formatting. Default: false. */\n format_turns?: boolean;\n /** End of turn confidence threshold. Default: 0.01. */\n end_of_turn_confidence_threshold?: number;\n /** Minimum silence duration in milliseconds when confident about end of turn. Default: 0. */\n min_end_of_turn_silence_when_confident?: number;\n /** Maximum turn silence in milliseconds. Default: not specified. */\n max_turn_silence?: number;\n /** Key terms prompt for recognition. Default: not specified. */\n keyterms_prompt?: string[];\n}\n\nexport type STTLanguages =\n | 'multi'\n | 'en'\n | 'de'\n | 'es'\n | 'fr'\n | 'ja'\n | 'pt'\n | 'zh'\n | 'hi'\n | AnyString;\n\ntype _STTModels = DeepgramModels | CartesiaModels | AssemblyaiModels | ElevenlabsSTTModels;\n\nexport type STTModels = _STTModels | 'auto' | AnyString;\n\nexport type ModelWithLanguage = `${_STTModels}:${STTLanguages}` | STTModels;\n\nexport type STTOptions<TModel extends STTModels> = TModel extends DeepgramModels\n ? DeepgramOptions\n : TModel extends CartesiaModels\n ? CartesiaOptions\n : TModel extends AssemblyaiModels\n ? AssemblyAIOptions\n : Record<string, unknown>;\n\n/** A fallback model with optional extra configuration. Extra fields are passed through to the provider. */\nexport interface STTFallbackModel {\n /** Model name (e.g. \"deepgram/nova-3\", \"assemblyai/universal-streaming\", \"cartesia/ink-whisper\"). */\n model: string;\n /** Extra configuration for the model. */\n extraKwargs?: Record<string, unknown>;\n}\n\nexport type STTFallbackModelType = STTFallbackModel | string;\n\n/** Parse a model string into [model, language]. Language is undefined if not specified. */\nexport function parseSTTModelString(model: string): [string, string | undefined] {\n const idx = model.lastIndexOf(':');\n if (idx !== -1) {\n return [model.slice(0, idx), model.slice(idx + 1)];\n }\n return [model, undefined];\n}\n\n/** Normalize a single or list of FallbackModelType into STTFallbackModel[]. */\nexport function normalizeSTTFallback(\n fallback: STTFallbackModelType | STTFallbackModelType[],\n): STTFallbackModel[] {\n const makeFallback = (model: STTFallbackModelType): STTFallbackModel => {\n if (typeof model === 'string') {\n const [name] = parseSTTModelString(model);\n return { model: name };\n }\n return model;\n };\n\n if (Array.isArray(fallback)) {\n return fallback.map(makeFallback);\n }\n return [makeFallback(fallback)];\n}\n\nexport type STTEncoding = 'pcm_s16le';\n\nconst DEFAULT_ENCODING: STTEncoding = 'pcm_s16le';\nconst DEFAULT_SAMPLE_RATE = 16000;\nconst DEFAULT_CANCEL_TIMEOUT = 5000;\n\nexport interface InferenceSTTOptions<TModel extends STTModels> {\n model?: TModel;\n language?: STTLanguages;\n encoding: STTEncoding;\n sampleRate: number;\n baseURL: string;\n apiKey: string;\n apiSecret: string;\n modelOptions: STTOptions<TModel>;\n fallback?: STTFallbackModel[];\n connOptions?: APIConnectOptions;\n}\n\n/**\n * Livekit Cloud Inference STT\n */\nexport class STT<TModel extends STTModels> extends BaseSTT {\n private opts: InferenceSTTOptions<TModel>;\n private streams: Set<SpeechStream<TModel>> = new Set();\n\n #logger = log();\n\n constructor(opts?: {\n model?: ModelWithLanguage;\n language?: STTLanguages;\n baseURL?: string;\n encoding?: STTEncoding;\n sampleRate?: number;\n apiKey?: string;\n apiSecret?: string;\n modelOptions?: STTOptions<TModel>;\n fallback?: STTFallbackModelType | STTFallbackModelType[];\n connOptions?: APIConnectOptions;\n }) {\n super({ streaming: true, interimResults: true, alignedTranscript: 'word' });\n\n const {\n model,\n language,\n baseURL,\n encoding = DEFAULT_ENCODING,\n sampleRate = DEFAULT_SAMPLE_RATE,\n apiKey,\n apiSecret,\n modelOptions = {} as STTOptions<TModel>,\n fallback,\n connOptions,\n } = opts || {};\n\n const lkBaseURL = baseURL || getDefaultInferenceUrl();\n const lkApiKey = apiKey || process.env.LIVEKIT_INFERENCE_API_KEY || process.env.LIVEKIT_API_KEY;\n if (!lkApiKey) {\n throw new Error('apiKey is required: pass apiKey or set LIVEKIT_API_KEY');\n }\n\n const lkApiSecret =\n apiSecret || process.env.LIVEKIT_INFERENCE_API_SECRET || process.env.LIVEKIT_API_SECRET;\n if (!lkApiSecret) {\n throw new Error('apiSecret is required: pass apiSecret or set LIVEKIT_API_SECRET');\n }\n\n // Parse language from model string if provided: \"provider/model:language\"\n let nextModel = model;\n let nextLanguage = language;\n if (typeof nextModel === 'string') {\n const idx = nextModel.lastIndexOf(':');\n if (idx !== -1) {\n const languageFromModel = nextModel.slice(idx + 1) as STTLanguages;\n if (nextLanguage && nextLanguage !== languageFromModel) {\n this.#logger.warn(\n '`language` is provided via both argument and model, using the one from the argument',\n { language: nextLanguage, model: nextModel },\n );\n } else {\n nextLanguage = languageFromModel;\n }\n nextModel = nextModel.slice(0, idx) as TModel;\n }\n }\n const normalizedFallback = fallback ? normalizeSTTFallback(fallback) : undefined;\n\n this.opts = {\n model: nextModel as TModel,\n language: nextLanguage,\n encoding,\n sampleRate,\n baseURL: lkBaseURL,\n apiKey: lkApiKey,\n apiSecret: lkApiSecret,\n modelOptions,\n fallback: normalizedFallback,\n connOptions: connOptions ?? DEFAULT_API_CONNECT_OPTIONS,\n };\n }\n\n get label(): string {\n return 'inference.STT';\n }\n\n get model(): string {\n return this.opts.model ?? 'auto';\n }\n\n get provider(): string {\n return 'livekit';\n }\n\n static fromModelString(modelString: string): STT<AnyString> {\n const [model, language] = parseSTTModelString(modelString);\n return new STT({ model, language });\n }\n\n protected async _recognize(_: AudioBuffer): Promise<SpeechEvent> {\n throw new Error('LiveKit STT does not support batch recognition, use stream() instead');\n }\n\n updateOptions(opts: Partial<Pick<InferenceSTTOptions<TModel>, 'model' | 'language'>>): void {\n this.opts = { ...this.opts, ...opts };\n\n for (const stream of this.streams) {\n stream.updateOptions(opts);\n }\n }\n\n stream(options?: {\n language?: STTLanguages | string;\n connOptions?: APIConnectOptions;\n }): SpeechStream<TModel> {\n const { language, connOptions = this.opts.connOptions ?? DEFAULT_API_CONNECT_OPTIONS } =\n options || {};\n const streamOpts = {\n ...this.opts,\n language: language ?? this.opts.language,\n } as InferenceSTTOptions<TModel>;\n\n const stream = new SpeechStream(this, streamOpts, connOptions);\n this.streams.add(stream);\n\n return stream;\n }\n\n async connectWs(timeout: number): Promise<WebSocket> {\n const params = {\n settings: {\n sample_rate: String(this.opts.sampleRate),\n encoding: this.opts.encoding,\n extra: this.opts.modelOptions,\n },\n } as Record<string, unknown>;\n\n if (this.opts.model && this.opts.model !== 'auto') {\n params.model = this.opts.model;\n }\n\n if (this.opts.language) {\n (params.settings as Record<string, unknown>).language = this.opts.language;\n }\n\n if (this.opts.fallback?.length) {\n params.fallback = {\n models: this.opts.fallback.map((m) => ({\n model: m.model,\n extra: m.extraKwargs ?? {},\n })),\n };\n }\n\n if (this.opts.connOptions) {\n params.connection = {\n timeout: this.opts.connOptions.timeoutMs / 1000,\n retries: this.opts.connOptions.maxRetry,\n };\n }\n\n let baseURL = this.opts.baseURL;\n if (baseURL.startsWith('http://') || baseURL.startsWith('https://')) {\n baseURL = baseURL.replace('http', 'ws');\n }\n\n const token = await createAccessToken(this.opts.apiKey, this.opts.apiSecret);\n const url = `${baseURL}/stt`;\n const headers = { Authorization: `Bearer ${token}` } as Record<string, string>;\n\n const socket = await connectWs(url, headers, timeout);\n const msg = { ...params, type: 'session.create' };\n socket.send(JSON.stringify(msg));\n\n return socket;\n }\n}\n\nexport class SpeechStream<TModel extends STTModels> extends BaseSpeechStream {\n private opts: InferenceSTTOptions<TModel>;\n private requestId = shortuuid('stt_request_');\n private speaking = false;\n private speechDuration = 0;\n private reconnectEvent = new Event();\n private stt: STT<TModel>;\n private connOptions: APIConnectOptions;\n\n #logger = log();\n\n constructor(\n sttImpl: STT<TModel>,\n opts: InferenceSTTOptions<TModel>,\n connOptions: APIConnectOptions,\n ) {\n super(sttImpl, opts.sampleRate, connOptions);\n this.opts = opts;\n this.stt = sttImpl;\n this.connOptions = connOptions;\n }\n\n get label(): string {\n return 'inference.SpeechStream';\n }\n\n updateOptions(opts: Partial<Pick<InferenceSTTOptions<TModel>, 'model' | 'language'>>): void {\n this.opts = { ...this.opts, ...opts };\n this.reconnectEvent.set();\n }\n\n protected async run(): Promise<void> {\n while (true) {\n // Create fresh resources for each connection attempt\n let ws: WebSocket | null = null;\n let closing = false;\n let finalReceived = false;\n\n const eventChannel = createStreamChannel<SttServerEvent>();\n\n const resourceCleanup = () => {\n if (closing) return;\n closing = true;\n eventChannel.close();\n ws?.removeAllListeners();\n ws?.close();\n };\n\n const createWsListener = async (ws: WebSocket, signal: AbortSignal) => {\n return new Promise<void>((resolve, reject) => {\n const onAbort = () => {\n resourceCleanup();\n reject(new Error('WebSocket connection aborted'));\n };\n\n signal.addEventListener('abort', onAbort, { once: true });\n\n ws.on('message', (data) => {\n const json = JSON.parse(data.toString()) as SttServerEvent;\n eventChannel.write(json);\n });\n\n ws.on('error', (e) => {\n this.#logger.error({ error: e }, 'WebSocket error');\n resourceCleanup();\n reject(e);\n });\n\n ws.on('close', (code: number) => {\n resourceCleanup();\n\n if (!closing) return this.#logger.error('WebSocket closed unexpectedly');\n if (finalReceived) return resolve();\n\n reject(\n new APIStatusError({\n message: 'LiveKit STT connection closed unexpectedly',\n options: { statusCode: code },\n }),\n );\n });\n });\n };\n\n const send = async (socket: WebSocket, signal: AbortSignal) => {\n const audioStream = new AudioByteStream(\n this.opts.sampleRate,\n 1,\n Math.floor(this.opts.sampleRate / 20), // 50ms\n );\n\n // Create abort promise once to avoid memory leak\n const abortPromise = new Promise<never>((_, reject) => {\n if (signal.aborted) {\n return reject(new Error('Send aborted'));\n }\n const onAbort = () => reject(new Error('Send aborted'));\n signal.addEventListener('abort', onAbort, { once: true });\n });\n\n // Manual iteration to support cancellation\n const iterator = this.input[Symbol.asyncIterator]();\n try {\n while (true) {\n const result = await Promise.race([iterator.next(), abortPromise]);\n\n if (result.done) break;\n const ev = result.value;\n\n let frames: AudioFrame[];\n if (ev === SpeechStream.FLUSH_SENTINEL) {\n frames = audioStream.flush();\n } else {\n const frame = ev as AudioFrame;\n frames = audioStream.write(new Int16Array(frame.data).buffer);\n }\n\n for (const frame of frames) {\n this.speechDuration += frame.samplesPerChannel / frame.sampleRate;\n const base64 = Buffer.from(frame.data.buffer).toString('base64');\n const msg = { type: 'input_audio', audio: base64 };\n socket.send(JSON.stringify(msg));\n }\n }\n\n closing = true;\n socket.send(JSON.stringify({ type: 'session.finalize' }));\n } catch (e) {\n if ((e as Error).message === 'Send aborted') {\n // Expected abort, don't log\n return;\n }\n throw e;\n }\n };\n\n const recv = async (signal: AbortSignal) => {\n const serverEventStream = eventChannel.stream();\n const reader = serverEventStream.getReader();\n\n try {\n while (!this.closed && !signal.aborted) {\n const result = await reader.read();\n if (signal.aborted) return;\n if (result.done) return;\n\n // Parse and validate with Zod schema\n const parseResult = await sttServerEventSchema.safeParseAsync(result.value);\n if (!parseResult.success) {\n this.#logger.warn(\n { error: parseResult.error, rawData: result.value },\n 'Failed to parse STT server event',\n );\n continue;\n }\n\n const event: SttServerEvent = parseResult.data;\n\n switch (event.type) {\n case 'session.created':\n case 'session.finalized':\n break;\n case 'session.closed':\n finalReceived = true;\n resourceCleanup();\n break;\n case 'interim_transcript':\n this.processTranscript(event, false);\n break;\n case 'final_transcript':\n this.processTranscript(event, true);\n break;\n case 'error':\n this.#logger.error({ error: event }, 'Received error from LiveKit STT');\n resourceCleanup();\n throw new APIError(`LiveKit STT returned error: ${JSON.stringify(event)}`);\n }\n }\n } finally {\n reader.releaseLock();\n try {\n await serverEventStream.cancel();\n } catch (e) {\n this.#logger.debug('Error cancelling serverEventStream (may already be cancelled):', e);\n }\n }\n };\n\n try {\n ws = await this.stt.connectWs(this.connOptions.timeoutMs);\n\n const controller = this.abortController; // Use base class abortController for proper cancellation\n const sendTask = Task.from(({ signal }) => send(ws!, signal), controller);\n const wsListenerTask = Task.from(({ signal }) => createWsListener(ws!, signal), controller);\n const recvTask = Task.from(({ signal }) => recv(signal), controller);\n const waitReconnectTask = Task.from(\n ({ signal }) => Promise.race([this.reconnectEvent.wait(), waitForAbort(signal)]),\n controller,\n );\n\n try {\n await Promise.race([\n Promise.all([sendTask.result, wsListenerTask.result, recvTask.result]),\n waitReconnectTask.result,\n ]);\n\n // If reconnect didn't trigger, tasks finished - exit loop\n if (!waitReconnectTask.done) break;\n\n // Reconnect triggered - clear event and continue loop\n this.reconnectEvent.clear();\n } finally {\n // Cancel all tasks to ensure cleanup\n await cancelAndWait(\n [sendTask, wsListenerTask, recvTask, waitReconnectTask],\n DEFAULT_CANCEL_TIMEOUT,\n );\n resourceCleanup();\n }\n } finally {\n // Ensure cleanup even if connectWs throws\n resourceCleanup();\n }\n }\n }\n\n private processTranscript(data: SttTranscriptEvent, isFinal: boolean) {\n // Check if queue is closed to avoid race condition during disconnect\n if (this.queue.closed) return;\n\n const requestId = data.session_id || this.requestId;\n const text = data.transcript;\n const language = data.language || this.opts.language || 'en';\n\n if (!text && !isFinal) return;\n\n try {\n // We'll have a more accurate way of detecting when speech started when we have VAD\n if (!this.speaking) {\n this.speaking = true;\n this.queue.put({ type: SpeechEventType.START_OF_SPEECH });\n }\n\n const speechData: SpeechData = {\n language,\n startTime: this.startTimeOffset + data.start,\n endTime: this.startTimeOffset + data.start + data.duration,\n confidence: data.confidence,\n text,\n words: data.words.map(\n (word): TimedString =>\n createTimedString({\n text: word.word,\n startTime: word.start + this.startTimeOffset,\n endTime: word.end + this.startTimeOffset,\n startTimeOffset: this.startTimeOffset,\n confidence: word.confidence,\n }),\n ),\n };\n\n if (isFinal) {\n if (this.speechDuration > 0) {\n this.queue.put({\n type: SpeechEventType.RECOGNITION_USAGE,\n requestId,\n recognitionUsage: { audioDuration: this.speechDuration },\n });\n this.speechDuration = 0;\n }\n\n this.queue.put({\n type: SpeechEventType.FINAL_TRANSCRIPT,\n requestId,\n alternatives: [speechData],\n });\n\n if (this.speaking) {\n this.speaking = false;\n this.queue.put({ type: SpeechEventType.END_OF_SPEECH });\n }\n } else {\n this.queue.put({\n type: SpeechEventType.INTERIM_TRANSCRIPT,\n requestId,\n alternatives: [speechData],\n });\n }\n } catch (e) {\n if (e instanceof Error && e.message.includes('Queue is closed')) {\n // Expected behavior on disconnect, log as warning\n this.#logger.warn(\n { err: e },\n 'Queue closed during transcript processing (expected during disconnect)',\n );\n } else {\n this.#logger.error({ err: e }, 'Error putting transcript to queue');\n }\n }\n }\n}\n"],"mappings":"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","names":["ws"]}
1
+ {"version":3,"sources":["../../src/inference/stt.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { type AudioFrame } from '@livekit/rtc-node';\nimport type { WebSocket } from 'ws';\nimport { APIError, APIStatusError } from '../_exceptions.js';\nimport { AudioByteStream } from '../audio.js';\nimport { type LanguageCode, areLanguagesEquivalent, normalizeLanguage } from '../language.js';\nimport { log } from '../log.js';\nimport { createStreamChannel } from '../stream/stream_channel.js';\nimport {\n STT as BaseSTT,\n SpeechStream as BaseSpeechStream,\n type SpeechData,\n type SpeechEvent,\n SpeechEventType,\n} from '../stt/index.js';\nimport { type APIConnectOptions, DEFAULT_API_CONNECT_OPTIONS } from '../types.js';\nimport { type AudioBuffer, Event, Task, cancelAndWait, shortuuid, waitForAbort } from '../utils.js';\nimport { type TimedString, createTimedString } from '../voice/io.js';\nimport {\n type SttServerEvent,\n type SttTranscriptEvent,\n sttServerEventSchema,\n} from './api_protos.js';\nimport { type AnyString, connectWs, createAccessToken, getDefaultInferenceUrl } from './utils.js';\n\nexport type DeepgramModels =\n | 'deepgram/flux-general'\n | 'deepgram/nova-3'\n | 'deepgram/nova-3-medical'\n | 'deepgram/nova-2'\n | 'deepgram/nova-2-medical'\n | 'deepgram/nova-2-conversationalai'\n | 'deepgram/nova-2-phonecall';\n\nexport type CartesiaModels = 'cartesia/ink-whisper';\n\nexport type AssemblyaiModels =\n | 'assemblyai/universal-streaming'\n | 'assemblyai/universal-streaming-multilingual';\n\nexport type ElevenlabsSTTModels = 'elevenlabs/scribe_v2_realtime';\n\nexport interface CartesiaOptions {\n /** Minimum volume threshold. Default: not specified. */\n min_volume?: number;\n /** Maximum silence duration in seconds. Default: not specified. */\n max_silence_duration_secs?: number;\n}\n\nexport interface DeepgramOptions {\n /** Enable filler words. Default: true. */\n filler_words?: boolean;\n /** Enable interim results. Default: true. */\n interim_results?: boolean;\n /** Endpointing timeout in milliseconds. Default: 25. */\n endpointing?: number;\n /** Enable punctuation. Default: false. */\n punctuate?: boolean;\n /** Enable smart formatting. */\n smart_format?: boolean;\n /** Keywords with boost values. */\n keywords?: Array<[string, number]>;\n /** Key terms for recognition. */\n keyterms?: string[];\n /** Enable profanity filter. */\n profanity_filter?: boolean;\n /** Convert spoken numbers to numerals. */\n numerals?: boolean;\n /** Opt out of model improvement program. */\n mip_opt_out?: boolean;\n}\n\nexport interface AssemblyAIOptions {\n /** Enable turn formatting. Default: false. */\n format_turns?: boolean;\n /** End of turn confidence threshold. Default: 0.01. */\n end_of_turn_confidence_threshold?: number;\n /** Minimum silence duration in milliseconds when confident about end of turn. Default: 0. */\n min_end_of_turn_silence_when_confident?: number;\n /** Maximum turn silence in milliseconds. Default: not specified. */\n max_turn_silence?: number;\n /** Key terms prompt for recognition. Default: not specified. */\n keyterms_prompt?: string[];\n}\n\nexport type STTLanguages =\n | 'multi'\n | 'en'\n | 'de'\n | 'es'\n | 'fr'\n | 'ja'\n | 'pt'\n | 'zh'\n | 'hi'\n | AnyString;\n\ntype _STTModels = DeepgramModels | CartesiaModels | AssemblyaiModels | ElevenlabsSTTModels;\n\nexport type STTModels = _STTModels | 'auto' | AnyString;\n\nexport type ModelWithLanguage = `${_STTModels}:${STTLanguages}` | STTModels;\n\nexport type STTOptions<TModel extends STTModels> = TModel extends DeepgramModels\n ? DeepgramOptions\n : TModel extends CartesiaModels\n ? CartesiaOptions\n : TModel extends AssemblyaiModels\n ? AssemblyAIOptions\n : Record<string, unknown>;\n\n/** A fallback model with optional extra configuration. Extra fields are passed through to the provider. */\nexport interface STTFallbackModel {\n /** Model name (e.g. \"deepgram/nova-3\", \"assemblyai/universal-streaming\", \"cartesia/ink-whisper\"). */\n model: string;\n /** Extra configuration for the model. */\n extraKwargs?: Record<string, unknown>;\n}\n\nexport type STTFallbackModelType = STTFallbackModel | string;\n\n/** Parse a model string into [model, language]. Language is undefined if not specified. */\nexport function parseSTTModelString(model: string): [string, LanguageCode | undefined] {\n const idx = model.lastIndexOf(':');\n if (idx !== -1) {\n return [model.slice(0, idx), normalizeLanguage(model.slice(idx + 1))];\n }\n return [model, undefined];\n}\n\n/** Normalize a single or list of FallbackModelType into STTFallbackModel[]. */\nexport function normalizeSTTFallback(\n fallback: STTFallbackModelType | STTFallbackModelType[],\n): STTFallbackModel[] {\n const makeFallback = (model: STTFallbackModelType): STTFallbackModel => {\n if (typeof model === 'string') {\n const [name] = parseSTTModelString(model);\n return { model: name };\n }\n return model;\n };\n\n if (Array.isArray(fallback)) {\n return fallback.map(makeFallback);\n }\n return [makeFallback(fallback)];\n}\n\nexport type STTEncoding = 'pcm_s16le';\n\nconst DEFAULT_ENCODING: STTEncoding = 'pcm_s16le';\nconst DEFAULT_SAMPLE_RATE = 16000;\nconst DEFAULT_CANCEL_TIMEOUT = 5000;\n\nexport interface InferenceSTTOptions<TModel extends STTModels> {\n model?: TModel;\n language?: LanguageCode;\n encoding: STTEncoding;\n sampleRate: number;\n baseURL: string;\n apiKey: string;\n apiSecret: string;\n modelOptions: STTOptions<TModel>;\n fallback?: STTFallbackModel[];\n connOptions?: APIConnectOptions;\n}\n\n/**\n * Livekit Cloud Inference STT\n */\nexport class STT<TModel extends STTModels> extends BaseSTT {\n private opts: InferenceSTTOptions<TModel>;\n private streams: Set<SpeechStream<TModel>> = new Set();\n\n #logger = log();\n\n constructor(opts?: {\n model?: ModelWithLanguage;\n language?: STTLanguages;\n baseURL?: string;\n encoding?: STTEncoding;\n sampleRate?: number;\n apiKey?: string;\n apiSecret?: string;\n modelOptions?: STTOptions<TModel>;\n fallback?: STTFallbackModelType | STTFallbackModelType[];\n connOptions?: APIConnectOptions;\n }) {\n super({ streaming: true, interimResults: true, alignedTranscript: 'word' });\n\n const {\n model,\n language,\n baseURL,\n encoding = DEFAULT_ENCODING,\n sampleRate = DEFAULT_SAMPLE_RATE,\n apiKey,\n apiSecret,\n modelOptions = {} as STTOptions<TModel>,\n fallback,\n connOptions,\n } = opts || {};\n\n const lkBaseURL = baseURL || getDefaultInferenceUrl();\n const lkApiKey = apiKey || process.env.LIVEKIT_INFERENCE_API_KEY || process.env.LIVEKIT_API_KEY;\n if (!lkApiKey) {\n throw new Error('apiKey is required: pass apiKey or set LIVEKIT_API_KEY');\n }\n\n const lkApiSecret =\n apiSecret || process.env.LIVEKIT_INFERENCE_API_SECRET || process.env.LIVEKIT_API_SECRET;\n if (!lkApiSecret) {\n throw new Error('apiSecret is required: pass apiSecret or set LIVEKIT_API_SECRET');\n }\n\n // Parse language from model string if provided: \"provider/model:language\"\n let nextModel = model;\n let nextLanguage = language;\n if (typeof nextModel === 'string') {\n const [parsedModel, parsedLanguage] = parseSTTModelString(nextModel);\n if (parsedLanguage !== undefined) {\n if (nextLanguage && !areLanguagesEquivalent(nextLanguage, parsedLanguage)) {\n this.#logger.warn(\n '`language` is provided via both argument and model, using the one from the argument',\n { language: nextLanguage, model: nextModel },\n );\n } else {\n nextLanguage = parsedLanguage as STTLanguages;\n }\n nextModel = parsedModel as TModel;\n }\n }\n const normalizedFallback = fallback ? normalizeSTTFallback(fallback) : undefined;\n\n this.opts = {\n model: nextModel as TModel,\n language: nextLanguage ? normalizeLanguage(nextLanguage) : undefined,\n encoding,\n sampleRate,\n baseURL: lkBaseURL,\n apiKey: lkApiKey,\n apiSecret: lkApiSecret,\n modelOptions,\n fallback: normalizedFallback,\n connOptions: connOptions ?? DEFAULT_API_CONNECT_OPTIONS,\n };\n }\n\n get label(): string {\n return 'inference.STT';\n }\n\n get model(): string {\n return this.opts.model ?? 'auto';\n }\n\n get provider(): string {\n return 'livekit';\n }\n\n static fromModelString(modelString: string): STT<AnyString> {\n const [model, language] = parseSTTModelString(modelString);\n return new STT({ model, language });\n }\n\n protected async _recognize(_: AudioBuffer): Promise<SpeechEvent> {\n throw new Error('LiveKit STT does not support batch recognition, use stream() instead');\n }\n\n updateOptions(opts: Partial<Pick<InferenceSTTOptions<TModel>, 'model' | 'language'>>): void {\n this.opts = {\n ...this.opts,\n ...opts,\n language: opts.language !== undefined ? normalizeLanguage(opts.language) : this.opts.language,\n };\n\n for (const stream of this.streams) {\n stream.updateOptions(opts);\n }\n }\n\n stream(options?: {\n language?: STTLanguages | string;\n connOptions?: APIConnectOptions;\n }): SpeechStream<TModel> {\n const { language, connOptions = this.opts.connOptions ?? DEFAULT_API_CONNECT_OPTIONS } =\n options || {};\n const streamOpts = {\n ...this.opts,\n language: language !== undefined ? normalizeLanguage(language) : this.opts.language,\n } as InferenceSTTOptions<TModel>;\n\n const stream = new SpeechStream(this, streamOpts, connOptions);\n this.streams.add(stream);\n\n return stream;\n }\n\n async connectWs(timeout: number): Promise<WebSocket> {\n const params = {\n settings: {\n sample_rate: String(this.opts.sampleRate),\n encoding: this.opts.encoding,\n extra: this.opts.modelOptions,\n },\n } as Record<string, unknown>;\n\n if (this.opts.model && this.opts.model !== 'auto') {\n params.model = this.opts.model;\n }\n\n if (this.opts.language) {\n (params.settings as Record<string, unknown>).language = this.opts.language;\n }\n\n if (this.opts.fallback?.length) {\n params.fallback = {\n models: this.opts.fallback.map((m) => ({\n model: m.model,\n extra: m.extraKwargs ?? {},\n })),\n };\n }\n\n if (this.opts.connOptions) {\n params.connection = {\n timeout: this.opts.connOptions.timeoutMs / 1000,\n retries: this.opts.connOptions.maxRetry,\n };\n }\n\n let baseURL = this.opts.baseURL;\n if (baseURL.startsWith('http://') || baseURL.startsWith('https://')) {\n baseURL = baseURL.replace('http', 'ws');\n }\n\n const token = await createAccessToken(this.opts.apiKey, this.opts.apiSecret);\n const url = `${baseURL}/stt`;\n const headers = { Authorization: `Bearer ${token}` } as Record<string, string>;\n\n const socket = await connectWs(url, headers, timeout);\n const msg = { ...params, type: 'session.create' };\n socket.send(JSON.stringify(msg));\n\n return socket;\n }\n}\n\nexport class SpeechStream<TModel extends STTModels> extends BaseSpeechStream {\n private opts: InferenceSTTOptions<TModel>;\n private requestId = shortuuid('stt_request_');\n private speaking = false;\n private speechDuration = 0;\n private reconnectEvent = new Event();\n private stt: STT<TModel>;\n private connOptions: APIConnectOptions;\n\n #logger = log();\n\n constructor(\n sttImpl: STT<TModel>,\n opts: InferenceSTTOptions<TModel>,\n connOptions: APIConnectOptions,\n ) {\n super(sttImpl, opts.sampleRate, connOptions);\n this.opts = opts;\n this.stt = sttImpl;\n this.connOptions = connOptions;\n }\n\n get label(): string {\n return 'inference.SpeechStream';\n }\n\n updateOptions(opts: Partial<Pick<InferenceSTTOptions<TModel>, 'model' | 'language'>>): void {\n this.opts = {\n ...this.opts,\n ...opts,\n language: opts.language !== undefined ? normalizeLanguage(opts.language) : this.opts.language,\n };\n this.reconnectEvent.set();\n }\n\n protected async run(): Promise<void> {\n while (true) {\n // Create fresh resources for each connection attempt\n let ws: WebSocket | null = null;\n let closing = false;\n let finalReceived = false;\n\n const eventChannel = createStreamChannel<SttServerEvent>();\n\n const resourceCleanup = () => {\n if (closing) return;\n closing = true;\n eventChannel.close();\n ws?.removeAllListeners();\n ws?.close();\n };\n\n const createWsListener = async (ws: WebSocket, signal: AbortSignal) => {\n return new Promise<void>((resolve, reject) => {\n const onAbort = () => {\n resourceCleanup();\n reject(new Error('WebSocket connection aborted'));\n };\n\n signal.addEventListener('abort', onAbort, { once: true });\n\n ws.on('message', (data) => {\n const json = JSON.parse(data.toString()) as SttServerEvent;\n eventChannel.write(json);\n });\n\n ws.on('error', (e) => {\n this.#logger.error({ error: e }, 'WebSocket error');\n resourceCleanup();\n reject(e);\n });\n\n ws.on('close', (code: number) => {\n resourceCleanup();\n\n if (!closing) return this.#logger.error('WebSocket closed unexpectedly');\n if (finalReceived) return resolve();\n\n reject(\n new APIStatusError({\n message: 'LiveKit STT connection closed unexpectedly',\n options: { statusCode: code },\n }),\n );\n });\n });\n };\n\n const send = async (socket: WebSocket, signal: AbortSignal) => {\n const audioStream = new AudioByteStream(\n this.opts.sampleRate,\n 1,\n Math.floor(this.opts.sampleRate / 20), // 50ms\n );\n\n // Create abort promise once to avoid memory leak\n const abortPromise = new Promise<never>((_, reject) => {\n if (signal.aborted) {\n return reject(new Error('Send aborted'));\n }\n const onAbort = () => reject(new Error('Send aborted'));\n signal.addEventListener('abort', onAbort, { once: true });\n });\n\n // Manual iteration to support cancellation\n const iterator = this.input[Symbol.asyncIterator]();\n try {\n while (true) {\n const result = await Promise.race([iterator.next(), abortPromise]);\n\n if (result.done) break;\n const ev = result.value;\n\n let frames: AudioFrame[];\n if (ev === SpeechStream.FLUSH_SENTINEL) {\n frames = audioStream.flush();\n } else {\n const frame = ev as AudioFrame;\n frames = audioStream.write(new Int16Array(frame.data).buffer);\n }\n\n for (const frame of frames) {\n this.speechDuration += frame.samplesPerChannel / frame.sampleRate;\n const base64 = Buffer.from(frame.data.buffer).toString('base64');\n const msg = { type: 'input_audio', audio: base64 };\n socket.send(JSON.stringify(msg));\n }\n }\n\n closing = true;\n socket.send(JSON.stringify({ type: 'session.finalize' }));\n } catch (e) {\n if ((e as Error).message === 'Send aborted') {\n // Expected abort, don't log\n return;\n }\n throw e;\n }\n };\n\n const recv = async (signal: AbortSignal) => {\n const serverEventStream = eventChannel.stream();\n const reader = serverEventStream.getReader();\n\n try {\n while (!this.closed && !signal.aborted) {\n const result = await reader.read();\n if (signal.aborted) return;\n if (result.done) return;\n\n // Parse and validate with Zod schema\n const parseResult = await sttServerEventSchema.safeParseAsync(result.value);\n if (!parseResult.success) {\n this.#logger.warn(\n { error: parseResult.error, rawData: result.value },\n 'Failed to parse STT server event',\n );\n continue;\n }\n\n const event: SttServerEvent = parseResult.data;\n\n switch (event.type) {\n case 'session.created':\n case 'session.finalized':\n break;\n case 'session.closed':\n finalReceived = true;\n resourceCleanup();\n break;\n case 'interim_transcript':\n this.processTranscript(event, false);\n break;\n case 'final_transcript':\n this.processTranscript(event, true);\n break;\n case 'error':\n this.#logger.error({ error: event }, 'Received error from LiveKit STT');\n resourceCleanup();\n throw new APIError(`LiveKit STT returned error: ${JSON.stringify(event)}`);\n }\n }\n } finally {\n reader.releaseLock();\n try {\n await serverEventStream.cancel();\n } catch (e) {\n this.#logger.debug('Error cancelling serverEventStream (may already be cancelled):', e);\n }\n }\n };\n\n try {\n ws = await this.stt.connectWs(this.connOptions.timeoutMs);\n\n const controller = this.abortController; // Use base class abortController for proper cancellation\n const sendTask = Task.from(({ signal }) => send(ws!, signal), controller);\n const wsListenerTask = Task.from(({ signal }) => createWsListener(ws!, signal), controller);\n const recvTask = Task.from(({ signal }) => recv(signal), controller);\n const waitReconnectTask = Task.from(\n ({ signal }) => Promise.race([this.reconnectEvent.wait(), waitForAbort(signal)]),\n controller,\n );\n\n try {\n await Promise.race([\n Promise.all([sendTask.result, wsListenerTask.result, recvTask.result]),\n waitReconnectTask.result,\n ]);\n\n // If reconnect didn't trigger, tasks finished - exit loop\n if (!waitReconnectTask.done) break;\n\n // Reconnect triggered - clear event and continue loop\n this.reconnectEvent.clear();\n } finally {\n // Cancel all tasks to ensure cleanup\n await cancelAndWait(\n [sendTask, wsListenerTask, recvTask, waitReconnectTask],\n DEFAULT_CANCEL_TIMEOUT,\n );\n resourceCleanup();\n }\n } finally {\n // Ensure cleanup even if connectWs throws\n resourceCleanup();\n }\n }\n }\n\n private processTranscript(data: SttTranscriptEvent, isFinal: boolean) {\n // Check if queue is closed to avoid race condition during disconnect\n if (this.queue.closed) return;\n\n const requestId = data.session_id || this.requestId;\n const text = data.transcript;\n const language = normalizeLanguage(data.language || this.opts.language || 'en');\n\n if (!text && !isFinal) return;\n\n try {\n // We'll have a more accurate way of detecting when speech started when we have VAD\n if (!this.speaking) {\n this.speaking = true;\n this.queue.put({ type: SpeechEventType.START_OF_SPEECH });\n }\n\n const speechData: SpeechData = {\n language,\n startTime: this.startTimeOffset + data.start,\n endTime: this.startTimeOffset + data.start + data.duration,\n confidence: data.confidence,\n text,\n words: data.words.map(\n (word): TimedString =>\n createTimedString({\n text: word.word,\n startTime: word.start + this.startTimeOffset,\n endTime: word.end + this.startTimeOffset,\n startTimeOffset: this.startTimeOffset,\n confidence: word.confidence,\n }),\n ),\n };\n\n if (isFinal) {\n if (this.speechDuration > 0) {\n this.queue.put({\n type: SpeechEventType.RECOGNITION_USAGE,\n requestId,\n recognitionUsage: { audioDuration: this.speechDuration },\n });\n this.speechDuration = 0;\n }\n\n this.queue.put({\n type: SpeechEventType.FINAL_TRANSCRIPT,\n requestId,\n alternatives: [speechData],\n });\n\n if (this.speaking) {\n this.speaking = false;\n this.queue.put({ type: SpeechEventType.END_OF_SPEECH });\n }\n } else {\n this.queue.put({\n type: SpeechEventType.INTERIM_TRANSCRIPT,\n requestId,\n alternatives: [speechData],\n });\n }\n } catch (e) {\n if (e instanceof Error && e.message.includes('Queue is closed')) {\n // Expected behavior on disconnect, log as warning\n this.#logger.warn(\n { err: e },\n 'Queue closed during transcript processing (expected during disconnect)',\n );\n } else {\n this.#logger.error({ err: e }, 'Error putting transcript to queue');\n }\n }\n }\n}\n"],"mappings":"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","names":["ws"]}
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  var import_vitest = require("vitest");
3
+ var import_language = require("../language.cjs");
3
4
  var import_log = require("../log.cjs");
4
5
  var import_types = require("../types.cjs");
5
6
  var import_stt = require("./stt.cjs");
@@ -26,6 +27,11 @@ function makeStt(overrides = {}) {
26
27
  (0, import_vitest.expect)(model).toBe("deepgram");
27
28
  (0, import_vitest.expect)(language).toBe("en");
28
29
  });
30
+ (0, import_vitest.it)("normalizes language suffixes", () => {
31
+ const [model, language] = (0, import_stt.parseSTTModelString)("deepgram:english");
32
+ (0, import_vitest.expect)(model).toBe("deepgram");
33
+ (0, import_vitest.expect)(language).toBe("en");
34
+ });
29
35
  (0, import_vitest.it)("provider/model format without language", () => {
30
36
  const [model, language] = (0, import_stt.parseSTTModelString)("deepgram/nova-3");
31
37
  (0, import_vitest.expect)(model).toBe("deepgram/nova-3");
@@ -128,6 +134,14 @@ function makeStt(overrides = {}) {
128
134
  });
129
135
  });
130
136
  (0, import_vitest.describe)("STT constructor fallback and connOptions", () => {
137
+ (0, import_vitest.it)("normalizes language in constructor and model string", () => {
138
+ const stt = makeStt({ model: "deepgram/nova-3:english" });
139
+ (0, import_vitest.expect)(stt["opts"].language).toBe("en");
140
+ });
141
+ (0, import_vitest.it)("prefers explicit normalized language over model suffix", () => {
142
+ const stt = makeStt({ model: "deepgram/nova-3:english", language: "en_US" });
143
+ (0, import_vitest.expect)(stt["opts"].language).toBe((0, import_language.normalizeLanguage)("en_US"));
144
+ });
131
145
  (0, import_vitest.it)("fallback not given defaults to undefined", () => {
132
146
  const stt = makeStt();
133
147
  (0, import_vitest.expect)(stt["opts"].fallback).toBeUndefined();
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/inference/stt.test.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { beforeAll, describe, expect, it } from 'vitest';\nimport { initializeLogger } from '../log.js';\nimport { type APIConnectOptions, DEFAULT_API_CONNECT_OPTIONS } from '../types.js';\nimport { STT, type STTFallbackModel, normalizeSTTFallback, parseSTTModelString } from './stt.js';\n\nbeforeAll(() => {\n initializeLogger({ level: 'silent', pretty: false });\n});\n\n/** Helper to create STT with required credentials. */\nfunction makeStt(overrides: Record<string, unknown> = {}) {\n const defaults = {\n model: 'deepgram' as const,\n apiKey: 'test-key',\n apiSecret: 'test-secret',\n baseURL: 'https://example.livekit.cloud',\n };\n return new STT({ ...defaults, ...overrides });\n}\n\ndescribe('parseSTTModelString', () => {\n it('simple model without language', () => {\n const [model, language] = parseSTTModelString('deepgram');\n expect(model).toBe('deepgram');\n expect(language).toBeUndefined();\n });\n\n it('model with language suffix', () => {\n const [model, language] = parseSTTModelString('deepgram:en');\n expect(model).toBe('deepgram');\n expect(language).toBe('en');\n });\n\n it('provider/model format without language', () => {\n const [model, language] = parseSTTModelString('deepgram/nova-3');\n expect(model).toBe('deepgram/nova-3');\n expect(language).toBeUndefined();\n });\n\n it('provider/model format with language', () => {\n const [model, language] = parseSTTModelString('deepgram/nova-3:en');\n expect(model).toBe('deepgram/nova-3');\n expect(language).toBe('en');\n });\n\n it.each([\n ['cartesia/ink-whisper:de', 'cartesia/ink-whisper', 'de'],\n ['assemblyai:es', 'assemblyai', 'es'],\n ['deepgram/nova-2-medical:ja', 'deepgram/nova-2-medical', 'ja'],\n ['deepgram/nova-3:multi', 'deepgram/nova-3', 'multi'],\n ['cartesia:zh', 'cartesia', 'zh'],\n ])('various providers and languages: %s', (modelStr, expectedModel, expectedLang) => {\n const [model, language] = parseSTTModelString(modelStr);\n expect(model).toBe(expectedModel);\n expect(language).toBe(expectedLang);\n });\n\n it('auto model without language', () => {\n const [model, language] = parseSTTModelString('auto');\n expect(model).toBe('auto');\n expect(language).toBeUndefined();\n });\n\n it('auto model with language', () => {\n const [model, language] = parseSTTModelString('auto:pt');\n expect(model).toBe('auto');\n expect(language).toBe('pt');\n });\n});\n\ndescribe('normalizeSTTFallback', () => {\n it('single string model', () => {\n const result = normalizeSTTFallback('deepgram/nova-3');\n expect(result).toEqual([{ model: 'deepgram/nova-3' }]);\n });\n\n it('single FallbackModel dict', () => {\n const fallback: STTFallbackModel = { model: 'deepgram/nova-3' };\n const result = normalizeSTTFallback(fallback);\n expect(result).toEqual([{ model: 'deepgram/nova-3' }]);\n });\n\n it('list of string models', () => {\n const result = normalizeSTTFallback(['deepgram/nova-3', 'cartesia/ink-whisper']);\n expect(result).toEqual([{ model: 'deepgram/nova-3' }, { model: 'cartesia/ink-whisper' }]);\n });\n\n it('list of FallbackModel dicts', () => {\n const fallbacks: STTFallbackModel[] = [{ model: 'deepgram/nova-3' }, { model: 'assemblyai' }];\n const result = normalizeSTTFallback(fallbacks);\n expect(result).toEqual([{ model: 'deepgram/nova-3' }, { model: 'assemblyai' }]);\n });\n\n it('mixed list of strings and dicts', () => {\n const result = normalizeSTTFallback([\n 'deepgram/nova-3',\n { model: 'cartesia/ink-whisper' } as STTFallbackModel,\n 'assemblyai',\n ]);\n expect(result).toEqual([\n { model: 'deepgram/nova-3' },\n { model: 'cartesia/ink-whisper' },\n { model: 'assemblyai' },\n ]);\n });\n\n it('string with language suffix discards language', () => {\n const result = normalizeSTTFallback('deepgram/nova-3:en');\n expect(result).toEqual([{ model: 'deepgram/nova-3' }]);\n });\n\n it('FallbackModel with extraKwargs is preserved', () => {\n const fallback: STTFallbackModel = {\n model: 'deepgram/nova-3',\n extraKwargs: { keywords: [['livekit', 1.5]], punctuate: true },\n };\n const result = normalizeSTTFallback(fallback);\n expect(result).toEqual([\n {\n model: 'deepgram/nova-3',\n extraKwargs: { keywords: [['livekit', 1.5]], punctuate: true },\n },\n ]);\n });\n\n it('list with extraKwargs preserved', () => {\n const result = normalizeSTTFallback([\n { model: 'deepgram/nova-3', extraKwargs: { punctuate: true } } as STTFallbackModel,\n 'cartesia/ink-whisper',\n { model: 'assemblyai', extraKwargs: { format_turns: true } } as STTFallbackModel,\n ]);\n expect(result).toEqual([\n { model: 'deepgram/nova-3', extraKwargs: { punctuate: true } },\n { model: 'cartesia/ink-whisper' },\n { model: 'assemblyai', extraKwargs: { format_turns: true } },\n ]);\n });\n\n it('empty list returns empty list', () => {\n const result = normalizeSTTFallback([]);\n expect(result).toEqual([]);\n });\n\n it('multiple colons in model string splits on last', () => {\n const result = normalizeSTTFallback('some:model:part:fr');\n expect(result).toEqual([{ model: 'some:model:part' }]);\n });\n});\n\ndescribe('STT constructor fallback and connOptions', () => {\n it('fallback not given defaults to undefined', () => {\n const stt = makeStt();\n expect(stt['opts'].fallback).toBeUndefined();\n });\n\n it('fallback single string is normalized', () => {\n const stt = makeStt({ fallback: 'cartesia/ink-whisper' });\n expect(stt['opts'].fallback).toEqual([{ model: 'cartesia/ink-whisper' }]);\n });\n\n it('fallback list of strings is normalized', () => {\n const stt = makeStt({ fallback: ['deepgram/nova-3', 'assemblyai'] });\n expect(stt['opts'].fallback).toEqual([{ model: 'deepgram/nova-3' }, { model: 'assemblyai' }]);\n });\n\n it('fallback single FallbackModel is normalized to list', () => {\n const stt = makeStt({ fallback: { model: 'deepgram/nova-3' } });\n expect(stt['opts'].fallback).toEqual([{ model: 'deepgram/nova-3' }]);\n });\n\n it('fallback with extraKwargs is preserved', () => {\n const stt = makeStt({\n fallback: {\n model: 'deepgram/nova-3',\n extraKwargs: { punctuate: true, keywords: [['livekit', 1.5]] },\n },\n });\n expect(stt['opts'].fallback).toEqual([\n {\n model: 'deepgram/nova-3',\n extraKwargs: { punctuate: true, keywords: [['livekit', 1.5]] },\n },\n ]);\n });\n\n it('fallback mixed list is normalized', () => {\n const stt = makeStt({\n fallback: [\n 'deepgram/nova-3',\n { model: 'cartesia', extraKwargs: { min_volume: 0.5 } },\n 'assemblyai',\n ],\n });\n expect(stt['opts'].fallback).toEqual([\n { model: 'deepgram/nova-3' },\n { model: 'cartesia', extraKwargs: { min_volume: 0.5 } },\n { model: 'assemblyai' },\n ]);\n });\n\n it('fallback string with language discards language', () => {\n const stt = makeStt({ fallback: 'deepgram/nova-3:en' });\n expect(stt['opts'].fallback).toEqual([{ model: 'deepgram/nova-3' }]);\n });\n\n it('connOptions not given uses default', () => {\n const stt = makeStt();\n expect(stt['opts'].connOptions).toEqual(DEFAULT_API_CONNECT_OPTIONS);\n });\n\n it('connOptions custom timeout', () => {\n const custom: APIConnectOptions = { timeoutMs: 30000, maxRetry: 3, retryIntervalMs: 2000 };\n const stt = makeStt({ connOptions: custom });\n expect(stt['opts'].connOptions).toEqual(custom);\n expect(stt['opts'].connOptions!.timeoutMs).toBe(30000);\n });\n\n it('connOptions custom maxRetry', () => {\n const custom: APIConnectOptions = { timeoutMs: 10000, maxRetry: 5, retryIntervalMs: 2000 };\n const stt = makeStt({ connOptions: custom });\n expect(stt['opts'].connOptions).toEqual(custom);\n expect(stt['opts'].connOptions!.maxRetry).toBe(5);\n });\n\n it('connOptions full custom', () => {\n const custom: APIConnectOptions = { timeoutMs: 60000, maxRetry: 10, retryIntervalMs: 2000 };\n const stt = makeStt({ connOptions: custom });\n expect(stt['opts'].connOptions).toEqual(custom);\n expect(stt['opts'].connOptions!.timeoutMs).toBe(60000);\n expect(stt['opts'].connOptions!.maxRetry).toBe(10);\n expect(stt['opts'].connOptions!.retryIntervalMs).toBe(2000);\n });\n});\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/inference/stt.test.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { beforeAll, describe, expect, it } from 'vitest';\nimport { normalizeLanguage } from '../language.js';\nimport { initializeLogger } from '../log.js';\nimport { type APIConnectOptions, DEFAULT_API_CONNECT_OPTIONS } from '../types.js';\nimport { STT, type STTFallbackModel, normalizeSTTFallback, parseSTTModelString } from './stt.js';\n\nbeforeAll(() => {\n initializeLogger({ level: 'silent', pretty: false });\n});\n\n/** Helper to create STT with required credentials. */\nfunction makeStt(overrides: Record<string, unknown> = {}) {\n const defaults = {\n model: 'deepgram' as const,\n apiKey: 'test-key',\n apiSecret: 'test-secret',\n baseURL: 'https://example.livekit.cloud',\n };\n return new STT({ ...defaults, ...overrides });\n}\n\ndescribe('parseSTTModelString', () => {\n it('simple model without language', () => {\n const [model, language] = parseSTTModelString('deepgram');\n expect(model).toBe('deepgram');\n expect(language).toBeUndefined();\n });\n\n it('model with language suffix', () => {\n const [model, language] = parseSTTModelString('deepgram:en');\n expect(model).toBe('deepgram');\n expect(language).toBe('en');\n });\n\n it('normalizes language suffixes', () => {\n const [model, language] = parseSTTModelString('deepgram:english');\n expect(model).toBe('deepgram');\n expect(language).toBe('en');\n });\n\n it('provider/model format without language', () => {\n const [model, language] = parseSTTModelString('deepgram/nova-3');\n expect(model).toBe('deepgram/nova-3');\n expect(language).toBeUndefined();\n });\n\n it('provider/model format with language', () => {\n const [model, language] = parseSTTModelString('deepgram/nova-3:en');\n expect(model).toBe('deepgram/nova-3');\n expect(language).toBe('en');\n });\n\n it.each([\n ['cartesia/ink-whisper:de', 'cartesia/ink-whisper', 'de'],\n ['assemblyai:es', 'assemblyai', 'es'],\n ['deepgram/nova-2-medical:ja', 'deepgram/nova-2-medical', 'ja'],\n ['deepgram/nova-3:multi', 'deepgram/nova-3', 'multi'],\n ['cartesia:zh', 'cartesia', 'zh'],\n ])('various providers and languages: %s', (modelStr, expectedModel, expectedLang) => {\n const [model, language] = parseSTTModelString(modelStr);\n expect(model).toBe(expectedModel);\n expect(language).toBe(expectedLang);\n });\n\n it('auto model without language', () => {\n const [model, language] = parseSTTModelString('auto');\n expect(model).toBe('auto');\n expect(language).toBeUndefined();\n });\n\n it('auto model with language', () => {\n const [model, language] = parseSTTModelString('auto:pt');\n expect(model).toBe('auto');\n expect(language).toBe('pt');\n });\n});\n\ndescribe('normalizeSTTFallback', () => {\n it('single string model', () => {\n const result = normalizeSTTFallback('deepgram/nova-3');\n expect(result).toEqual([{ model: 'deepgram/nova-3' }]);\n });\n\n it('single FallbackModel dict', () => {\n const fallback: STTFallbackModel = { model: 'deepgram/nova-3' };\n const result = normalizeSTTFallback(fallback);\n expect(result).toEqual([{ model: 'deepgram/nova-3' }]);\n });\n\n it('list of string models', () => {\n const result = normalizeSTTFallback(['deepgram/nova-3', 'cartesia/ink-whisper']);\n expect(result).toEqual([{ model: 'deepgram/nova-3' }, { model: 'cartesia/ink-whisper' }]);\n });\n\n it('list of FallbackModel dicts', () => {\n const fallbacks: STTFallbackModel[] = [{ model: 'deepgram/nova-3' }, { model: 'assemblyai' }];\n const result = normalizeSTTFallback(fallbacks);\n expect(result).toEqual([{ model: 'deepgram/nova-3' }, { model: 'assemblyai' }]);\n });\n\n it('mixed list of strings and dicts', () => {\n const result = normalizeSTTFallback([\n 'deepgram/nova-3',\n { model: 'cartesia/ink-whisper' } as STTFallbackModel,\n 'assemblyai',\n ]);\n expect(result).toEqual([\n { model: 'deepgram/nova-3' },\n { model: 'cartesia/ink-whisper' },\n { model: 'assemblyai' },\n ]);\n });\n\n it('string with language suffix discards language', () => {\n const result = normalizeSTTFallback('deepgram/nova-3:en');\n expect(result).toEqual([{ model: 'deepgram/nova-3' }]);\n });\n\n it('FallbackModel with extraKwargs is preserved', () => {\n const fallback: STTFallbackModel = {\n model: 'deepgram/nova-3',\n extraKwargs: { keywords: [['livekit', 1.5]], punctuate: true },\n };\n const result = normalizeSTTFallback(fallback);\n expect(result).toEqual([\n {\n model: 'deepgram/nova-3',\n extraKwargs: { keywords: [['livekit', 1.5]], punctuate: true },\n },\n ]);\n });\n\n it('list with extraKwargs preserved', () => {\n const result = normalizeSTTFallback([\n { model: 'deepgram/nova-3', extraKwargs: { punctuate: true } } as STTFallbackModel,\n 'cartesia/ink-whisper',\n { model: 'assemblyai', extraKwargs: { format_turns: true } } as STTFallbackModel,\n ]);\n expect(result).toEqual([\n { model: 'deepgram/nova-3', extraKwargs: { punctuate: true } },\n { model: 'cartesia/ink-whisper' },\n { model: 'assemblyai', extraKwargs: { format_turns: true } },\n ]);\n });\n\n it('empty list returns empty list', () => {\n const result = normalizeSTTFallback([]);\n expect(result).toEqual([]);\n });\n\n it('multiple colons in model string splits on last', () => {\n const result = normalizeSTTFallback('some:model:part:fr');\n expect(result).toEqual([{ model: 'some:model:part' }]);\n });\n});\n\ndescribe('STT constructor fallback and connOptions', () => {\n it('normalizes language in constructor and model string', () => {\n const stt = makeStt({ model: 'deepgram/nova-3:english' });\n expect(stt['opts'].language).toBe('en');\n });\n\n it('prefers explicit normalized language over model suffix', () => {\n const stt = makeStt({ model: 'deepgram/nova-3:english', language: 'en_US' });\n expect(stt['opts'].language).toBe(normalizeLanguage('en_US'));\n });\n\n it('fallback not given defaults to undefined', () => {\n const stt = makeStt();\n expect(stt['opts'].fallback).toBeUndefined();\n });\n\n it('fallback single string is normalized', () => {\n const stt = makeStt({ fallback: 'cartesia/ink-whisper' });\n expect(stt['opts'].fallback).toEqual([{ model: 'cartesia/ink-whisper' }]);\n });\n\n it('fallback list of strings is normalized', () => {\n const stt = makeStt({ fallback: ['deepgram/nova-3', 'assemblyai'] });\n expect(stt['opts'].fallback).toEqual([{ model: 'deepgram/nova-3' }, { model: 'assemblyai' }]);\n });\n\n it('fallback single FallbackModel is normalized to list', () => {\n const stt = makeStt({ fallback: { model: 'deepgram/nova-3' } });\n expect(stt['opts'].fallback).toEqual([{ model: 'deepgram/nova-3' }]);\n });\n\n it('fallback with extraKwargs is preserved', () => {\n const stt = makeStt({\n fallback: {\n model: 'deepgram/nova-3',\n extraKwargs: { punctuate: true, keywords: [['livekit', 1.5]] },\n },\n });\n expect(stt['opts'].fallback).toEqual([\n {\n model: 'deepgram/nova-3',\n extraKwargs: { punctuate: true, keywords: [['livekit', 1.5]] },\n },\n ]);\n });\n\n it('fallback mixed list is normalized', () => {\n const stt = makeStt({\n fallback: [\n 'deepgram/nova-3',\n { model: 'cartesia', extraKwargs: { min_volume: 0.5 } },\n 'assemblyai',\n ],\n });\n expect(stt['opts'].fallback).toEqual([\n { model: 'deepgram/nova-3' },\n { model: 'cartesia', extraKwargs: { min_volume: 0.5 } },\n { model: 'assemblyai' },\n ]);\n });\n\n it('fallback string with language discards language', () => {\n const stt = makeStt({ fallback: 'deepgram/nova-3:en' });\n expect(stt['opts'].fallback).toEqual([{ model: 'deepgram/nova-3' }]);\n });\n\n it('connOptions not given uses default', () => {\n const stt = makeStt();\n expect(stt['opts'].connOptions).toEqual(DEFAULT_API_CONNECT_OPTIONS);\n });\n\n it('connOptions custom timeout', () => {\n const custom: APIConnectOptions = { timeoutMs: 30000, maxRetry: 3, retryIntervalMs: 2000 };\n const stt = makeStt({ connOptions: custom });\n expect(stt['opts'].connOptions).toEqual(custom);\n expect(stt['opts'].connOptions!.timeoutMs).toBe(30000);\n });\n\n it('connOptions custom maxRetry', () => {\n const custom: APIConnectOptions = { timeoutMs: 10000, maxRetry: 5, retryIntervalMs: 2000 };\n const stt = makeStt({ connOptions: custom });\n expect(stt['opts'].connOptions).toEqual(custom);\n expect(stt['opts'].connOptions!.maxRetry).toBe(5);\n });\n\n it('connOptions full custom', () => {\n const custom: APIConnectOptions = { timeoutMs: 60000, maxRetry: 10, retryIntervalMs: 2000 };\n const stt = makeStt({ connOptions: custom });\n expect(stt['opts'].connOptions).toEqual(custom);\n expect(stt['opts'].connOptions!.timeoutMs).toBe(60000);\n expect(stt['opts'].connOptions!.maxRetry).toBe(10);\n expect(stt['opts'].connOptions!.retryIntervalMs).toBe(2000);\n });\n});\n"],"mappings":"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","names":[]}
@@ -1,4 +1,5 @@
1
1
  import { beforeAll, describe, expect, it } from "vitest";
2
+ import { normalizeLanguage } from "../language.js";
2
3
  import { initializeLogger } from "../log.js";
3
4
  import { DEFAULT_API_CONNECT_OPTIONS } from "../types.js";
4
5
  import { STT, normalizeSTTFallback, parseSTTModelString } from "./stt.js";
@@ -25,6 +26,11 @@ describe("parseSTTModelString", () => {
25
26
  expect(model).toBe("deepgram");
26
27
  expect(language).toBe("en");
27
28
  });
29
+ it("normalizes language suffixes", () => {
30
+ const [model, language] = parseSTTModelString("deepgram:english");
31
+ expect(model).toBe("deepgram");
32
+ expect(language).toBe("en");
33
+ });
28
34
  it("provider/model format without language", () => {
29
35
  const [model, language] = parseSTTModelString("deepgram/nova-3");
30
36
  expect(model).toBe("deepgram/nova-3");
@@ -127,6 +133,14 @@ describe("normalizeSTTFallback", () => {
127
133
  });
128
134
  });
129
135
  describe("STT constructor fallback and connOptions", () => {
136
+ it("normalizes language in constructor and model string", () => {
137
+ const stt = makeStt({ model: "deepgram/nova-3:english" });
138
+ expect(stt["opts"].language).toBe("en");
139
+ });
140
+ it("prefers explicit normalized language over model suffix", () => {
141
+ const stt = makeStt({ model: "deepgram/nova-3:english", language: "en_US" });
142
+ expect(stt["opts"].language).toBe(normalizeLanguage("en_US"));
143
+ });
130
144
  it("fallback not given defaults to undefined", () => {
131
145
  const stt = makeStt();
132
146
  expect(stt["opts"].fallback).toBeUndefined();
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/inference/stt.test.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { beforeAll, describe, expect, it } from 'vitest';\nimport { initializeLogger } from '../log.js';\nimport { type APIConnectOptions, DEFAULT_API_CONNECT_OPTIONS } from '../types.js';\nimport { STT, type STTFallbackModel, normalizeSTTFallback, parseSTTModelString } from './stt.js';\n\nbeforeAll(() => {\n initializeLogger({ level: 'silent', pretty: false });\n});\n\n/** Helper to create STT with required credentials. */\nfunction makeStt(overrides: Record<string, unknown> = {}) {\n const defaults = {\n model: 'deepgram' as const,\n apiKey: 'test-key',\n apiSecret: 'test-secret',\n baseURL: 'https://example.livekit.cloud',\n };\n return new STT({ ...defaults, ...overrides });\n}\n\ndescribe('parseSTTModelString', () => {\n it('simple model without language', () => {\n const [model, language] = parseSTTModelString('deepgram');\n expect(model).toBe('deepgram');\n expect(language).toBeUndefined();\n });\n\n it('model with language suffix', () => {\n const [model, language] = parseSTTModelString('deepgram:en');\n expect(model).toBe('deepgram');\n expect(language).toBe('en');\n });\n\n it('provider/model format without language', () => {\n const [model, language] = parseSTTModelString('deepgram/nova-3');\n expect(model).toBe('deepgram/nova-3');\n expect(language).toBeUndefined();\n });\n\n it('provider/model format with language', () => {\n const [model, language] = parseSTTModelString('deepgram/nova-3:en');\n expect(model).toBe('deepgram/nova-3');\n expect(language).toBe('en');\n });\n\n it.each([\n ['cartesia/ink-whisper:de', 'cartesia/ink-whisper', 'de'],\n ['assemblyai:es', 'assemblyai', 'es'],\n ['deepgram/nova-2-medical:ja', 'deepgram/nova-2-medical', 'ja'],\n ['deepgram/nova-3:multi', 'deepgram/nova-3', 'multi'],\n ['cartesia:zh', 'cartesia', 'zh'],\n ])('various providers and languages: %s', (modelStr, expectedModel, expectedLang) => {\n const [model, language] = parseSTTModelString(modelStr);\n expect(model).toBe(expectedModel);\n expect(language).toBe(expectedLang);\n });\n\n it('auto model without language', () => {\n const [model, language] = parseSTTModelString('auto');\n expect(model).toBe('auto');\n expect(language).toBeUndefined();\n });\n\n it('auto model with language', () => {\n const [model, language] = parseSTTModelString('auto:pt');\n expect(model).toBe('auto');\n expect(language).toBe('pt');\n });\n});\n\ndescribe('normalizeSTTFallback', () => {\n it('single string model', () => {\n const result = normalizeSTTFallback('deepgram/nova-3');\n expect(result).toEqual([{ model: 'deepgram/nova-3' }]);\n });\n\n it('single FallbackModel dict', () => {\n const fallback: STTFallbackModel = { model: 'deepgram/nova-3' };\n const result = normalizeSTTFallback(fallback);\n expect(result).toEqual([{ model: 'deepgram/nova-3' }]);\n });\n\n it('list of string models', () => {\n const result = normalizeSTTFallback(['deepgram/nova-3', 'cartesia/ink-whisper']);\n expect(result).toEqual([{ model: 'deepgram/nova-3' }, { model: 'cartesia/ink-whisper' }]);\n });\n\n it('list of FallbackModel dicts', () => {\n const fallbacks: STTFallbackModel[] = [{ model: 'deepgram/nova-3' }, { model: 'assemblyai' }];\n const result = normalizeSTTFallback(fallbacks);\n expect(result).toEqual([{ model: 'deepgram/nova-3' }, { model: 'assemblyai' }]);\n });\n\n it('mixed list of strings and dicts', () => {\n const result = normalizeSTTFallback([\n 'deepgram/nova-3',\n { model: 'cartesia/ink-whisper' } as STTFallbackModel,\n 'assemblyai',\n ]);\n expect(result).toEqual([\n { model: 'deepgram/nova-3' },\n { model: 'cartesia/ink-whisper' },\n { model: 'assemblyai' },\n ]);\n });\n\n it('string with language suffix discards language', () => {\n const result = normalizeSTTFallback('deepgram/nova-3:en');\n expect(result).toEqual([{ model: 'deepgram/nova-3' }]);\n });\n\n it('FallbackModel with extraKwargs is preserved', () => {\n const fallback: STTFallbackModel = {\n model: 'deepgram/nova-3',\n extraKwargs: { keywords: [['livekit', 1.5]], punctuate: true },\n };\n const result = normalizeSTTFallback(fallback);\n expect(result).toEqual([\n {\n model: 'deepgram/nova-3',\n extraKwargs: { keywords: [['livekit', 1.5]], punctuate: true },\n },\n ]);\n });\n\n it('list with extraKwargs preserved', () => {\n const result = normalizeSTTFallback([\n { model: 'deepgram/nova-3', extraKwargs: { punctuate: true } } as STTFallbackModel,\n 'cartesia/ink-whisper',\n { model: 'assemblyai', extraKwargs: { format_turns: true } } as STTFallbackModel,\n ]);\n expect(result).toEqual([\n { model: 'deepgram/nova-3', extraKwargs: { punctuate: true } },\n { model: 'cartesia/ink-whisper' },\n { model: 'assemblyai', extraKwargs: { format_turns: true } },\n ]);\n });\n\n it('empty list returns empty list', () => {\n const result = normalizeSTTFallback([]);\n expect(result).toEqual([]);\n });\n\n it('multiple colons in model string splits on last', () => {\n const result = normalizeSTTFallback('some:model:part:fr');\n expect(result).toEqual([{ model: 'some:model:part' }]);\n });\n});\n\ndescribe('STT constructor fallback and connOptions', () => {\n it('fallback not given defaults to undefined', () => {\n const stt = makeStt();\n expect(stt['opts'].fallback).toBeUndefined();\n });\n\n it('fallback single string is normalized', () => {\n const stt = makeStt({ fallback: 'cartesia/ink-whisper' });\n expect(stt['opts'].fallback).toEqual([{ model: 'cartesia/ink-whisper' }]);\n });\n\n it('fallback list of strings is normalized', () => {\n const stt = makeStt({ fallback: ['deepgram/nova-3', 'assemblyai'] });\n expect(stt['opts'].fallback).toEqual([{ model: 'deepgram/nova-3' }, { model: 'assemblyai' }]);\n });\n\n it('fallback single FallbackModel is normalized to list', () => {\n const stt = makeStt({ fallback: { model: 'deepgram/nova-3' } });\n expect(stt['opts'].fallback).toEqual([{ model: 'deepgram/nova-3' }]);\n });\n\n it('fallback with extraKwargs is preserved', () => {\n const stt = makeStt({\n fallback: {\n model: 'deepgram/nova-3',\n extraKwargs: { punctuate: true, keywords: [['livekit', 1.5]] },\n },\n });\n expect(stt['opts'].fallback).toEqual([\n {\n model: 'deepgram/nova-3',\n extraKwargs: { punctuate: true, keywords: [['livekit', 1.5]] },\n },\n ]);\n });\n\n it('fallback mixed list is normalized', () => {\n const stt = makeStt({\n fallback: [\n 'deepgram/nova-3',\n { model: 'cartesia', extraKwargs: { min_volume: 0.5 } },\n 'assemblyai',\n ],\n });\n expect(stt['opts'].fallback).toEqual([\n { model: 'deepgram/nova-3' },\n { model: 'cartesia', extraKwargs: { min_volume: 0.5 } },\n { model: 'assemblyai' },\n ]);\n });\n\n it('fallback string with language discards language', () => {\n const stt = makeStt({ fallback: 'deepgram/nova-3:en' });\n expect(stt['opts'].fallback).toEqual([{ model: 'deepgram/nova-3' }]);\n });\n\n it('connOptions not given uses default', () => {\n const stt = makeStt();\n expect(stt['opts'].connOptions).toEqual(DEFAULT_API_CONNECT_OPTIONS);\n });\n\n it('connOptions custom timeout', () => {\n const custom: APIConnectOptions = { timeoutMs: 30000, maxRetry: 3, retryIntervalMs: 2000 };\n const stt = makeStt({ connOptions: custom });\n expect(stt['opts'].connOptions).toEqual(custom);\n expect(stt['opts'].connOptions!.timeoutMs).toBe(30000);\n });\n\n it('connOptions custom maxRetry', () => {\n const custom: APIConnectOptions = { timeoutMs: 10000, maxRetry: 5, retryIntervalMs: 2000 };\n const stt = makeStt({ connOptions: custom });\n expect(stt['opts'].connOptions).toEqual(custom);\n expect(stt['opts'].connOptions!.maxRetry).toBe(5);\n });\n\n it('connOptions full custom', () => {\n const custom: APIConnectOptions = { timeoutMs: 60000, maxRetry: 10, retryIntervalMs: 2000 };\n const stt = makeStt({ connOptions: custom });\n expect(stt['opts'].connOptions).toEqual(custom);\n expect(stt['opts'].connOptions!.timeoutMs).toBe(60000);\n expect(stt['opts'].connOptions!.maxRetry).toBe(10);\n expect(stt['opts'].connOptions!.retryIntervalMs).toBe(2000);\n });\n});\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/inference/stt.test.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { beforeAll, describe, expect, it } from 'vitest';\nimport { normalizeLanguage } from '../language.js';\nimport { initializeLogger } from '../log.js';\nimport { type APIConnectOptions, DEFAULT_API_CONNECT_OPTIONS } from '../types.js';\nimport { STT, type STTFallbackModel, normalizeSTTFallback, parseSTTModelString } from './stt.js';\n\nbeforeAll(() => {\n initializeLogger({ level: 'silent', pretty: false });\n});\n\n/** Helper to create STT with required credentials. */\nfunction makeStt(overrides: Record<string, unknown> = {}) {\n const defaults = {\n model: 'deepgram' as const,\n apiKey: 'test-key',\n apiSecret: 'test-secret',\n baseURL: 'https://example.livekit.cloud',\n };\n return new STT({ ...defaults, ...overrides });\n}\n\ndescribe('parseSTTModelString', () => {\n it('simple model without language', () => {\n const [model, language] = parseSTTModelString('deepgram');\n expect(model).toBe('deepgram');\n expect(language).toBeUndefined();\n });\n\n it('model with language suffix', () => {\n const [model, language] = parseSTTModelString('deepgram:en');\n expect(model).toBe('deepgram');\n expect(language).toBe('en');\n });\n\n it('normalizes language suffixes', () => {\n const [model, language] = parseSTTModelString('deepgram:english');\n expect(model).toBe('deepgram');\n expect(language).toBe('en');\n });\n\n it('provider/model format without language', () => {\n const [model, language] = parseSTTModelString('deepgram/nova-3');\n expect(model).toBe('deepgram/nova-3');\n expect(language).toBeUndefined();\n });\n\n it('provider/model format with language', () => {\n const [model, language] = parseSTTModelString('deepgram/nova-3:en');\n expect(model).toBe('deepgram/nova-3');\n expect(language).toBe('en');\n });\n\n it.each([\n ['cartesia/ink-whisper:de', 'cartesia/ink-whisper', 'de'],\n ['assemblyai:es', 'assemblyai', 'es'],\n ['deepgram/nova-2-medical:ja', 'deepgram/nova-2-medical', 'ja'],\n ['deepgram/nova-3:multi', 'deepgram/nova-3', 'multi'],\n ['cartesia:zh', 'cartesia', 'zh'],\n ])('various providers and languages: %s', (modelStr, expectedModel, expectedLang) => {\n const [model, language] = parseSTTModelString(modelStr);\n expect(model).toBe(expectedModel);\n expect(language).toBe(expectedLang);\n });\n\n it('auto model without language', () => {\n const [model, language] = parseSTTModelString('auto');\n expect(model).toBe('auto');\n expect(language).toBeUndefined();\n });\n\n it('auto model with language', () => {\n const [model, language] = parseSTTModelString('auto:pt');\n expect(model).toBe('auto');\n expect(language).toBe('pt');\n });\n});\n\ndescribe('normalizeSTTFallback', () => {\n it('single string model', () => {\n const result = normalizeSTTFallback('deepgram/nova-3');\n expect(result).toEqual([{ model: 'deepgram/nova-3' }]);\n });\n\n it('single FallbackModel dict', () => {\n const fallback: STTFallbackModel = { model: 'deepgram/nova-3' };\n const result = normalizeSTTFallback(fallback);\n expect(result).toEqual([{ model: 'deepgram/nova-3' }]);\n });\n\n it('list of string models', () => {\n const result = normalizeSTTFallback(['deepgram/nova-3', 'cartesia/ink-whisper']);\n expect(result).toEqual([{ model: 'deepgram/nova-3' }, { model: 'cartesia/ink-whisper' }]);\n });\n\n it('list of FallbackModel dicts', () => {\n const fallbacks: STTFallbackModel[] = [{ model: 'deepgram/nova-3' }, { model: 'assemblyai' }];\n const result = normalizeSTTFallback(fallbacks);\n expect(result).toEqual([{ model: 'deepgram/nova-3' }, { model: 'assemblyai' }]);\n });\n\n it('mixed list of strings and dicts', () => {\n const result = normalizeSTTFallback([\n 'deepgram/nova-3',\n { model: 'cartesia/ink-whisper' } as STTFallbackModel,\n 'assemblyai',\n ]);\n expect(result).toEqual([\n { model: 'deepgram/nova-3' },\n { model: 'cartesia/ink-whisper' },\n { model: 'assemblyai' },\n ]);\n });\n\n it('string with language suffix discards language', () => {\n const result = normalizeSTTFallback('deepgram/nova-3:en');\n expect(result).toEqual([{ model: 'deepgram/nova-3' }]);\n });\n\n it('FallbackModel with extraKwargs is preserved', () => {\n const fallback: STTFallbackModel = {\n model: 'deepgram/nova-3',\n extraKwargs: { keywords: [['livekit', 1.5]], punctuate: true },\n };\n const result = normalizeSTTFallback(fallback);\n expect(result).toEqual([\n {\n model: 'deepgram/nova-3',\n extraKwargs: { keywords: [['livekit', 1.5]], punctuate: true },\n },\n ]);\n });\n\n it('list with extraKwargs preserved', () => {\n const result = normalizeSTTFallback([\n { model: 'deepgram/nova-3', extraKwargs: { punctuate: true } } as STTFallbackModel,\n 'cartesia/ink-whisper',\n { model: 'assemblyai', extraKwargs: { format_turns: true } } as STTFallbackModel,\n ]);\n expect(result).toEqual([\n { model: 'deepgram/nova-3', extraKwargs: { punctuate: true } },\n { model: 'cartesia/ink-whisper' },\n { model: 'assemblyai', extraKwargs: { format_turns: true } },\n ]);\n });\n\n it('empty list returns empty list', () => {\n const result = normalizeSTTFallback([]);\n expect(result).toEqual([]);\n });\n\n it('multiple colons in model string splits on last', () => {\n const result = normalizeSTTFallback('some:model:part:fr');\n expect(result).toEqual([{ model: 'some:model:part' }]);\n });\n});\n\ndescribe('STT constructor fallback and connOptions', () => {\n it('normalizes language in constructor and model string', () => {\n const stt = makeStt({ model: 'deepgram/nova-3:english' });\n expect(stt['opts'].language).toBe('en');\n });\n\n it('prefers explicit normalized language over model suffix', () => {\n const stt = makeStt({ model: 'deepgram/nova-3:english', language: 'en_US' });\n expect(stt['opts'].language).toBe(normalizeLanguage('en_US'));\n });\n\n it('fallback not given defaults to undefined', () => {\n const stt = makeStt();\n expect(stt['opts'].fallback).toBeUndefined();\n });\n\n it('fallback single string is normalized', () => {\n const stt = makeStt({ fallback: 'cartesia/ink-whisper' });\n expect(stt['opts'].fallback).toEqual([{ model: 'cartesia/ink-whisper' }]);\n });\n\n it('fallback list of strings is normalized', () => {\n const stt = makeStt({ fallback: ['deepgram/nova-3', 'assemblyai'] });\n expect(stt['opts'].fallback).toEqual([{ model: 'deepgram/nova-3' }, { model: 'assemblyai' }]);\n });\n\n it('fallback single FallbackModel is normalized to list', () => {\n const stt = makeStt({ fallback: { model: 'deepgram/nova-3' } });\n expect(stt['opts'].fallback).toEqual([{ model: 'deepgram/nova-3' }]);\n });\n\n it('fallback with extraKwargs is preserved', () => {\n const stt = makeStt({\n fallback: {\n model: 'deepgram/nova-3',\n extraKwargs: { punctuate: true, keywords: [['livekit', 1.5]] },\n },\n });\n expect(stt['opts'].fallback).toEqual([\n {\n model: 'deepgram/nova-3',\n extraKwargs: { punctuate: true, keywords: [['livekit', 1.5]] },\n },\n ]);\n });\n\n it('fallback mixed list is normalized', () => {\n const stt = makeStt({\n fallback: [\n 'deepgram/nova-3',\n { model: 'cartesia', extraKwargs: { min_volume: 0.5 } },\n 'assemblyai',\n ],\n });\n expect(stt['opts'].fallback).toEqual([\n { model: 'deepgram/nova-3' },\n { model: 'cartesia', extraKwargs: { min_volume: 0.5 } },\n { model: 'assemblyai' },\n ]);\n });\n\n it('fallback string with language discards language', () => {\n const stt = makeStt({ fallback: 'deepgram/nova-3:en' });\n expect(stt['opts'].fallback).toEqual([{ model: 'deepgram/nova-3' }]);\n });\n\n it('connOptions not given uses default', () => {\n const stt = makeStt();\n expect(stt['opts'].connOptions).toEqual(DEFAULT_API_CONNECT_OPTIONS);\n });\n\n it('connOptions custom timeout', () => {\n const custom: APIConnectOptions = { timeoutMs: 30000, maxRetry: 3, retryIntervalMs: 2000 };\n const stt = makeStt({ connOptions: custom });\n expect(stt['opts'].connOptions).toEqual(custom);\n expect(stt['opts'].connOptions!.timeoutMs).toBe(30000);\n });\n\n it('connOptions custom maxRetry', () => {\n const custom: APIConnectOptions = { timeoutMs: 10000, maxRetry: 5, retryIntervalMs: 2000 };\n const stt = makeStt({ connOptions: custom });\n expect(stt['opts'].connOptions).toEqual(custom);\n expect(stt['opts'].connOptions!.maxRetry).toBe(5);\n });\n\n it('connOptions full custom', () => {\n const custom: APIConnectOptions = { timeoutMs: 60000, maxRetry: 10, retryIntervalMs: 2000 };\n const stt = makeStt({ connOptions: custom });\n expect(stt['opts'].connOptions).toEqual(custom);\n expect(stt['opts'].connOptions!.timeoutMs).toBe(60000);\n expect(stt['opts'].connOptions!.maxRetry).toBe(10);\n expect(stt['opts'].connOptions!.retryIntervalMs).toBe(2000);\n });\n});\n"],"mappings":"AAGA,SAAS,WAAW,UAAU,QAAQ,UAAU;AAChD,SAAS,yBAAyB;AAClC,SAAS,wBAAwB;AACjC,SAAiC,mCAAmC;AACpE,SAAS,KAA4B,sBAAsB,2BAA2B;AAEtF,UAAU,MAAM;AACd,mBAAiB,EAAE,OAAO,UAAU,QAAQ,MAAM,CAAC;AACrD,CAAC;AAGD,SAAS,QAAQ,YAAqC,CAAC,GAAG;AACxD,QAAM,WAAW;AAAA,IACf,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,SAAS;AAAA,EACX;AACA,SAAO,IAAI,IAAI,EAAE,GAAG,UAAU,GAAG,UAAU,CAAC;AAC9C;AAEA,SAAS,uBAAuB,MAAM;AACpC,KAAG,iCAAiC,MAAM;AACxC,UAAM,CAAC,OAAO,QAAQ,IAAI,oBAAoB,UAAU;AACxD,WAAO,KAAK,EAAE,KAAK,UAAU;AAC7B,WAAO,QAAQ,EAAE,cAAc;AAAA,EACjC,CAAC;AAED,KAAG,8BAA8B,MAAM;AACrC,UAAM,CAAC,OAAO,QAAQ,IAAI,oBAAoB,aAAa;AAC3D,WAAO,KAAK,EAAE,KAAK,UAAU;AAC7B,WAAO,QAAQ,EAAE,KAAK,IAAI;AAAA,EAC5B,CAAC;AAED,KAAG,gCAAgC,MAAM;AACvC,UAAM,CAAC,OAAO,QAAQ,IAAI,oBAAoB,kBAAkB;AAChE,WAAO,KAAK,EAAE,KAAK,UAAU;AAC7B,WAAO,QAAQ,EAAE,KAAK,IAAI;AAAA,EAC5B,CAAC;AAED,KAAG,0CAA0C,MAAM;AACjD,UAAM,CAAC,OAAO,QAAQ,IAAI,oBAAoB,iBAAiB;AAC/D,WAAO,KAAK,EAAE,KAAK,iBAAiB;AACpC,WAAO,QAAQ,EAAE,cAAc;AAAA,EACjC,CAAC;AAED,KAAG,uCAAuC,MAAM;AAC9C,UAAM,CAAC,OAAO,QAAQ,IAAI,oBAAoB,oBAAoB;AAClE,WAAO,KAAK,EAAE,KAAK,iBAAiB;AACpC,WAAO,QAAQ,EAAE,KAAK,IAAI;AAAA,EAC5B,CAAC;AAED,KAAG,KAAK;AAAA,IACN,CAAC,2BAA2B,wBAAwB,IAAI;AAAA,IACxD,CAAC,iBAAiB,cAAc,IAAI;AAAA,IACpC,CAAC,8BAA8B,2BAA2B,IAAI;AAAA,IAC9D,CAAC,yBAAyB,mBAAmB,OAAO;AAAA,IACpD,CAAC,eAAe,YAAY,IAAI;AAAA,EAClC,CAAC,EAAE,uCAAuC,CAAC,UAAU,eAAe,iBAAiB;AACnF,UAAM,CAAC,OAAO,QAAQ,IAAI,oBAAoB,QAAQ;AACtD,WAAO,KAAK,EAAE,KAAK,aAAa;AAChC,WAAO,QAAQ,EAAE,KAAK,YAAY;AAAA,EACpC,CAAC;AAED,KAAG,+BAA+B,MAAM;AACtC,UAAM,CAAC,OAAO,QAAQ,IAAI,oBAAoB,MAAM;AACpD,WAAO,KAAK,EAAE,KAAK,MAAM;AACzB,WAAO,QAAQ,EAAE,cAAc;AAAA,EACjC,CAAC;AAED,KAAG,4BAA4B,MAAM;AACnC,UAAM,CAAC,OAAO,QAAQ,IAAI,oBAAoB,SAAS;AACvD,WAAO,KAAK,EAAE,KAAK,MAAM;AACzB,WAAO,QAAQ,EAAE,KAAK,IAAI;AAAA,EAC5B,CAAC;AACH,CAAC;AAED,SAAS,wBAAwB,MAAM;AACrC,KAAG,uBAAuB,MAAM;AAC9B,UAAM,SAAS,qBAAqB,iBAAiB;AACrD,WAAO,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,kBAAkB,CAAC,CAAC;AAAA,EACvD,CAAC;AAED,KAAG,6BAA6B,MAAM;AACpC,UAAM,WAA6B,EAAE,OAAO,kBAAkB;AAC9D,UAAM,SAAS,qBAAqB,QAAQ;AAC5C,WAAO,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,kBAAkB,CAAC,CAAC;AAAA,EACvD,CAAC;AAED,KAAG,yBAAyB,MAAM;AAChC,UAAM,SAAS,qBAAqB,CAAC,mBAAmB,sBAAsB,CAAC;AAC/E,WAAO,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,kBAAkB,GAAG,EAAE,OAAO,uBAAuB,CAAC,CAAC;AAAA,EAC1F,CAAC;AAED,KAAG,+BAA+B,MAAM;AACtC,UAAM,YAAgC,CAAC,EAAE,OAAO,kBAAkB,GAAG,EAAE,OAAO,aAAa,CAAC;AAC5F,UAAM,SAAS,qBAAqB,SAAS;AAC7C,WAAO,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,kBAAkB,GAAG,EAAE,OAAO,aAAa,CAAC,CAAC;AAAA,EAChF,CAAC;AAED,KAAG,mCAAmC,MAAM;AAC1C,UAAM,SAAS,qBAAqB;AAAA,MAClC;AAAA,MACA,EAAE,OAAO,uBAAuB;AAAA,MAChC;AAAA,IACF,CAAC;AACD,WAAO,MAAM,EAAE,QAAQ;AAAA,MACrB,EAAE,OAAO,kBAAkB;AAAA,MAC3B,EAAE,OAAO,uBAAuB;AAAA,MAChC,EAAE,OAAO,aAAa;AAAA,IACxB,CAAC;AAAA,EACH,CAAC;AAED,KAAG,iDAAiD,MAAM;AACxD,UAAM,SAAS,qBAAqB,oBAAoB;AACxD,WAAO,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,kBAAkB,CAAC,CAAC;AAAA,EACvD,CAAC;AAED,KAAG,+CAA+C,MAAM;AACtD,UAAM,WAA6B;AAAA,MACjC,OAAO;AAAA,MACP,aAAa,EAAE,UAAU,CAAC,CAAC,WAAW,GAAG,CAAC,GAAG,WAAW,KAAK;AAAA,IAC/D;AACA,UAAM,SAAS,qBAAqB,QAAQ;AAC5C,WAAO,MAAM,EAAE,QAAQ;AAAA,MACrB;AAAA,QACE,OAAO;AAAA,QACP,aAAa,EAAE,UAAU,CAAC,CAAC,WAAW,GAAG,CAAC,GAAG,WAAW,KAAK;AAAA,MAC/D;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AAED,KAAG,mCAAmC,MAAM;AAC1C,UAAM,SAAS,qBAAqB;AAAA,MAClC,EAAE,OAAO,mBAAmB,aAAa,EAAE,WAAW,KAAK,EAAE;AAAA,MAC7D;AAAA,MACA,EAAE,OAAO,cAAc,aAAa,EAAE,cAAc,KAAK,EAAE;AAAA,IAC7D,CAAC;AACD,WAAO,MAAM,EAAE,QAAQ;AAAA,MACrB,EAAE,OAAO,mBAAmB,aAAa,EAAE,WAAW,KAAK,EAAE;AAAA,MAC7D,EAAE,OAAO,uBAAuB;AAAA,MAChC,EAAE,OAAO,cAAc,aAAa,EAAE,cAAc,KAAK,EAAE;AAAA,IAC7D,CAAC;AAAA,EACH,CAAC;AAED,KAAG,iCAAiC,MAAM;AACxC,UAAM,SAAS,qBAAqB,CAAC,CAAC;AACtC,WAAO,MAAM,EAAE,QAAQ,CAAC,CAAC;AAAA,EAC3B,CAAC;AAED,KAAG,kDAAkD,MAAM;AACzD,UAAM,SAAS,qBAAqB,oBAAoB;AACxD,WAAO,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,kBAAkB,CAAC,CAAC;AAAA,EACvD,CAAC;AACH,CAAC;AAED,SAAS,4CAA4C,MAAM;AACzD,KAAG,uDAAuD,MAAM;AAC9D,UAAM,MAAM,QAAQ,EAAE,OAAO,0BAA0B,CAAC;AACxD,WAAO,IAAI,MAAM,EAAE,QAAQ,EAAE,KAAK,IAAI;AAAA,EACxC,CAAC;AAED,KAAG,0DAA0D,MAAM;AACjE,UAAM,MAAM,QAAQ,EAAE,OAAO,2BAA2B,UAAU,QAAQ,CAAC;AAC3E,WAAO,IAAI,MAAM,EAAE,QAAQ,EAAE,KAAK,kBAAkB,OAAO,CAAC;AAAA,EAC9D,CAAC;AAED,KAAG,4CAA4C,MAAM;AACnD,UAAM,MAAM,QAAQ;AACpB,WAAO,IAAI,MAAM,EAAE,QAAQ,EAAE,cAAc;AAAA,EAC7C,CAAC;AAED,KAAG,wCAAwC,MAAM;AAC/C,UAAM,MAAM,QAAQ,EAAE,UAAU,uBAAuB,CAAC;AACxD,WAAO,IAAI,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,EAAE,OAAO,uBAAuB,CAAC,CAAC;AAAA,EAC1E,CAAC;AAED,KAAG,0CAA0C,MAAM;AACjD,UAAM,MAAM,QAAQ,EAAE,UAAU,CAAC,mBAAmB,YAAY,EAAE,CAAC;AACnE,WAAO,IAAI,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,EAAE,OAAO,kBAAkB,GAAG,EAAE,OAAO,aAAa,CAAC,CAAC;AAAA,EAC9F,CAAC;AAED,KAAG,uDAAuD,MAAM;AAC9D,UAAM,MAAM,QAAQ,EAAE,UAAU,EAAE,OAAO,kBAAkB,EAAE,CAAC;AAC9D,WAAO,IAAI,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,EAAE,OAAO,kBAAkB,CAAC,CAAC;AAAA,EACrE,CAAC;AAED,KAAG,0CAA0C,MAAM;AACjD,UAAM,MAAM,QAAQ;AAAA,MAClB,UAAU;AAAA,QACR,OAAO;AAAA,QACP,aAAa,EAAE,WAAW,MAAM,UAAU,CAAC,CAAC,WAAW,GAAG,CAAC,EAAE;AAAA,MAC/D;AAAA,IACF,CAAC;AACD,WAAO,IAAI,MAAM,EAAE,QAAQ,EAAE,QAAQ;AAAA,MACnC;AAAA,QACE,OAAO;AAAA,QACP,aAAa,EAAE,WAAW,MAAM,UAAU,CAAC,CAAC,WAAW,GAAG,CAAC,EAAE;AAAA,MAC/D;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AAED,KAAG,qCAAqC,MAAM;AAC5C,UAAM,MAAM,QAAQ;AAAA,MAClB,UAAU;AAAA,QACR;AAAA,QACA,EAAE,OAAO,YAAY,aAAa,EAAE,YAAY,IAAI,EAAE;AAAA,QACtD;AAAA,MACF;AAAA,IACF,CAAC;AACD,WAAO,IAAI,MAAM,EAAE,QAAQ,EAAE,QAAQ;AAAA,MACnC,EAAE,OAAO,kBAAkB;AAAA,MAC3B,EAAE,OAAO,YAAY,aAAa,EAAE,YAAY,IAAI,EAAE;AAAA,MACtD,EAAE,OAAO,aAAa;AAAA,IACxB,CAAC;AAAA,EACH,CAAC;AAED,KAAG,mDAAmD,MAAM;AAC1D,UAAM,MAAM,QAAQ,EAAE,UAAU,qBAAqB,CAAC;AACtD,WAAO,IAAI,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,EAAE,OAAO,kBAAkB,CAAC,CAAC;AAAA,EACrE,CAAC;AAED,KAAG,sCAAsC,MAAM;AAC7C,UAAM,MAAM,QAAQ;AACpB,WAAO,IAAI,MAAM,EAAE,WAAW,EAAE,QAAQ,2BAA2B;AAAA,EACrE,CAAC;AAED,KAAG,8BAA8B,MAAM;AACrC,UAAM,SAA4B,EAAE,WAAW,KAAO,UAAU,GAAG,iBAAiB,IAAK;AACzF,UAAM,MAAM,QAAQ,EAAE,aAAa,OAAO,CAAC;AAC3C,WAAO,IAAI,MAAM,EAAE,WAAW,EAAE,QAAQ,MAAM;AAC9C,WAAO,IAAI,MAAM,EAAE,YAAa,SAAS,EAAE,KAAK,GAAK;AAAA,EACvD,CAAC;AAED,KAAG,+BAA+B,MAAM;AACtC,UAAM,SAA4B,EAAE,WAAW,KAAO,UAAU,GAAG,iBAAiB,IAAK;AACzF,UAAM,MAAM,QAAQ,EAAE,aAAa,OAAO,CAAC;AAC3C,WAAO,IAAI,MAAM,EAAE,WAAW,EAAE,QAAQ,MAAM;AAC9C,WAAO,IAAI,MAAM,EAAE,YAAa,QAAQ,EAAE,KAAK,CAAC;AAAA,EAClD,CAAC;AAED,KAAG,2BAA2B,MAAM;AAClC,UAAM,SAA4B,EAAE,WAAW,KAAO,UAAU,IAAI,iBAAiB,IAAK;AAC1F,UAAM,MAAM,QAAQ,EAAE,aAAa,OAAO,CAAC;AAC3C,WAAO,IAAI,MAAM,EAAE,WAAW,EAAE,QAAQ,MAAM;AAC9C,WAAO,IAAI,MAAM,EAAE,YAAa,SAAS,EAAE,KAAK,GAAK;AACrD,WAAO,IAAI,MAAM,EAAE,YAAa,QAAQ,EAAE,KAAK,EAAE;AACjD,WAAO,IAAI,MAAM,EAAE,YAAa,eAAe,EAAE,KAAK,GAAI;AAAA,EAC5D,CAAC;AACH,CAAC;","names":[]}
@@ -28,6 +28,7 @@ var import_ws = require("ws");
28
28
  var import_exceptions = require("../_exceptions.cjs");
29
29
  var import_audio = require("../audio.cjs");
30
30
  var import_connection_pool = require("../connection_pool.cjs");
31
+ var import_language = require("../language.cjs");
31
32
  var import_log = require("../log.cjs");
32
33
  var import_stream_channel = require("../stream/stream_channel.cjs");
33
34
  var import_tokenize = require("../tokenize/index.cjs");
@@ -110,7 +111,7 @@ class TTS extends import_tts.TTS {
110
111
  this.opts = {
111
112
  model: nextModel,
112
113
  voice: nextVoice,
113
- language,
114
+ language: (0, import_language.normalizeLanguage)(language),
114
115
  encoding,
115
116
  sampleRate,
116
117
  baseURL: lkBaseURL,
@@ -143,9 +144,13 @@ class TTS extends import_tts.TTS {
143
144
  return new TTS({ model, voice: voice || void 0 });
144
145
  }
145
146
  updateOptions(opts) {
146
- this.opts = { ...this.opts, ...opts };
147
+ this.opts = {
148
+ ...this.opts,
149
+ ...opts,
150
+ language: opts.language !== void 0 ? (0, import_language.normalizeLanguage)(opts.language) : this.opts.language
151
+ };
147
152
  for (const stream of this.streams) {
148
- stream.updateOptions(opts);
153
+ stream.updateOptions(this.opts);
149
154
  }
150
155
  }
151
156
  synthesize(_) {
@@ -222,7 +227,11 @@ class SynthesizeStream extends import_tts.SynthesizeStream {
222
227
  return "inference.SynthesizeStream";
223
228
  }
224
229
  updateOptions(opts) {
225
- this.opts = { ...this.opts, ...opts };
230
+ this.opts = {
231
+ ...this.opts,
232
+ ...opts,
233
+ language: opts.language !== void 0 ? (0, import_language.normalizeLanguage)(opts.language) : this.opts.language
234
+ };
226
235
  }
227
236
  async run() {
228
237
  let closing = false;