@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
@@ -6,6 +6,7 @@ import { WebSocket } from 'ws';
6
6
  import { APIError, APIStatusError } from '../_exceptions.js';
7
7
  import { AudioByteStream } from '../audio.js';
8
8
  import { ConnectionPool } from '../connection_pool.js';
9
+ import { type LanguageCode, normalizeLanguage } from '../language.js';
9
10
  import { log } from '../log.js';
10
11
  import { createStreamChannel } from '../stream/stream_channel.js';
11
12
  import { basic as tokenizeBasic } from '../tokenize/index.js';
@@ -62,7 +63,14 @@ export interface DeepgramTTSOptions {}
62
63
 
63
64
  export interface RimeOptions {}
64
65
 
65
- export interface InworldOptions {}
66
+ export interface InworldOptions {
67
+ /** Controls how fast the voice speaks. 1.0 is normal speed, 0.5 is half, 1.5 is 1.5x. Default: 1.0. */
68
+ speaking_rate?: number;
69
+ /** Controls randomness in the output. Recommended between 0.6 and 1.1. Default: 1.1. */
70
+ temperature?: number;
71
+ /** Controls text normalization. "ON" expands numbers, dates, abbreviations. "OFF" reads text as written. Default: "ON". */
72
+ text_normalization?: 'ON' | 'OFF';
73
+ }
66
74
 
67
75
  type _TTSModels =
68
76
  | CartesiaModels
