@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
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ var import_heap_js = require("heap-js");
3
+ var import_vitest = require("vitest");
4
+ var import_utils = require("../utils.cjs");
5
+ var import_agent_activity = require("./agent_activity.cjs");
6
+ var import_speech_handle = require("./speech_handle.cjs");
7
+ import_vitest.vi.mock("./agent.js", () => {
8
+ class Agent {
9
+ }
10
+ class AgentTask extends Agent {
11
+ }
12
+ class StopResponse {
13
+ }
14
+ return {
15
+ Agent,
16
+ AgentTask,
17
+ StopResponse,
18
+ _getActivityTaskInfo: () => null,
19
+ _setActivityTaskInfo: () => {
20
+ },
21
+ functionCallStorage: {
22
+ getStore: () => void 0,
23
+ enterWith: () => {
24
+ },
25
+ run: (_, fn) => fn()
26
+ },
27
+ speechHandleStorage: {
28
+ getStore: () => void 0,
29
+ enterWith: () => {
30
+ }
31
+ }
32
+ };
33
+ });
34
+ import_vitest.vi.mock("../version.js", () => ({ version: "0.0.0-test" }));
35
+ async function raceTimeout(promise, ms) {
36
+ let timer;
37
+ const timeout = new Promise((resolve) => {
38
+ timer = setTimeout(() => resolve("timeout"), ms);
39
+ });
40
+ return Promise.race([promise.then(() => "resolved"), timeout]).finally(
41
+ () => clearTimeout(timer)
42
+ );
43
+ }
44
+ function buildMainTaskRunner() {
45
+ const q_updated = new import_utils.Future();
46
+ const speechQueue = new import_heap_js.Heap((a, b) => b[0] - a[0] || a[1] - b[1]);
47
+ const fakeActivity = {
48
+ q_updated,
49
+ speechQueue,
50
+ _currentSpeech: void 0,
51
+ _schedulingPaused: false,
52
+ getDrainPendingSpeechTasks: () => [],
53
+ logger: {
54
+ info: () => {
55
+ },
56
+ debug: () => {
57
+ },
58
+ warn: () => {
59
+ },
60
+ error: () => {
61
+ }
62
+ }
63
+ };
64
+ const mainTask = import_agent_activity.AgentActivity.prototype.mainTask;
65
+ return {
66
+ fakeActivity,
67
+ mainTask: mainTask.bind(fakeActivity),
68
+ speechQueue,
69
+ q_updated
70
+ };
71
+ }
72
+ (0, import_vitest.describe)("AgentActivity - mainTask", () => {
73
+ (0, import_vitest.it)("should recover when speech handle is interrupted after authorization", async () => {
74
+ const { fakeActivity, mainTask, speechQueue, q_updated } = buildMainTaskRunner();
75
+ const handle = import_speech_handle.SpeechHandle.create({ allowInterruptions: true });
76
+ speechQueue.push([import_speech_handle.SpeechHandle.SPEECH_PRIORITY_NORMAL, 1, handle]);
77
+ handle._markScheduled();
78
+ q_updated.resolve();
79
+ const ac = new AbortController();
80
+ const mainTaskPromise = mainTask(ac.signal);
81
+ await new Promise((r) => setTimeout(r, 50));
82
+ handle.interrupt();
83
+ await new Promise((r) => setTimeout(r, 50));
84
+ fakeActivity._schedulingPaused = true;
85
+ fakeActivity.q_updated = new import_utils.Future();
86
+ fakeActivity.q_updated.resolve();
87
+ ac.abort();
88
+ const result = await raceTimeout(mainTaskPromise, 2e3);
89
+ (0, import_vitest.expect)(result).toBe("resolved");
90
+ });
91
+ (0, import_vitest.it)("should process next queued handle after an interrupted one", async () => {
92
+ const { fakeActivity, mainTask, speechQueue, q_updated } = buildMainTaskRunner();
93
+ const handleA = import_speech_handle.SpeechHandle.create({ allowInterruptions: true });
94
+ const handleB = import_speech_handle.SpeechHandle.create({ allowInterruptions: true });
95
+ speechQueue.push([import_speech_handle.SpeechHandle.SPEECH_PRIORITY_NORMAL, 1, handleA]);
96
+ handleA._markScheduled();
97
+ speechQueue.push([import_speech_handle.SpeechHandle.SPEECH_PRIORITY_NORMAL, 2, handleB]);
98
+ handleB._markScheduled();
99
+ q_updated.resolve();
100
+ const ac = new AbortController();
101
+ const mainTaskPromise = mainTask(ac.signal);
102
+ await new Promise((r) => setTimeout(r, 50));
103
+ handleA.interrupt();
104
+ await new Promise((r) => setTimeout(r, 50));
105
+ try {
106
+ handleB._markGenerationDone();
107
+ } catch {
108
+ }
109
+ await new Promise((r) => setTimeout(r, 50));
110
+ fakeActivity._schedulingPaused = true;
111
+ fakeActivity.q_updated = new import_utils.Future();
112
+ fakeActivity.q_updated.resolve();
113
+ ac.abort();
114
+ const result = await raceTimeout(mainTaskPromise, 2e3);
115
+ (0, import_vitest.expect)(result).toBe("resolved");
116
+ });
117
+ (0, import_vitest.it)("should skip handles that were interrupted before being popped", async () => {
118
+ const { fakeActivity, mainTask, speechQueue, q_updated } = buildMainTaskRunner();
119
+ const handle = import_speech_handle.SpeechHandle.create({ allowInterruptions: true });
120
+ handle.interrupt();
121
+ speechQueue.push([import_speech_handle.SpeechHandle.SPEECH_PRIORITY_NORMAL, 1, handle]);
122
+ handle._markScheduled();
123
+ q_updated.resolve();
124
+ const ac = new AbortController();
125
+ const mainTaskPromise = mainTask(ac.signal);
126
+ await new Promise((r) => setTimeout(r, 50));
127
+ fakeActivity._schedulingPaused = true;
128
+ fakeActivity.q_updated = new import_utils.Future();
129
+ fakeActivity.q_updated.resolve();
130
+ ac.abort();
131
+ const result = await raceTimeout(mainTaskPromise, 2e3);
132
+ (0, import_vitest.expect)(result).toBe("resolved");
133
+ });
134
+ });
135
+ //# sourceMappingURL=agent_activity.test.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/voice/agent_activity.test.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Regression tests for mainTask speech handle processing.\n *\n * When a speech handle is interrupted after _authorizeGeneration() but before the\n * reply task calls _markGenerationDone(), mainTask hangs on _waitForGeneration()\n * indefinitely. All subsequent speech handles queue behind it and the agent becomes\n * unresponsive.\n *\n * Fix: race _waitForGeneration() against the interrupt future via waitIfNotInterrupted().\n *\n * Related: #1124, #1089, #836\n */\nimport { Heap } from 'heap-js';\nimport { describe, expect, it, vi } from 'vitest';\nimport { Future } from '../utils.js';\nimport { AgentActivity } from './agent_activity.js';\nimport { SpeechHandle } from './speech_handle.js';\n\n// Break circular dependency: agent_activity.ts → agent.js → beta/workflows/task_group.ts\nvi.mock('./agent.js', () => {\n class Agent {}\n class AgentTask extends Agent {}\n class StopResponse {}\n return {\n Agent,\n AgentTask,\n StopResponse,\n _getActivityTaskInfo: () => null,\n _setActivityTaskInfo: () => {},\n functionCallStorage: {\n getStore: () => undefined,\n enterWith: () => {},\n run: (_: unknown, fn: () => unknown) => fn(),\n },\n speechHandleStorage: {\n getStore: () => undefined,\n enterWith: () => {},\n },\n };\n});\n\nvi.mock('../version.js', () => ({ version: '0.0.0-test' }));\n\nasync function raceTimeout(promise: Promise<unknown>, ms: number): Promise<'resolved' | 'timeout'> {\n let timer: ReturnType<typeof setTimeout>;\n const timeout = new Promise<'timeout'>((resolve) => {\n timer = setTimeout(() => resolve('timeout'), ms);\n });\n return Promise.race([promise.then(() => 'resolved' as const), timeout]).finally(() =>\n clearTimeout(timer),\n );\n}\n\n/**\n * Build a minimal stand-in with just enough state for mainTask to run.\n *\n * mainTask accesses: q_updated, speechQueue, _currentSpeech, _schedulingPaused,\n * getDrainPendingSpeechTasks(), and logger. We provide stubs for all of these,\n * then bind the real AgentActivity.prototype.mainTask to this object.\n */\nfunction buildMainTaskRunner() {\n const q_updated = new Future<void>();\n type HeapItem = [number, number, SpeechHandle];\n const speechQueue = new Heap<HeapItem>((a: HeapItem, b: HeapItem) => b[0] - a[0] || a[1] - b[1]);\n\n const fakeActivity = {\n q_updated,\n speechQueue,\n _currentSpeech: undefined as SpeechHandle | undefined,\n _schedulingPaused: false,\n getDrainPendingSpeechTasks: () => [],\n logger: {\n info: () => {},\n debug: () => {},\n warn: () => {},\n error: () => {},\n },\n };\n\n const mainTask = (AgentActivity.prototype as Record<string, unknown>).mainTask as (\n signal: AbortSignal,\n ) => Promise<void>;\n\n return {\n fakeActivity,\n mainTask: mainTask.bind(fakeActivity),\n speechQueue,\n q_updated,\n };\n}\n\ndescribe('AgentActivity - mainTask', () => {\n it('should recover when speech handle is interrupted after authorization', async () => {\n const { fakeActivity, mainTask, speechQueue, q_updated } = buildMainTaskRunner();\n\n const handle = SpeechHandle.create({ allowInterruptions: true });\n\n speechQueue.push([SpeechHandle.SPEECH_PRIORITY_NORMAL, 1, handle]);\n handle._markScheduled();\n q_updated.resolve();\n\n const ac = new AbortController();\n const mainTaskPromise = mainTask(ac.signal);\n\n // Give mainTask time to pop the handle and call _authorizeGeneration\n await new Promise((r) => setTimeout(r, 50));\n\n // Interrupt while waiting for generation\n handle.interrupt();\n\n // Let mainTask react to the interrupt, then signal exit\n await new Promise((r) => setTimeout(r, 50));\n fakeActivity._schedulingPaused = true;\n fakeActivity.q_updated = new Future();\n fakeActivity.q_updated.resolve();\n ac.abort();\n\n const result = await raceTimeout(mainTaskPromise, 2000);\n expect(result).toBe('resolved');\n });\n\n it('should process next queued handle after an interrupted one', async () => {\n const { fakeActivity, mainTask, speechQueue, q_updated } = buildMainTaskRunner();\n\n const handleA = SpeechHandle.create({ allowInterruptions: true });\n const handleB = SpeechHandle.create({ allowInterruptions: true });\n\n speechQueue.push([SpeechHandle.SPEECH_PRIORITY_NORMAL, 1, handleA]);\n handleA._markScheduled();\n speechQueue.push([SpeechHandle.SPEECH_PRIORITY_NORMAL, 2, handleB]);\n handleB._markScheduled();\n q_updated.resolve();\n\n const ac = new AbortController();\n const mainTaskPromise = mainTask(ac.signal);\n\n // Wait for mainTask to pick up handle A\n await new Promise((r) => setTimeout(r, 50));\n\n // Interrupt handle A\n handleA.interrupt();\n\n // Wait for mainTask to move to handle B and authorize it\n await new Promise((r) => setTimeout(r, 50));\n\n // Resolve handle B's generation (simulating normal reply task completion).\n // If mainTask is stuck on handle A (bug), handle B was never authorized and this\n // throws — we catch it and let the timeout assert the real failure.\n try {\n handleB._markGenerationDone();\n } catch {\n // Expected when fix is absent: handle B has no active generation\n }\n\n // Let mainTask finish\n await new Promise((r) => setTimeout(r, 50));\n fakeActivity._schedulingPaused = true;\n fakeActivity.q_updated = new Future();\n fakeActivity.q_updated.resolve();\n ac.abort();\n\n const result = await raceTimeout(mainTaskPromise, 2000);\n expect(result).toBe('resolved');\n });\n\n it('should skip handles that were interrupted before being popped', async () => {\n const { fakeActivity, mainTask, speechQueue, q_updated } = buildMainTaskRunner();\n\n const handle = SpeechHandle.create({ allowInterruptions: true });\n\n // Interrupt before mainTask ever sees it\n handle.interrupt();\n\n speechQueue.push([SpeechHandle.SPEECH_PRIORITY_NORMAL, 1, handle]);\n handle._markScheduled();\n q_updated.resolve();\n\n const ac = new AbortController();\n const mainTaskPromise = mainTask(ac.signal);\n\n await new Promise((r) => setTimeout(r, 50));\n fakeActivity._schedulingPaused = true;\n fakeActivity.q_updated = new Future();\n fakeActivity.q_updated.resolve();\n ac.abort();\n\n const result = await raceTimeout(mainTaskPromise, 2000);\n expect(result).toBe('resolved');\n });\n});\n"],"mappings":"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","names":[]}
@@ -0,0 +1,134 @@
1
+ import { Heap } from "heap-js";
2
+ import { describe, expect, it, vi } from "vitest";
3
+ import { Future } from "../utils.js";
4
+ import { AgentActivity } from "./agent_activity.js";
5
+ import { SpeechHandle } from "./speech_handle.js";
6
+ vi.mock("./agent.js", () => {
7
+ class Agent {
8
+ }
9
+ class AgentTask extends Agent {
10
+ }
11
+ class StopResponse {
12
+ }
13
+ return {
14
+ Agent,
15
+ AgentTask,
16
+ StopResponse,
17
+ _getActivityTaskInfo: () => null,
18
+ _setActivityTaskInfo: () => {
19
+ },
20
+ functionCallStorage: {
21
+ getStore: () => void 0,
22
+ enterWith: () => {
23
+ },
24
+ run: (_, fn) => fn()
25
+ },
26
+ speechHandleStorage: {
27
+ getStore: () => void 0,
28
+ enterWith: () => {
29
+ }
30
+ }
31
+ };
32
+ });
33
+ vi.mock("../version.js", () => ({ version: "0.0.0-test" }));
34
+ async function raceTimeout(promise, ms) {
35
+ let timer;
36
+ const timeout = new Promise((resolve) => {
37
+ timer = setTimeout(() => resolve("timeout"), ms);
38
+ });
39
+ return Promise.race([promise.then(() => "resolved"), timeout]).finally(
40
+ () => clearTimeout(timer)
41
+ );
42
+ }
43
+ function buildMainTaskRunner() {
44
+ const q_updated = new Future();
45
+ const speechQueue = new Heap((a, b) => b[0] - a[0] || a[1] - b[1]);
46
+ const fakeActivity = {
47
+ q_updated,
48
+ speechQueue,
49
+ _currentSpeech: void 0,
50
+ _schedulingPaused: false,
51
+ getDrainPendingSpeechTasks: () => [],
52
+ logger: {
53
+ info: () => {
54
+ },
55
+ debug: () => {
56
+ },
57
+ warn: () => {
58
+ },
59
+ error: () => {
60
+ }
61
+ }
62
+ };
63
+ const mainTask = AgentActivity.prototype.mainTask;
64
+ return {
65
+ fakeActivity,
66
+ mainTask: mainTask.bind(fakeActivity),
67
+ speechQueue,
68
+ q_updated
69
+ };
70
+ }
71
+ describe("AgentActivity - mainTask", () => {
72
+ it("should recover when speech handle is interrupted after authorization", async () => {
73
+ const { fakeActivity, mainTask, speechQueue, q_updated } = buildMainTaskRunner();
74
+ const handle = SpeechHandle.create({ allowInterruptions: true });
75
+ speechQueue.push([SpeechHandle.SPEECH_PRIORITY_NORMAL, 1, handle]);
76
+ handle._markScheduled();
77
+ q_updated.resolve();
78
+ const ac = new AbortController();
79
+ const mainTaskPromise = mainTask(ac.signal);
80
+ await new Promise((r) => setTimeout(r, 50));
81
+ handle.interrupt();
82
+ await new Promise((r) => setTimeout(r, 50));
83
+ fakeActivity._schedulingPaused = true;
84
+ fakeActivity.q_updated = new Future();
85
+ fakeActivity.q_updated.resolve();
86
+ ac.abort();
87
+ const result = await raceTimeout(mainTaskPromise, 2e3);
88
+ expect(result).toBe("resolved");
89
+ });
90
+ it("should process next queued handle after an interrupted one", async () => {
91
+ const { fakeActivity, mainTask, speechQueue, q_updated } = buildMainTaskRunner();
92
+ const handleA = SpeechHandle.create({ allowInterruptions: true });
93
+ const handleB = SpeechHandle.create({ allowInterruptions: true });
94
+ speechQueue.push([SpeechHandle.SPEECH_PRIORITY_NORMAL, 1, handleA]);
95
+ handleA._markScheduled();
96
+ speechQueue.push([SpeechHandle.SPEECH_PRIORITY_NORMAL, 2, handleB]);
97
+ handleB._markScheduled();
98
+ q_updated.resolve();
99
+ const ac = new AbortController();
100
+ const mainTaskPromise = mainTask(ac.signal);
101
+ await new Promise((r) => setTimeout(r, 50));
102
+ handleA.interrupt();
103
+ await new Promise((r) => setTimeout(r, 50));
104
+ try {
105
+ handleB._markGenerationDone();
106
+ } catch {
107
+ }
108
+ await new Promise((r) => setTimeout(r, 50));
109
+ fakeActivity._schedulingPaused = true;
110
+ fakeActivity.q_updated = new Future();
111
+ fakeActivity.q_updated.resolve();
112
+ ac.abort();
113
+ const result = await raceTimeout(mainTaskPromise, 2e3);
114
+ expect(result).toBe("resolved");
115
+ });
116
+ it("should skip handles that were interrupted before being popped", async () => {
117
+ const { fakeActivity, mainTask, speechQueue, q_updated } = buildMainTaskRunner();
118
+ const handle = SpeechHandle.create({ allowInterruptions: true });
119
+ handle.interrupt();
120
+ speechQueue.push([SpeechHandle.SPEECH_PRIORITY_NORMAL, 1, handle]);
121
+ handle._markScheduled();
122
+ q_updated.resolve();
123
+ const ac = new AbortController();
124
+ const mainTaskPromise = mainTask(ac.signal);
125
+ await new Promise((r) => setTimeout(r, 50));
126
+ fakeActivity._schedulingPaused = true;
127
+ fakeActivity.q_updated = new Future();
128
+ fakeActivity.q_updated.resolve();
129
+ ac.abort();
130
+ const result = await raceTimeout(mainTaskPromise, 2e3);
131
+ expect(result).toBe("resolved");
132
+ });
133
+ });
134
+ //# sourceMappingURL=agent_activity.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/voice/agent_activity.test.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Regression tests for mainTask speech handle processing.\n *\n * When a speech handle is interrupted after _authorizeGeneration() but before the\n * reply task calls _markGenerationDone(), mainTask hangs on _waitForGeneration()\n * indefinitely. All subsequent speech handles queue behind it and the agent becomes\n * unresponsive.\n *\n * Fix: race _waitForGeneration() against the interrupt future via waitIfNotInterrupted().\n *\n * Related: #1124, #1089, #836\n */\nimport { Heap } from 'heap-js';\nimport { describe, expect, it, vi } from 'vitest';\nimport { Future } from '../utils.js';\nimport { AgentActivity } from './agent_activity.js';\nimport { SpeechHandle } from './speech_handle.js';\n\n// Break circular dependency: agent_activity.ts → agent.js → beta/workflows/task_group.ts\nvi.mock('./agent.js', () => {\n class Agent {}\n class AgentTask extends Agent {}\n class StopResponse {}\n return {\n Agent,\n AgentTask,\n StopResponse,\n _getActivityTaskInfo: () => null,\n _setActivityTaskInfo: () => {},\n functionCallStorage: {\n getStore: () => undefined,\n enterWith: () => {},\n run: (_: unknown, fn: () => unknown) => fn(),\n },\n speechHandleStorage: {\n getStore: () => undefined,\n enterWith: () => {},\n },\n };\n});\n\nvi.mock('../version.js', () => ({ version: '0.0.0-test' }));\n\nasync function raceTimeout(promise: Promise<unknown>, ms: number): Promise<'resolved' | 'timeout'> {\n let timer: ReturnType<typeof setTimeout>;\n const timeout = new Promise<'timeout'>((resolve) => {\n timer = setTimeout(() => resolve('timeout'), ms);\n });\n return Promise.race([promise.then(() => 'resolved' as const), timeout]).finally(() =>\n clearTimeout(timer),\n );\n}\n\n/**\n * Build a minimal stand-in with just enough state for mainTask to run.\n *\n * mainTask accesses: q_updated, speechQueue, _currentSpeech, _schedulingPaused,\n * getDrainPendingSpeechTasks(), and logger. We provide stubs for all of these,\n * then bind the real AgentActivity.prototype.mainTask to this object.\n */\nfunction buildMainTaskRunner() {\n const q_updated = new Future<void>();\n type HeapItem = [number, number, SpeechHandle];\n const speechQueue = new Heap<HeapItem>((a: HeapItem, b: HeapItem) => b[0] - a[0] || a[1] - b[1]);\n\n const fakeActivity = {\n q_updated,\n speechQueue,\n _currentSpeech: undefined as SpeechHandle | undefined,\n _schedulingPaused: false,\n getDrainPendingSpeechTasks: () => [],\n logger: {\n info: () => {},\n debug: () => {},\n warn: () => {},\n error: () => {},\n },\n };\n\n const mainTask = (AgentActivity.prototype as Record<string, unknown>).mainTask as (\n signal: AbortSignal,\n ) => Promise<void>;\n\n return {\n fakeActivity,\n mainTask: mainTask.bind(fakeActivity),\n speechQueue,\n q_updated,\n };\n}\n\ndescribe('AgentActivity - mainTask', () => {\n it('should recover when speech handle is interrupted after authorization', async () => {\n const { fakeActivity, mainTask, speechQueue, q_updated } = buildMainTaskRunner();\n\n const handle = SpeechHandle.create({ allowInterruptions: true });\n\n speechQueue.push([SpeechHandle.SPEECH_PRIORITY_NORMAL, 1, handle]);\n handle._markScheduled();\n q_updated.resolve();\n\n const ac = new AbortController();\n const mainTaskPromise = mainTask(ac.signal);\n\n // Give mainTask time to pop the handle and call _authorizeGeneration\n await new Promise((r) => setTimeout(r, 50));\n\n // Interrupt while waiting for generation\n handle.interrupt();\n\n // Let mainTask react to the interrupt, then signal exit\n await new Promise((r) => setTimeout(r, 50));\n fakeActivity._schedulingPaused = true;\n fakeActivity.q_updated = new Future();\n fakeActivity.q_updated.resolve();\n ac.abort();\n\n const result = await raceTimeout(mainTaskPromise, 2000);\n expect(result).toBe('resolved');\n });\n\n it('should process next queued handle after an interrupted one', async () => {\n const { fakeActivity, mainTask, speechQueue, q_updated } = buildMainTaskRunner();\n\n const handleA = SpeechHandle.create({ allowInterruptions: true });\n const handleB = SpeechHandle.create({ allowInterruptions: true });\n\n speechQueue.push([SpeechHandle.SPEECH_PRIORITY_NORMAL, 1, handleA]);\n handleA._markScheduled();\n speechQueue.push([SpeechHandle.SPEECH_PRIORITY_NORMAL, 2, handleB]);\n handleB._markScheduled();\n q_updated.resolve();\n\n const ac = new AbortController();\n const mainTaskPromise = mainTask(ac.signal);\n\n // Wait for mainTask to pick up handle A\n await new Promise((r) => setTimeout(r, 50));\n\n // Interrupt handle A\n handleA.interrupt();\n\n // Wait for mainTask to move to handle B and authorize it\n await new Promise((r) => setTimeout(r, 50));\n\n // Resolve handle B's generation (simulating normal reply task completion).\n // If mainTask is stuck on handle A (bug), handle B was never authorized and this\n // throws — we catch it and let the timeout assert the real failure.\n try {\n handleB._markGenerationDone();\n } catch {\n // Expected when fix is absent: handle B has no active generation\n }\n\n // Let mainTask finish\n await new Promise((r) => setTimeout(r, 50));\n fakeActivity._schedulingPaused = true;\n fakeActivity.q_updated = new Future();\n fakeActivity.q_updated.resolve();\n ac.abort();\n\n const result = await raceTimeout(mainTaskPromise, 2000);\n expect(result).toBe('resolved');\n });\n\n it('should skip handles that were interrupted before being popped', async () => {\n const { fakeActivity, mainTask, speechQueue, q_updated } = buildMainTaskRunner();\n\n const handle = SpeechHandle.create({ allowInterruptions: true });\n\n // Interrupt before mainTask ever sees it\n handle.interrupt();\n\n speechQueue.push([SpeechHandle.SPEECH_PRIORITY_NORMAL, 1, handle]);\n handle._markScheduled();\n q_updated.resolve();\n\n const ac = new AbortController();\n const mainTaskPromise = mainTask(ac.signal);\n\n await new Promise((r) => setTimeout(r, 50));\n fakeActivity._schedulingPaused = true;\n fakeActivity.q_updated = new Future();\n fakeActivity.q_updated.resolve();\n ac.abort();\n\n const result = await raceTimeout(mainTaskPromise, 2000);\n expect(result).toBe('resolved');\n });\n});\n"],"mappings":"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","names":[]}
@@ -19,7 +19,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
19
19
  var agent_session_exports = {};
20
20
  __export(agent_session_exports, {
21
21
  AgentSession: () => AgentSession,
22
- defaultSessionOptions: () => defaultSessionOptions
22
+ defaultAgentSessionOptions: () => defaultAgentSessionOptions
23
23
  });
24
24
  module.exports = __toCommonJS(agent_session_exports);
25
25
  var import_mutex = require("@livekit/mutex");
@@ -34,17 +34,17 @@ var import_telemetry = require("../telemetry/index.cjs");
34
34
  var import_types = require("../types.cjs");
35
35
  var import_utils = require("../utils.cjs");
36
36
  var import_agent_activity = require("./agent_activity.cjs");
37
- var import_client_events = require("./client_events.cjs");
38
37
  var import_events = require("./events.cjs");
39
38
  var import_io = require("./io.cjs");
40
39
  var import_recorder_io = require("./recorder_io/index.cjs");
40
+ var import_remote_session = require("./remote_session.cjs");
41
41
  var import_room_io = require("./room_io/index.cjs");
42
42
  var import_run_result = require("./testing/run_result.cjs");
43
43
  var import_utils2 = require("./turn_config/utils.cjs");
44
44
  var import_utils3 = require("./utils.cjs");
45
- const defaultSessionOptions = {
45
+ const defaultAgentSessionOptions = {
46
46
  maxToolSteps: 3,
47
- preemptiveGeneration: false,
47
+ preemptiveGeneration: true,
48
48
  userAwayTimeout: 15,
49
49
  aecWarmupDuration: 3e3,
50
50
  turnHandling: {},
@@ -56,14 +56,16 @@ class AgentSession extends import_node_events.EventEmitter {
56
56
  llm;
57
57
  tts;
58
58
  turnDetection;
59
+ /** @deprecated use {@link sessionOptions } instead */
59
60
  options;
61
+ sessionOptions;
60
62
  activityLock = new import_mutex.Mutex();
61
63
  agent;
62
64
  activity;
63
65
  nextActivity;
64
66
  updateActivityTask;
65
67
  started = false;
66
- clientEventsHandler;
68
+ sessionHost;
67
69
  _chatCtx;
68
70
  _userData;
69
71
  _userState = "listening";
@@ -78,10 +80,10 @@ class AgentSession extends import_node_events.EventEmitter {
78
80
  // Unrecoverable error counts, reset after agent speaking
79
81
  llmErrorCounts = 0;
80
82
  ttsErrorCounts = 0;
81
- interruptionDetectionErrorCounts = 0;
82
83
  sessionSpan;
83
84
  agentSpeakingSpan;
84
85
  _interruptionDetection;
86
+ /** @internal */
85
87
  _usageCollector = new import_model_usage.ModelUsageCollector();
86
88
  /** @internal */
87
89
  _roomIO;
@@ -103,10 +105,10 @@ class AgentSession extends import_node_events.EventEmitter {
103
105
  _userSpeakingSpan;
104
106
  logger = (0, import_log.log)();
105
107
  constructor(options) {
106
- var _a, _b, _c;
108
+ var _a;
107
109
  super();
108
- const opts = (0, import_utils2.migrateLegacyOptions)(options);
109
- const { vad, stt, llm, tts, userData, connOptions, options: sessionOptions } = opts;
110
+ const { agentSessionOptions: opts, legacyVoiceOptions } = (0, import_utils2.migrateLegacyOptions)(options);
111
+ const { vad, stt, llm, tts, userData, connOptions, ...resolvedSessionOptions } = opts;
110
112
  this._connOptions = {
111
113
  sttConnOptions: { ...import_types.DEFAULT_API_CONNECT_OPTIONS, ...connOptions == null ? void 0 : connOptions.sttConnOptions },
112
114
  llmConnOptions: { ...import_types.DEFAULT_API_CONNECT_OPTIONS, ...connOptions == null ? void 0 : connOptions.llmConnOptions },
@@ -129,23 +131,21 @@ class AgentSession extends import_node_events.EventEmitter {
129
131
  } else {
130
132
  this.tts = tts;
131
133
  }
132
- this.turnDetection = (_a = sessionOptions == null ? void 0 : sessionOptions.turnHandling) == null ? void 0 : _a.turnDetection;
133
- this._interruptionDetection = (_c = (_b = sessionOptions == null ? void 0 : sessionOptions.turnHandling) == null ? void 0 : _b.interruption) == null ? void 0 : _c.mode;
134
+ this.turnDetection = resolvedSessionOptions.turnHandling.turnDetection;
135
+ this._interruptionDetection = (_a = resolvedSessionOptions.turnHandling.interruption) == null ? void 0 : _a.mode;
134
136
  this._userData = userData;
135
137
  this._input = new import_io.AgentInput(this.onAudioInputChanged);
136
138
  this._output = new import_io.AgentOutput(this.onAudioOutputChanged, this.onTextOutputChanged);
137
139
  this._chatCtx = import_chat_context.ChatContext.empty();
138
- this.options = opts.options;
139
- this._aecWarmupRemaining = this.options.aecWarmupDuration ?? 0;
140
+ this.sessionOptions = resolvedSessionOptions;
141
+ this.options = legacyVoiceOptions;
142
+ this._aecWarmupRemaining = this.sessionOptions.aecWarmupDuration ?? 0;
140
143
  this._onUserInputTranscribed = this._onUserInputTranscribed.bind(this);
141
144
  this.on(import_events.AgentSessionEventTypes.UserInputTranscribed, this._onUserInputTranscribed);
142
145
  }
143
146
  emit(event, ...args) {
144
147
  const eventData = args[0];
145
148
  this._recordedEvents.push(eventData);
146
- if (event === import_events.AgentSessionEventTypes.MetricsCollected) {
147
- this._usageCollector.collect(eventData.metrics);
148
- }
149
149
  return super.emit(event, ...args);
150
150
  }
151
151
  get input() {
@@ -177,7 +177,7 @@ class AgentSession extends import_node_events.EventEmitter {
177
177
  return { modelUsage: this._usageCollector.flatten().map(import_model_usage.filterZeroValues) };
178
178
  }
179
179
  get useTtsAlignedTranscript() {
180
- return this.options.useTtsAlignedTranscript;
180
+ return this.sessionOptions.useTtsAlignedTranscript;
181
181
  }
182
182
  set userData(value) {
183
183
  this._userData = value;
@@ -216,9 +216,11 @@ class AgentSession extends import_node_events.EventEmitter {
216
216
  outputOptions
217
217
  });
218
218
  this._roomIO.start();
219
- this.clientEventsHandler = new import_client_events.ClientEventsHandler(this, this._roomIO);
219
+ const transport = new import_remote_session.RoomSessionTransport(room, this._roomIO);
220
+ this.sessionHost = new import_remote_session.SessionHost(transport);
221
+ this.sessionHost.registerSession(this);
220
222
  if ((inputOptions == null ? void 0 : inputOptions.textEnabled) !== false) {
221
- this.clientEventsHandler.registerTextInput(
223
+ this.sessionHost.registerTextInput(
222
224
  (inputOptions == null ? void 0 : inputOptions.textInputCallback) ?? import_room_io.DEFAULT_TEXT_INPUT_CALLBACK
223
225
  );
224
226
  }
@@ -252,8 +254,8 @@ class AgentSession extends import_node_events.EventEmitter {
252
254
  }
253
255
  tasks.push(this._updateActivity(this.agent, { waitOnEnter: false }));
254
256
  await Promise.allSettled(tasks);
255
- if (this.clientEventsHandler) {
256
- await this.clientEventsHandler.start();
257
+ if (this.sessionHost) {
258
+ await this.sessionHost.start();
257
259
  }
258
260
  this.logger.debug(
259
261
  `using audio io: ${this.input.audio ? "`" + this.input.audio.constructor.name + "`" : "(none)"} -> \`AgentSession\` -> ${this.output.audio ? "`" + this.output.audio.constructor.name + "`" : "(none)"}`
@@ -554,7 +556,9 @@ class AgentSession extends import_node_events.EventEmitter {
554
556
  if (this.closingTask) {
555
557
  return;
556
558
  }
557
- this.closeImpl(reason, error, drain);
559
+ this.closingTask = this.closeImpl(reason, error, drain).finally(() => {
560
+ this.closingTask = null;
561
+ });
558
562
  }
559
563
  /** @internal */
560
564
  _onError(error) {
@@ -572,12 +576,10 @@ class AgentSession extends import_node_events.EventEmitter {
572
576
  return;
573
577
  }
574
578
  } else if (error.type === "interruption_detection_error") {
575
- this.interruptionDetectionErrorCounts += 1;
576
- if (this.interruptionDetectionErrorCounts <= this._connOptions.maxUnrecoverableErrors) {
577
- return;
578
- }
579
+ this.logger.error(error.toString());
580
+ return;
579
581
  }
580
- this.logger.error(error, "AgentSession is closing due to unrecoverable error");
582
+ this.logger.error(error, "AgentSession is closing due to an unrecoverable error");
581
583
  this.closingTask = (async () => {
582
584
  await this.closeImpl(import_events.CloseReason.ERROR, error);
583
585
  })().then(() => {
@@ -602,7 +604,6 @@ class AgentSession extends import_node_events.EventEmitter {
602
604
  if (state === "speaking") {
603
605
  this.llmErrorCounts = 0;
604
606
  this.ttsErrorCounts = 0;
605
- this.interruptionDetectionErrorCounts = 0;
606
607
  if (this.agentSpeakingSpan === void 0) {
607
608
  this.agentSpeakingSpan = import_telemetry.tracer.startSpan({
608
609
  name: "agent_speaking",
@@ -638,7 +639,7 @@ class AgentSession extends import_node_events.EventEmitter {
638
639
  );
639
640
  }
640
641
  /** @internal */
641
- _updateUserState(state, lastSpeakingTime) {
642
+ _updateUserState(state, options) {
642
643
  var _a;
643
644
  if (this._userState === state) {
644
645
  return;
@@ -646,15 +647,15 @@ class AgentSession extends import_node_events.EventEmitter {
646
647
  if (state === "speaking" && this._userSpeakingSpan === void 0) {
647
648
  this._userSpeakingSpan = import_telemetry.tracer.startSpan({
648
649
  name: "user_speaking",
649
- context: this.rootSpanContext,
650
- startTime: lastSpeakingTime
650
+ context: (options == null ? void 0 : options.otelContext) ?? this.rootSpanContext,
651
+ startTime: options == null ? void 0 : options.lastSpeakingTime
651
652
  });
652
653
  const linked = (_a = this._roomIO) == null ? void 0 : _a.linkedParticipant;
653
654
  if (linked) {
654
655
  (0, import_utils3.setParticipantSpanAttributes)(this._userSpeakingSpan, linked);
655
656
  }
656
657
  } else if (this._userSpeakingSpan !== void 0) {
657
- this._userSpeakingSpan.end(lastSpeakingTime);
658
+ this._userSpeakingSpan.end(options == null ? void 0 : options.lastSpeakingTime);
658
659
  this._userSpeakingSpan = void 0;
659
660
  }
660
661
  const oldState = this._userState;
@@ -684,7 +685,7 @@ class AgentSession extends import_node_events.EventEmitter {
684
685
  }
685
686
  _setUserAwayTimer() {
686
687
  this._cancelUserAwayTimer();
687
- if (this.options.userAwayTimeout === null || this.options.userAwayTimeout === void 0) {
688
+ if (this.sessionOptions.userAwayTimeout === null || this.sessionOptions.userAwayTimeout === void 0) {
688
689
  return;
689
690
  }
690
691
  if (this._roomIO && !this._roomIO.isParticipantAvailable) {
@@ -693,7 +694,7 @@ class AgentSession extends import_node_events.EventEmitter {
693
694
  this.userAwayTimer = setTimeout(() => {
694
695
  this.logger.debug("User away timeout triggered");
695
696
  this._updateUserState("away");
696
- }, this.options.userAwayTimeout * 1e3);
697
+ }, this.sessionOptions.userAwayTimeout * 1e3);
697
698
  }
698
699
  _cancelUserAwayTimer() {
699
700
  if (this.userAwayTimer !== null) {
@@ -758,8 +759,8 @@ class AgentSession extends import_node_events.EventEmitter {
758
759
  this.input.audio = null;
759
760
  this.output.audio = null;
760
761
  this.output.transcription = null;
761
- await ((_b = this.clientEventsHandler) == null ? void 0 : _b.close());
762
- this.clientEventsHandler = void 0;
762
+ await ((_b = this.sessionHost) == null ? void 0 : _b.close());
763
+ this.sessionHost = void 0;
763
764
  await ((_c = this._roomIO) == null ? void 0 : _c.close());
764
765
  this._roomIO = void 0;
765
766
  await ((_d = this.activity) == null ? void 0 : _d.close());
@@ -783,13 +784,12 @@ class AgentSession extends import_node_events.EventEmitter {
783
784
  this.rootSpanContext = void 0;
784
785
  this.llmErrorCounts = 0;
785
786
  this.ttsErrorCounts = 0;
786
- this.interruptionDetectionErrorCounts = 0;
787
787
  this.logger.info({ reason, error }, "AgentSession closed");
788
788
  }
789
789
  }
790
790
  // Annotate the CommonJS export names for ESM import in node:
791
791
  0 && (module.exports = {
792
792
  AgentSession,
793
- defaultSessionOptions
793
+ defaultAgentSessionOptions
794
794
  });
795
795
  //# sourceMappingURL=agent_session.cjs.map