@@ -142,7 +150,7 @@ const DEFAULT_LANGUAGE = 'en';
142
150
  export interface InferenceTTSOptions<TModel extends TTSModels> {
143
151
  model?: TModel;
144
152
  voice?: string;
145
- language?: string;
153
+ language?: LanguageCode;
146
154
  encoding: TTSEncoding;
147
155
  sampleRate: number;
148
156
  baseURL: string;
@@ -228,7 +236,7 @@ export class TTS<TModel extends TTSModels> extends BaseTTS {
228
236
  this.opts = {
229
237
  model: nextModel,
230
238
  voice: nextVoice,
231
- language,
239
+ language: normalizeLanguage(language),
232
240
  encoding,
233
241
  sampleRate,
234
242
  baseURL: lkBaseURL,
@@ -267,9 +275,13 @@ export class TTS<TModel extends TTSModels> extends BaseTTS {
267
275
  }
268
276
 
269
277
  updateOptions(opts: Partial<Pick<InferenceTTSOptions<TModel>, 'model' | 'voice' | 'language'>>) {
270
- this.opts = { ...this.opts, ...opts };
278
+ this.opts = {
279
+ ...this.opts,
280
+ ...opts,
281
+ language: opts.language !== undefined ? normalizeLanguage(opts.language) : this.opts.language,
282
+ };
271
283
  for (const stream of this.streams) {
272
- stream.updateOptions(opts);
284
+ stream.updateOptions(this.opts);
273
285
  }
274
286
  }
275
287
 
@@ -362,7 +374,11 @@ export class SynthesizeStream<TModel extends TTSModels> extends BaseSynthesizeSt
362
374
  }
363
375
 
364
376
  updateOptions(opts: Partial<Pick<InferenceTTSOptions<TModel>, 'model' | 'voice' | 'language'>>) {
365
- this.opts = { ...this.opts, ...opts };
377
+ this.opts = {
378
+ ...this.opts,
379
+ ...opts,
380
+ language: opts.language !== undefined ? normalizeLanguage(opts.language) : this.opts.language,
381
+ };
366
382
  }
367
383
 
368
384
  protected async run(): Promise<void> {
@@ -16,11 +16,22 @@ import type { IPCMessage } from './message.js';
16
16
  const ORPHANED_TIMEOUT = 15 * 1000;
17
17
 
18
18
  const safeSend = (msg: IPCMessage): boolean => {
19
- if (process.connected && process.send) {
20
- process.send(msg);
21
- return true;
19
+ try {
20
+ if (process.connected && process.send) {
21
+ process.send(msg);
22
+ return true;
23
+ }
24
+ return false;
25
+ } catch (error) {
26
+ // Channel closed is expected during graceful shutdown
27
+ // Log at debug level to avoid noise in production logs
28
+ if (error instanceof Error && error.message.includes('Channel closed')) {
29
+ log().debug({ msgCase: msg.case }, 'IPC channel closed, message not sent');
30
+ } else {
31
+ log().error({ error, msgCase: msg.case }, 'IPC send failed unexpectedly');
32
+ }
33
+ return false;
22
34
  }
23
- return false;
24
35
  };
25
36
 
26
37
  type JobTask = {
@@ -109,27 +120,36 @@ const startJob = (
109
120
  }
110
121
  }, 10000);
111
122
 
112
- // Run the job function within the AsyncLocalStorage context
113
- await runWithJobContextAsync(ctx, async () => {
114
- const { tracer, traceTypes } = await import('../telemetry/index.js');
115
- return tracer.startActiveSpan(
116
- async (span) => {
117
- span.setAttribute(traceTypes.ATTR_JOB_ID, info.job.id);
118
- span.setAttribute(traceTypes.ATTR_AGENT_NAME, info.job.agentName);
119
- span.setAttribute(traceTypes.ATTR_ROOM_NAME, info.job.room?.name ?? '');
120
- return func(ctx);
121
- },
122
- { name: 'job_entrypoint' },
123
- );
124
- }).finally(() => {
125
- clearTimeout(unconnectedTimeout);
126
- });
127
-
128
- await once(closeEvent, 'close').then((close) => {
129
- logger.debug('shutting down');
123
+ try {
124
+ // Run the job function within the AsyncLocalStorage context
125
+ await runWithJobContextAsync(ctx, async () => {
126
+ const { tracer, traceTypes } = await import('../telemetry/index.js');
127
+ return tracer.startActiveSpan(
128
+ async (span) => {
129
+ span.setAttribute(traceTypes.ATTR_JOB_ID, info.job.id);
130
+ span.setAttribute(traceTypes.ATTR_AGENT_NAME, info.job.agentName);
131
+ span.setAttribute(traceTypes.ATTR_ROOM_NAME, info.job.room?.name ?? '');
132
+ return func(ctx);
133
+ },
134
+ { name: 'job_entrypoint' },
135
+ );
136
+ }).finally(() => {
137
+ clearTimeout(unconnectedTimeout);
138
+ });
139
+
140
+ await once(closeEvent, 'close').then((close) => {
141
+ logger.debug('shutting down');
142
+ shutdown = true;
143
+ safeSend({ case: 'exiting', value: { reason: close[1] } });
144
+ });
145
+ } catch (error) {
146
+ logger.error({ error }, 'error in entry function');
130
147
  shutdown = true;
131
- safeSend({ case: 'exiting', value: { reason: close[1] } });
132
- });
148
+ safeSend({
149
+ case: 'exiting',
150
+ value: { reason: error instanceof Error ? error.message : String(error) },
151
+ });
152
+ }
133
153
 
134
154
  // Close the primary agent session if it exists
135
155
  if (ctx._primaryAgentSession) {
package/src/job.ts CHANGED
@@ -276,7 +276,7 @@ export class JobContext {
276
276
  jobId: this.job.id,
277
277
  roomId: this.job.room?.sid || '',
278
278
  room: this.job.room?.name || '',
279
- options: targetSession.options,
279
+ options: targetSession.sessionOptions,
280
280
  events: targetSession._recordedEvents,
281
281
  enableRecording: targetSession._enableRecording,
282
282
  chatHistory: targetSession.history.copy(),
@@ -0,0 +1,62 @@
1
+ // SPDX-FileCopyrightText: 2025 LiveKit, Inc.
2
+ //
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ import { describe, expect, it } from 'vitest';
5
+ import {
6
+ areLanguagesEquivalent,
7
+ getBaseLanguage,
8
+ getIsoLanguage,
9
+ getLanguageRegion,
10
+ normalizeLanguage,
11
+ toLanguageName,
12
+ } from './language.js';
13
+
14
+ describe('normalizeLanguage', () => {
15
+ it('normalizes language names', () => {
16
+ expect(normalizeLanguage('english')).toBe('en');
17
+ });
18
+
19
+ it('normalizes iso 639-3 codes', () => {
20
+ expect(normalizeLanguage('eng')).toBe('en');
21
+ });
22
+
23
+ it('normalizes bcp-47 casing and separators', () => {
24
+ expect(normalizeLanguage('en_us')).toBe('en-US');
25
+ expect(normalizeLanguage('zh_hans_cn')).toBe('zh-Hans-CN');
26
+ });
27
+
28
+ it('preserves iso 639-3 in compound tags', () => {
29
+ expect(normalizeLanguage('cmn_hans_cn')).toBe('cmn-Hans-CN');
30
+ });
31
+
32
+ it('passes unknown codes through in lowercase', () => {
33
+ expect(normalizeLanguage('MULTI')).toBe('multi');
34
+ });
35
+
36
+ it('preserves empty string sentinel', () => {
37
+ expect(normalizeLanguage('')).toBe('');
38
+ });
39
+ });
40
+
41
+ describe('language helpers', () => {
42
+ it('extracts base language', () => {
43
+ expect(getBaseLanguage('cmn-Hans-CN')).toBe('zh');
44
+ });
45
+
46
+ it('builds iso language tag', () => {
47
+ expect(getIsoLanguage('cmn-Hans-CN')).toBe('zh-CN');
48
+ });
49
+
50
+ it('extracts region', () => {
51
+ expect(getLanguageRegion('en-US')).toBe('US');
52
+ });
53
+
54
+ it('maps normalized code back to language name', () => {
55
+ expect(toLanguageName('eng')).toBe('english');
56
+ });
57
+
58
+ it('compares equivalent representations', () => {
59
+ expect(areLanguagesEquivalent('english', 'en')).toBe(true);
60
+ expect(areLanguagesEquivalent('en_us', 'en-US')).toBe(true);
61
+ });
62
+ });
@@ -0,0 +1,380 @@
1
+ // SPDX-FileCopyrightText: 2026 LiveKit, Inc.
2
+ //
3
+ // SPDX-License-Identifier: Apache-2.0
4
+
5
+ export const KNOWN_LANGUAGE_CODES = [
6
+ 'af',
7
+ 'am',
8
+ 'ar',
9
+ 'as',
10
+ 'az',
11
+ 'be',
12
+ 'bg',
13
+ 'bn',
14
+ 'bs',
15
+ 'ca',
16
+ 'cs',
17
+ 'cy',
18
+ 'da',
19
+ 'de',
20
+ 'el',
21
+ 'en',
22
+ 'es',
23
+ 'et',
24
+ 'eu',
25
+ 'fa',
26
+ 'ff',
27
+ 'fi',
28
+ 'fr',
29
+ 'ga',
30
+ 'gl',
31
+ 'gu',
32
+ 'ha',
33
+ 'he',
34
+ 'hi',
35
+ 'hr',
36
+ 'hu',
37
+ 'hy',
38
+ 'id',
39
+ 'ig',
40
+ 'is',
41
+ 'it',
42
+ 'ja',
43
+ 'jv',
44
+ 'ka',
45
+ 'kk',
46
+ 'km',
47
+ 'kn',
48
+ 'ko',
49
+ 'ku',
50
+ 'ky',
51
+ 'lb',
52
+ 'lg',
53
+ 'ln',
54
+ 'lo',
55
+ 'lt',
56
+ 'lv',
57
+ 'mi',
58
+ 'mk',
59
+ 'ml',
60
+ 'mn',
61
+ 'mr',
62
+ 'ms',
63
+ 'mt',
64
+ 'my',
65
+ 'ne',
66
+ 'nl',
67
+ 'no',
68
+ 'ny',
69
+ 'oc',
70
+ 'or',
71
+ 'pa',
72
+ 'pl',
73
+ 'ps',
74
+ 'pt',
75
+ 'ro',
76
+ 'ru',
77
+ 'sd',
78
+ 'sk',
79
+ 'sl',
80
+ 'sn',
81
+ 'so',
82
+ 'sq',
83
+ 'sr',
84
+ 'sv',
85
+ 'sw',
86
+ 'ta',
87
+ 'te',
88
+ 'tg',
89
+ 'th',
90
+ 'tl',
91
+ 'tr',
92
+ 'uk',
93
+ 'ur',
94
+ 'uz',
95
+ 'vi',
96
+ 'wo',
97
+ 'xh',
98
+ 'yo',
99
+ 'zh',
100
+ 'zu',
101
+ ] as const;
102
+
103
+ export type KnownLanguageCode = (typeof KNOWN_LANGUAGE_CODES)[number];
104
+
105
+ declare const languageCodeBrand: unique symbol;
106
+
107
+ export type LanguageCode = string & { readonly [languageCodeBrand]: 'LanguageCode' };
108
+
109
+ export function asLanguageCode(language: string): LanguageCode {
110
+ return language as LanguageCode;
111
+ }
112
+
113
+ const ISO_639_3_TO_1: Record<string, string | undefined> = {
114
+ afr: 'af',
115
+ amh: 'am',
116
+ ara: 'ar',
117
+ hye: 'hy',
118
+ asm: 'as',
119
+ ast: undefined,
120
+ aze: 'az',
121
+ bel: 'be',
122
+ ben: 'bn',
123
+ bos: 'bs',
124
+ bul: 'bg',
125
+ mya: 'my',
126
+ yue: undefined,
127
+ cat: 'ca',
128
+ ceb: undefined,
129
+ cmn: 'zh',
130
+ nya: 'ny',
131
+ hrv: 'hr',
132
+ ces: 'cs',
133
+ dan: 'da',
134
+ nld: 'nl',
135
+ eng: 'en',
136
+ est: 'et',
137
+ fil: undefined,
138
+ fin: 'fi',
139
+ fra: 'fr',
140
+ ful: 'ff',
141
+ glg: 'gl',
142
+ lug: 'lg',
143
+ kat: 'ka',
144
+ deu: 'de',
145
+ ell: 'el',
146
+ guj: 'gu',
147
+ hau: 'ha',
148
+ heb: 'he',
149
+ hin: 'hi',
150
+ hun: 'hu',
151
+ isl: 'is',
152
+ ibo: 'ig',
153
+ ind: 'id',
154
+ gle: 'ga',
155
+ ita: 'it',
156
+ jpn: 'ja',
157
+ jav: 'jv',
158
+ kea: undefined,
159
+ kan: 'kn',
160
+ kaz: 'kk',
161
+ khm: 'km',
162
+ kor: 'ko',
163
+ kur: 'ku',
164
+ kir: 'ky',
165
+ lao: 'lo',
166
+ lav: 'lv',
167
+ lin: 'ln',
168
+ lit: 'lt',
169
+ luo: undefined,
170
+ ltz: 'lb',
171
+ mkd: 'mk',
172
+ msa: 'ms',
173
+ mal: 'ml',
174
+ mlt: 'mt',
175
+ zho: 'zh',
176
+ mri: 'mi',
177
+ mar: 'mr',
178
+ mon: 'mn',
179
+ nep: 'ne',
180
+ nso: undefined,
181
+ nor: 'no',
182
+ oci: 'oc',
183
+ ori: 'or',
184
+ pus: 'ps',
185
+ fas: 'fa',
186
+ pol: 'pl',
187
+ por: 'pt',
188
+ pan: 'pa',
189
+ ron: 'ro',
190
+ rus: 'ru',
191
+ srp: 'sr',
192
+ sna: 'sn',
193
+ snd: 'sd',
194
+ slk: 'sk',
195
+ slv: 'sl',
196
+ som: 'so',
197
+ spa: 'es',
198
+ swa: 'sw',
199
+ swe: 'sv',
200
+ tam: 'ta',
201
+ tgk: 'tg',
202
+ tel: 'te',
203
+ tha: 'th',
204
+ tur: 'tr',
205
+ ukr: 'uk',
206
+ umb: undefined,
207
+ urd: 'ur',
208
+ uzb: 'uz',
209
+ vie: 'vi',
210
+ cym: 'cy',
211
+ wol: 'wo',
212
+ xho: 'xh',
213
+ zul: 'zu',
214
+ };
215
+
216
+ const LANGUAGE_NAMES_TO_CODE: Record<string, string> = {
217
+ afrikaans: 'af',
218
+ albanian: 'sq',
219
+ amharic: 'am',
220
+ arabic: 'ar',
221
+ armenian: 'hy',
222
+ azerbaijani: 'az',
223
+ basque: 'eu',
224
+ belarusian: 'be',
225
+ bengali: 'bn',
226
+ bosnian: 'bs',
227
+ bulgarian: 'bg',
228
+ burmese: 'my',
229
+ catalan: 'ca',
230
+ chinese: 'zh',
231
+ croatian: 'hr',
232
+ czech: 'cs',
233
+ danish: 'da',
234
+ dutch: 'nl',
235
+ english: 'en',
236
+ estonian: 'et',
237
+ finnish: 'fi',
238
+ french: 'fr',
239
+ galician: 'gl',
240
+ georgian: 'ka',
241
+ german: 'de',
242
+ greek: 'el',
243
+ gujarati: 'gu',
244
+ hausa: 'ha',
245
+ hebrew: 'he',
246
+ hindi: 'hi',
247
+ hungarian: 'hu',
248
+ icelandic: 'is',
249
+ indonesian: 'id',
250
+ irish: 'ga',
251
+ italian: 'it',
252
+ japanese: 'ja',
253
+ javanese: 'jv',
254
+ kannada: 'kn',
255
+ kazakh: 'kk',
256
+ khmer: 'km',
257
+ korean: 'ko',
258
+ kurdish: 'ku',
259
+ kyrgyz: 'ky',
260
+ lao: 'lo',
261
+ latvian: 'lv',
262
+ lingala: 'ln',
263
+ lithuanian: 'lt',
264
+ luxembourgish: 'lb',
265
+ macedonian: 'mk',
266
+ malay: 'ms',
267
+ malayalam: 'ml',
268
+ maltese: 'mt',
269
+ maori: 'mi',
270
+ marathi: 'mr',
271
+ mongolian: 'mn',
272
+ nepali: 'ne',
273
+ norwegian: 'no',
274
+ occitan: 'oc',
275
+ oriya: 'or',
276
+ pashto: 'ps',
277
+ persian: 'fa',
278
+ polish: 'pl',
279
+ portuguese: 'pt',
280
+ punjabi: 'pa',
281
+ romanian: 'ro',
282
+ russian: 'ru',
283
+ serbian: 'sr',
284
+ shona: 'sn',
285
+ sindhi: 'sd',
286
+ slovak: 'sk',
287
+ slovene: 'sl',
288
+ slovenian: 'sl',
289
+ somali: 'so',
290
+ spanish: 'es',
291
+ swahili: 'sw',
292
+ swedish: 'sv',
293
+ tagalog: 'tl',
294
+ tamil: 'ta',
295
+ tajik: 'tg',
296
+ telugu: 'te',
297
+ thai: 'th',
298
+ turkish: 'tr',
299
+ ukrainian: 'uk',
300
+ urdu: 'ur',
301
+ uzbek: 'uz',
302
+ vietnamese: 'vi',
303
+ welsh: 'cy',
304
+ wolof: 'wo',
305
+ xhosa: 'xh',
306
+ yoruba: 'yo',
307
+ zulu: 'zu',
308
+ };
309
+
310
+ const CODE_TO_LANGUAGE_NAME: Record<string, string> = Object.fromEntries(
311
+ Object.entries(LANGUAGE_NAMES_TO_CODE).map(([name, code]) => [code, name]),
312
+ );
313
+
314
+ CODE_TO_LANGUAGE_NAME.sl = 'slovene';
315
+
316
+ export function normalizeLanguage(language: string): LanguageCode {
317
+ const lowered = language.trim().toLowerCase();
318
+ if (lowered === '') {
319
+ return asLanguageCode('');
320
+ }
321
+
322
+ if (lowered in LANGUAGE_NAMES_TO_CODE) {
323
+ return asLanguageCode(LANGUAGE_NAMES_TO_CODE[lowered]!);
324
+ }
325
+
326
+ if (lowered in ISO_639_3_TO_1) {
327
+ return asLanguageCode(ISO_639_3_TO_1[lowered] ?? lowered);
328
+ }
329
+
330
+ const parts = lowered.replaceAll('_', '-').split('-');
331
+ if (parts.length >= 2) {
332
+ const [base, ...rest] = parts;
333
+ return asLanguageCode(
334
+ [
335
+ base,
336
+ ...rest.map((part) => {
337
+ if (part.length === 4) {
338
+ return part.charAt(0).toUpperCase() + part.slice(1);
339
+ }
340
+ return part.toUpperCase();
341
+ }),
342
+ ].join('-'),
343
+ );
344
+ }
345
+
346
+ return asLanguageCode(lowered);
347
+ }
348
+
349
+ export function getBaseLanguage(language: string): string {
350
+ const normalized = normalizeLanguage(language);
351
+ const [base = ''] = normalized.split('-');
352
+ return ISO_639_3_TO_1[base] ?? base;
353
+ }
354
+
355
+ export function getIsoLanguage(language: string): string {
356
+ const normalized = normalizeLanguage(language);
357
+ const region = getLanguageRegion(normalized);
358
+ const baseLanguage = getBaseLanguage(normalized);
359
+ return region ? `${baseLanguage}-${region}` : baseLanguage;
360
+ }
361
+
362
+ export function getLanguageRegion(language: string): string | undefined {
363
+ const normalized = normalizeLanguage(language);
364
+ const [, ...parts] = normalized.split('-');
365
+ return parts.find((part) => part.length === 2);
366
+ }
367
+
368
+ export function toLanguageName(language: string): string | undefined {
369
+ return CODE_TO_LANGUAGE_NAME[getBaseLanguage(language)];
370
+ }
371
+
372
+ export function areLanguagesEquivalent(
373
+ left: string | null | undefined,
374
+ right: string | null | undefined,
375
+ ): boolean {
376
+ if (left == null || right == null) {
377
+ return left === right;
378
+ }
379
+ return normalizeLanguage(left) === normalizeLanguage(right);
380
+ }
package/src/llm/index.ts CHANGED
@@ -65,8 +65,10 @@ export {
65
65
  executeToolCall,
66
66
  oaiBuildFunctionInfo,
67
67
  oaiParams,
68
+ serializeImage,
68
69
  toJsonSchema,
69
70
  type OpenAIFunctionParameters,
71
+ type SerializedImage,
70
72
  } from './utils.js';
71
73
 
72
74
  export {
@@ -25,9 +25,13 @@ export function isStreamReaderReleaseError(e: unknown) {
25
25
  'Invalid state: The reader is not attached to a stream',
26
26
  'Controller is already closed',
27
27
  'WritableStream is closed',
28
+ // bun
29
+ 'Stream reader cancelled via releaseLock()',
30
+ // deno
31
+ 'The reader was released.',
28
32
  ];
29
33
 
30
- if (e instanceof TypeError) {
34
+ if (e instanceof TypeError || (e instanceof Error && e.name === 'AbortError')) {
31
35
  return allowedMessages.some((message) => e.message.includes(message));
32
36
  }
33
37
 
package/src/stt/stt.ts CHANGED
@@ -7,6 +7,7 @@ import { EventEmitter } from 'node:events';
7
7
  import type { ReadableStream } from 'node:stream/web';
8
8
  import { APIConnectionError, APIError } from '../_exceptions.js';
9
9
  import { calculateAudioDurationSeconds } from '../audio.js';
10
+ import type { LanguageCode } from '../language.js';
10
11
  import { log } from '../log.js';
11
12
  import type { STTMetrics } from '../metrics/base.js';
12
13
  import { DeferredReadableStream } from '../stream/deferred_stream.js';
@@ -50,7 +51,7 @@ export enum SpeechEventType {
50
51
  /** SpeechData contains metadata about this {@link SpeechEvent}. */
51
52
  export interface SpeechData {
52
53
  /** Language code of the speech. */
53
- language: string;
54
+ language: LanguageCode;
54
55
  /** Transcribed text. */
55
56
  text: string;
56
57
  /** Start time of the speech segment in seconds. */
package/src/utils.ts CHANGED
@@ -846,7 +846,12 @@ export async function waitForParticipant({
846
846
  }
847
847
  };
848
848
 
849
+ const onDisconnected = () => {
850
+ fut.reject(new Error('Got disconnected from room while waiting for participant'));
851
+ };
852
+
849
853
  room.on(RoomEvent.ParticipantConnected, onParticipantConnected);
854
+ room.on(RoomEvent.Disconnected, onDisconnected);
850
855
 
851
856
  try {
852
857
  for (const p of room.remoteParticipants.values()) {
@@ -859,6 +864,7 @@ export async function waitForParticipant({
859
864
  return await fut.await;
860
865
  } finally {
861
866
  room.off(RoomEvent.ParticipantConnected, onParticipantConnected);
867
+ room.off(RoomEvent.Disconnected, onDisconnected);
862
868
  }
863
869
  }
864
870
 
@@ -953,3 +959,17 @@ export const isCloud = (url: URL) => {
953
959
  const hostname = url.hostname;
954
960
  return hostname.endsWith('.livekit.cloud') || hostname.endsWith('.livekit.run');
955
961
  };
962
+
963
+ /**
964
+ * Whether the agent is running in development mode (launched via `dev` or `connect`).
965
+ */
966
+ export const isDevMode = (): boolean => {
967
+ return process.env.LIVEKIT_DEV_MODE === '1';
968
+ };
969
+
970
+ /**
971
+ * Whether the agent is hosted on LiveKit Cloud.
972
+ */
973
+ export const isHosted = (): boolean => {
974
+ return process.env.LIVEKIT_REMOTE_EOT_URL !== undefined;
975
+ };