@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 +1 @@
1
- {"version":3,"sources":["../../../src/voice/room_io/room_io.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport {\n type AudioFrame,\n ConnectionState,\n DisconnectReason,\n type FrameProcessor,\n type NoiseCancellationOptions,\n type Participant,\n ParticipantKind,\n type RemoteParticipant,\n type Room,\n RoomEvent,\n TrackPublishOptions,\n TrackSource,\n} from '@livekit/rtc-node';\nimport type { WritableStreamDefaultWriter } from 'node:stream/web';\nimport { ATTRIBUTE_PUBLISH_ON_BEHALF } from '../../constants.js';\nimport { log } from '../../log.js';\nimport { IdentityTransform } from '../../stream/identity_transform.js';\nimport { Future, Task, waitForAbort } from '../../utils.js';\nimport { type AgentSession } from '../agent_session.js';\nimport type { TextInputCallback } from '../client_events.js';\nimport {\n AgentSessionEventTypes,\n type AgentStateChangedEvent,\n CloseReason,\n type UserInputTranscribedEvent,\n} from '../events.js';\nimport type { AudioOutput, TextOutput } from '../io.js';\nimport { TranscriptionSynchronizer } from '../transcription/synchronizer.js';\nimport { ParticipantAudioInputStream } from './_input.js';\nimport {\n ParalellTextOutput,\n ParticipantAudioOutput,\n ParticipantLegacyTranscriptionOutput,\n ParticipantTranscriptionOutput,\n} from './_output.js';\n\nexport const DEFAULT_TEXT_INPUT_CALLBACK: TextInputCallback = (sess, ev) => {\n sess.interrupt();\n sess.generateReply({ userInput: ev.text });\n};\n\nconst DEFAULT_PARTICIPANT_KINDS: ParticipantKind[] = [\n ParticipantKind.CONNECTOR,\n ParticipantKind.SIP,\n ParticipantKind.STANDARD,\n];\n\nconst CLOSE_ON_DISCONNECT_REASONS: DisconnectReason[] = [\n DisconnectReason.CLIENT_INITIATED,\n DisconnectReason.ROOM_DELETED,\n DisconnectReason.USER_REJECTED,\n];\n\nexport interface RoomInputOptions {\n audioSampleRate: number;\n audioNumChannels: number;\n /** If not given, default to True. */\n textEnabled: boolean;\n /** If not given, default to True. */\n audioEnabled: boolean;\n /** If not given, default to False. */\n videoEnabled: boolean;\n /** The participant to link to. If not provided, link to the first participant.\n Can be overridden by the `participant` argument of RoomIO constructor or `set_participant`.\n */\n participantIdentity?: string;\n noiseCancellation?: NoiseCancellationOptions | FrameProcessor<AudioFrame>;\n textInputCallback?: TextInputCallback;\n /** Participant kinds accepted for auto subscription. If not provided,\n accept `DEFAULT_PARTICIPANT_KINDS`\n */\n participantKinds?: ParticipantKind[];\n /** Close the AgentSession if the linked participant disconnects with reasons in\n CLIENT_INITIATED, ROOM_DELETED, or USER_REJECTED.\n */\n closeOnDisconnect: boolean;\n}\n\nexport interface RoomOutputOptions {\n /** If not given, default to True. */\n transcriptionEnabled: boolean;\n /** If not given, default to True. */\n audioEnabled: boolean;\n audioSampleRate: number;\n audioNumChannels: number;\n /** False to disable transcription synchronization with audio output.\n Otherwise, transcription is emitted as quickly as available.\n */\n syncTranscription: boolean;\n /** The name of the audio track to publish. If not provided, default to \"roomio_audio\".\n */\n audioPublishOptions: TrackPublishOptions;\n}\n\nconst DEFAULT_ROOM_INPUT_OPTIONS: RoomInputOptions = {\n audioSampleRate: 24000,\n audioNumChannels: 1,\n textEnabled: true,\n audioEnabled: true,\n videoEnabled: false,\n textInputCallback: DEFAULT_TEXT_INPUT_CALLBACK,\n closeOnDisconnect: true,\n};\n\nconst DEFAULT_ROOM_OUTPUT_OPTIONS: RoomOutputOptions = {\n audioSampleRate: 24000,\n audioNumChannels: 1,\n transcriptionEnabled: true,\n audioEnabled: true,\n syncTranscription: true,\n audioPublishOptions: new TrackPublishOptions({ source: TrackSource.SOURCE_MICROPHONE }),\n};\n\nexport class RoomIO {\n private agentSession: AgentSession;\n private room: Room;\n private inputOptions: RoomInputOptions;\n private outputOptions: RoomOutputOptions;\n\n private audioInput?: ParticipantAudioInputStream;\n private participantAudioOutput?: ParticipantAudioOutput;\n private userTranscriptOutput?: ParalellTextOutput;\n private agentTranscriptOutput?: ParalellTextOutput;\n private transcriptionSynchronizer?: TranscriptionSynchronizer;\n private participantIdentity: string | null = null;\n\n private participantAvailableFuture: Future<RemoteParticipant> = new Future();\n private roomConnectedFuture: Future<void> = new Future();\n\n // Use stream API for transcript queue\n private userTranscriptStream = new IdentityTransform<UserInputTranscribedEvent>();\n private userTranscriptWriter: WritableStreamDefaultWriter<UserInputTranscribedEvent>;\n private forwardUserTranscriptTask?: Task<void>;\n private initTask?: Task<void>;\n\n private logger = log();\n\n constructor({\n agentSession,\n room,\n participant = null,\n inputOptions,\n outputOptions,\n }: {\n agentSession: AgentSession;\n room: Room;\n participant?: RemoteParticipant | string | null;\n inputOptions?: Partial<RoomInputOptions>;\n outputOptions?: Partial<RoomOutputOptions>;\n }) {\n this.agentSession = agentSession;\n this.room = room;\n this.inputOptions = { ...DEFAULT_ROOM_INPUT_OPTIONS, ...inputOptions };\n this.outputOptions = { ...DEFAULT_ROOM_OUTPUT_OPTIONS, ...outputOptions };\n\n this.userTranscriptWriter = this.userTranscriptStream.writable.getWriter();\n\n this.participantIdentity = participant\n ? typeof participant === 'string'\n ? participant\n : participant.identity\n : this.inputOptions.participantIdentity ?? null;\n }\n private async init(signal: AbortSignal): Promise<void> {\n await Promise.race([this.roomConnectedFuture.await, waitForAbort(signal)]);\n if (signal.aborted) {\n return;\n }\n\n for (const participant of this.room.remoteParticipants.values()) {\n this.onParticipantConnected(participant);\n }\n if (signal.aborted) {\n return;\n }\n\n const participant = await Promise.race([\n this.participantAvailableFuture.await,\n waitForAbort(signal),\n ]);\n\n if (!participant) {\n return;\n }\n\n this.setParticipant(participant.identity);\n\n // init agent outputs\n this.updateTranscriptionOutput({\n output: this.agentTranscriptOutput,\n participant: this.room.localParticipant?.identity ?? null,\n });\n\n await this.participantAudioOutput?.start(signal);\n }\n\n private onConnectionStateChanged = (state: ConnectionState) => {\n this.logger.debug({ state }, 'connection state changed');\n if (\n state === ConnectionState.CONN_CONNECTED &&\n this.room.isConnected &&\n !this.roomConnectedFuture.done\n ) {\n this.roomConnectedFuture.resolve();\n }\n };\n\n private onParticipantConnected = (participant: RemoteParticipant) => {\n if (this.participantAvailableFuture.done) {\n return;\n }\n\n if (this.participantIdentity) {\n if (participant.identity !== this.participantIdentity) {\n return;\n }\n } else if (\n // otherwise, skip participants that are marked as publishing for this agent\n participant.attributes?.[ATTRIBUTE_PUBLISH_ON_BEHALF] === this.room.localParticipant?.identity\n ) {\n return;\n }\n\n const acceptedKinds = this.inputOptions.participantKinds ?? DEFAULT_PARTICIPANT_KINDS;\n if (participant.info.kind !== undefined && !acceptedKinds.includes(participant.info.kind)) {\n return;\n }\n\n this.participantAvailableFuture.resolve(participant);\n };\n\n private onParticipantDisconnected = (participant: RemoteParticipant) => {\n if (participant.identity !== this.participantIdentity) {\n return;\n }\n this.participantAvailableFuture = new Future<RemoteParticipant>();\n if (\n this.inputOptions.closeOnDisconnect &&\n participant.disconnectReason &&\n CLOSE_ON_DISCONNECT_REASONS.includes(participant.disconnectReason)\n ) {\n this.logger.info(\n {\n participant: participant.identity,\n reason: DisconnectReason[participant.disconnectReason],\n },\n 'closing agent session due to participant disconnect ' +\n '(disable via `RoomInputOptions.closeOnDisconnect=False`)',\n );\n this.agentSession._closeSoon({\n reason: CloseReason.PARTICIPANT_DISCONNECTED,\n });\n }\n };\n\n private onUserInputTranscribed = (ev: UserInputTranscribedEvent) => {\n this.userTranscriptWriter.write(ev).catch((error) => {\n this.logger.error({ error }, 'Failed to write transcript event to stream');\n });\n };\n\n private onAgentStateChanged = async (ev: AgentStateChangedEvent) => {\n if (this.room.isConnected && this.room.localParticipant) {\n await this.room.localParticipant.setAttributes({\n [`lk.agent.state`]: ev.newState,\n });\n }\n };\n\n private async forwardUserTranscript(signal: AbortSignal): Promise<void> {\n const reader = this.userTranscriptStream.readable.getReader();\n try {\n while (!signal.aborted) {\n const { done, value } = await reader.read();\n if (done) break;\n\n const event = value;\n // IMPORTANT: need to await here to avoid race condition\n await this.userTranscriptOutput?.captureText(event.transcript);\n if (event.isFinal) {\n this.userTranscriptOutput?.flush();\n }\n }\n } catch (error) {\n this.logger.error({ error }, 'Error processing transcript stream');\n }\n }\n\n private createTranscriptionOutput(options: {\n isDeltaStream: boolean;\n participant: Participant | string | null;\n }) {\n return new ParalellTextOutput([\n new ParticipantLegacyTranscriptionOutput(\n this.room,\n options.isDeltaStream,\n options.participant,\n ),\n new ParticipantTranscriptionOutput(this.room, options.isDeltaStream, options.participant),\n ]);\n }\n\n private updateTranscriptionOutput({\n output,\n participant,\n }: {\n output?: ParalellTextOutput;\n participant: string | null;\n }) {\n if (!output) {\n return;\n }\n\n for (const sink of output._sinks) {\n if (\n sink instanceof ParticipantLegacyTranscriptionOutput ||\n sink instanceof ParticipantTranscriptionOutput\n ) {\n sink.setParticipant(participant);\n }\n }\n }\n\n get audioOutput(): AudioOutput | undefined {\n if (!this.transcriptionSynchronizer) {\n return this.participantAudioOutput;\n }\n\n return this.transcriptionSynchronizer.audioOutput;\n }\n\n get transcriptionOutput(): TextOutput | undefined {\n if (!this.transcriptionSynchronizer) {\n return this.agentTranscriptOutput;\n }\n\n return this.transcriptionSynchronizer.textOutput;\n }\n\n get isParticipantAvailable(): boolean {\n return this.participantAvailableFuture.done;\n }\n\n get rtcRoom(): Room {\n return this.room;\n }\n\n get linkedParticipant(): RemoteParticipant | undefined {\n if (!this.isParticipantAvailable) {\n return undefined;\n }\n\n return this.participantAvailableFuture.result;\n }\n\n get localParticipant(): Participant | undefined {\n return this.room.localParticipant ?? undefined;\n }\n\n /** Switch to a different participant */\n setParticipant(participantIdentity: string | null) {\n this.logger.debug({ participantIdentity }, 'setting participant');\n if (participantIdentity === null) {\n this.unsetParticipant();\n return;\n }\n\n if (this.participantIdentity !== participantIdentity) {\n this.participantAvailableFuture = new Future<RemoteParticipant>();\n\n // check if new participant is already connected\n for (const participant of this.room.remoteParticipants.values()) {\n if (participant.identity === participantIdentity) {\n this.participantAvailableFuture.resolve(participant);\n break;\n }\n }\n }\n\n // update participant identity and handlers\n this.participantIdentity = participantIdentity;\n this.audioInput?.setParticipant(participantIdentity);\n this.updateTranscriptionOutput({\n output: this.userTranscriptOutput,\n participant: participantIdentity,\n });\n }\n\n unsetParticipant() {\n this.participantIdentity = null;\n this.participantAvailableFuture = new Future<RemoteParticipant>();\n this.audioInput?.setParticipant(null);\n this.updateTranscriptionOutput({\n output: this.userTranscriptOutput,\n participant: null,\n });\n }\n\n start() {\n // -- create inputs --\n if (this.inputOptions.audioEnabled) {\n this.audioInput = new ParticipantAudioInputStream({\n room: this.room,\n sampleRate: this.inputOptions.audioSampleRate,\n numChannels: this.inputOptions.audioNumChannels,\n noiseCancellation: this.inputOptions.noiseCancellation,\n });\n }\n\n // -- create outputs --\n if (this.outputOptions.audioEnabled) {\n this.participantAudioOutput = new ParticipantAudioOutput(this.room, {\n sampleRate: this.outputOptions.audioSampleRate,\n numChannels: this.outputOptions.audioNumChannels,\n trackPublishOptions: this.outputOptions.audioPublishOptions,\n });\n }\n if (this.outputOptions.transcriptionEnabled) {\n this.userTranscriptOutput = this.createTranscriptionOutput({\n isDeltaStream: false,\n participant: this.participantIdentity,\n });\n // Start the transcript forwarding\n this.forwardUserTranscriptTask = Task.from((controller) =>\n this.forwardUserTranscript(controller.signal),\n );\n this.agentTranscriptOutput = this.createTranscriptionOutput({\n isDeltaStream: true,\n participant: null,\n });\n\n // use the RoomIO's audio output if available, otherwise use the agent's audio output\n // TODO(AJS-176): check for agent output\n const audioOutput = this.participantAudioOutput;\n if (this.outputOptions.syncTranscription && audioOutput) {\n this.transcriptionSynchronizer = new TranscriptionSynchronizer(\n audioOutput,\n this.agentTranscriptOutput,\n );\n }\n }\n\n // -- set the room event handlers --\n this.room.on(RoomEvent.ParticipantConnected, this.onParticipantConnected);\n this.room.on(RoomEvent.ConnectionStateChanged, this.onConnectionStateChanged);\n this.room.on(RoomEvent.ParticipantDisconnected, this.onParticipantDisconnected);\n if (this.room.isConnected) {\n this.onConnectionStateChanged(ConnectionState.CONN_CONNECTED);\n }\n\n this.initTask = Task.from((controller) => this.init(controller.signal));\n\n // attach the agent to the session\n if (this.audioInput) {\n this.agentSession.input.audio = this.audioInput;\n }\n if (this.audioOutput) {\n this.agentSession.output.audio = this.audioOutput;\n }\n if (this.transcriptionOutput) {\n this.agentSession.output.transcription = this.transcriptionOutput;\n }\n\n this.agentSession.on(AgentSessionEventTypes.AgentStateChanged, this.onAgentStateChanged);\n this.agentSession.on(AgentSessionEventTypes.UserInputTranscribed, this.onUserInputTranscribed);\n }\n\n async close() {\n this.room.off(RoomEvent.ParticipantConnected, this.onParticipantConnected);\n this.room.off(RoomEvent.ConnectionStateChanged, this.onConnectionStateChanged);\n this.room.off(RoomEvent.ParticipantDisconnected, this.onParticipantDisconnected);\n this.agentSession.off(AgentSessionEventTypes.UserInputTranscribed, this.onUserInputTranscribed);\n this.agentSession.off(AgentSessionEventTypes.AgentStateChanged, this.onAgentStateChanged);\n\n await this.initTask?.cancelAndWait();\n\n // Close stream FIRST so reader.read() in forwardUserTranscript can exit.\n // This is a workaround for a race condition in the stream API.\n this.userTranscriptWriter.close();\n await this.forwardUserTranscriptTask?.cancelAndWait();\n\n await this.audioInput?.close();\n await this.participantAudioOutput?.close();\n await this.transcriptionSynchronizer?.close();\n }\n}\n"],"mappings":"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","names":["participant"]}
1
+ {"version":3,"sources":["../../../src/voice/room_io/room_io.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { TextStreamReader } from '@livekit/rtc-node';\nimport {\n type AudioFrame,\n ConnectionState,\n DisconnectReason,\n type FrameProcessor,\n type NoiseCancellationOptions,\n type Participant,\n ParticipantKind,\n type RemoteParticipant,\n type Room,\n RoomEvent,\n TrackPublishOptions,\n TrackSource,\n} from '@livekit/rtc-node';\nimport type { WritableStreamDefaultWriter } from 'node:stream/web';\nimport { ATTRIBUTE_PUBLISH_ON_BEHALF, TOPIC_CHAT } from '../../constants.js';\nimport { log } from '../../log.js';\nimport { IdentityTransform } from '../../stream/identity_transform.js';\nimport { Future, Task, waitForAbort } from '../../utils.js';\nimport { type AgentSession } from '../agent_session.js';\nimport {\n AgentSessionEventTypes,\n type AgentStateChangedEvent,\n CloseReason,\n type UserInputTranscribedEvent,\n} from '../events.js';\nimport type { AudioOutput, TextOutput } from '../io.js';\nimport type { TextInputCallback } from '../remote_session.js';\nimport { TranscriptionSynchronizer } from '../transcription/synchronizer.js';\nimport { ParticipantAudioInputStream } from './_input.js';\nimport {\n ParalellTextOutput,\n ParticipantAudioOutput,\n ParticipantLegacyTranscriptionOutput,\n ParticipantTranscriptionOutput,\n} from './_output.js';\n\nexport const DEFAULT_TEXT_INPUT_CALLBACK: TextInputCallback = (sess, ev) => {\n sess.interrupt();\n sess.generateReply({ userInput: ev.text });\n};\n\nconst DEFAULT_PARTICIPANT_KINDS: ParticipantKind[] = [\n ParticipantKind.CONNECTOR,\n ParticipantKind.SIP,\n ParticipantKind.STANDARD,\n];\n\nconst CLOSE_ON_DISCONNECT_REASONS: DisconnectReason[] = [\n DisconnectReason.CLIENT_INITIATED,\n DisconnectReason.ROOM_DELETED,\n DisconnectReason.USER_REJECTED,\n];\n\nexport interface RoomInputOptions {\n audioSampleRate: number;\n audioNumChannels: number;\n /** If not given, default to True. */\n textEnabled: boolean;\n /** If not given, default to True. */\n audioEnabled: boolean;\n /** If not given, default to False. */\n videoEnabled: boolean;\n /** The participant to link to. If not provided, link to the first participant.\n Can be overridden by the `participant` argument of RoomIO constructor or `set_participant`.\n */\n participantIdentity?: string;\n noiseCancellation?: NoiseCancellationOptions | FrameProcessor<AudioFrame>;\n textInputCallback?: TextInputCallback;\n /** Participant kinds accepted for auto subscription. If not provided,\n accept `DEFAULT_PARTICIPANT_KINDS`\n */\n participantKinds?: ParticipantKind[];\n /** Close the AgentSession if the linked participant disconnects with reasons in\n CLIENT_INITIATED, ROOM_DELETED, or USER_REJECTED.\n */\n closeOnDisconnect: boolean;\n}\n\nexport interface RoomOutputOptions {\n /** If not given, default to True. */\n transcriptionEnabled: boolean;\n /** If not given, default to True. */\n audioEnabled: boolean;\n audioSampleRate: number;\n audioNumChannels: number;\n /** False to disable transcription synchronization with audio output.\n Otherwise, transcription is emitted as quickly as available.\n */\n syncTranscription: boolean;\n /** The name of the audio track to publish. If not provided, default to \"roomio_audio\".\n */\n audioPublishOptions: TrackPublishOptions;\n}\n\nconst DEFAULT_ROOM_INPUT_OPTIONS: RoomInputOptions = {\n audioSampleRate: 24000,\n audioNumChannels: 1,\n textEnabled: true,\n audioEnabled: true,\n videoEnabled: false,\n textInputCallback: DEFAULT_TEXT_INPUT_CALLBACK,\n closeOnDisconnect: true,\n};\n\nconst DEFAULT_ROOM_OUTPUT_OPTIONS: RoomOutputOptions = {\n audioSampleRate: 24000,\n audioNumChannels: 1,\n transcriptionEnabled: true,\n audioEnabled: true,\n syncTranscription: true,\n audioPublishOptions: new TrackPublishOptions({ source: TrackSource.SOURCE_MICROPHONE }),\n};\n\nexport class RoomIO {\n private agentSession: AgentSession;\n private room: Room;\n private inputOptions: RoomInputOptions;\n private outputOptions: RoomOutputOptions;\n\n private audioInput?: ParticipantAudioInputStream;\n private participantAudioOutput?: ParticipantAudioOutput;\n private userTranscriptOutput?: ParalellTextOutput;\n private agentTranscriptOutput?: ParalellTextOutput;\n private transcriptionSynchronizer?: TranscriptionSynchronizer;\n private participantIdentity: string | null = null;\n private textStreamHandlerRegistered = false;\n\n private participantAvailableFuture: Future<RemoteParticipant> = new Future();\n private roomConnectedFuture: Future<void> = new Future();\n\n // Use stream API for transcript queue\n private userTranscriptStream = new IdentityTransform<UserInputTranscribedEvent>();\n private userTranscriptWriter: WritableStreamDefaultWriter<UserInputTranscribedEvent>;\n private forwardUserTranscriptTask?: Task<void>;\n private initTask?: Task<void>;\n\n private logger = log();\n\n constructor({\n agentSession,\n room,\n participant = null,\n inputOptions,\n outputOptions,\n }: {\n agentSession: AgentSession;\n room: Room;\n participant?: RemoteParticipant | string | null;\n inputOptions?: Partial<RoomInputOptions>;\n outputOptions?: Partial<RoomOutputOptions>;\n }) {\n this.agentSession = agentSession;\n this.room = room;\n this.inputOptions = { ...DEFAULT_ROOM_INPUT_OPTIONS, ...inputOptions };\n this.outputOptions = { ...DEFAULT_ROOM_OUTPUT_OPTIONS, ...outputOptions };\n\n this.userTranscriptWriter = this.userTranscriptStream.writable.getWriter();\n\n this.participantIdentity = participant\n ? typeof participant === 'string'\n ? participant\n : participant.identity\n : this.inputOptions.participantIdentity ?? null;\n }\n private async init(signal: AbortSignal): Promise<void> {\n await Promise.race([this.roomConnectedFuture.await, waitForAbort(signal)]);\n if (signal.aborted) {\n return;\n }\n\n for (const participant of this.room.remoteParticipants.values()) {\n this.onParticipantConnected(participant);\n }\n if (signal.aborted) {\n return;\n }\n\n const participant = await Promise.race([\n this.participantAvailableFuture.await,\n waitForAbort(signal),\n ]);\n\n if (!participant) {\n return;\n }\n\n this.setParticipant(participant.identity);\n\n // init agent outputs\n this.updateTranscriptionOutput({\n output: this.agentTranscriptOutput,\n participant: this.room.localParticipant?.identity ?? null,\n });\n\n await this.participantAudioOutput?.start(signal);\n }\n\n private onConnectionStateChanged = (state: ConnectionState) => {\n this.logger.debug({ state }, 'connection state changed');\n if (\n state === ConnectionState.CONN_CONNECTED &&\n this.room.isConnected &&\n !this.roomConnectedFuture.done\n ) {\n this.roomConnectedFuture.resolve();\n }\n };\n\n private onParticipantConnected = (participant: RemoteParticipant) => {\n if (this.participantAvailableFuture.done) {\n return;\n }\n\n if (this.participantIdentity) {\n if (participant.identity !== this.participantIdentity) {\n return;\n }\n } else if (\n // otherwise, skip participants that are marked as publishing for this agent\n participant.attributes?.[ATTRIBUTE_PUBLISH_ON_BEHALF] === this.room.localParticipant?.identity\n ) {\n return;\n }\n\n const acceptedKinds = this.inputOptions.participantKinds ?? DEFAULT_PARTICIPANT_KINDS;\n if (participant.info.kind !== undefined && !acceptedKinds.includes(participant.info.kind)) {\n return;\n }\n\n this.participantAvailableFuture.resolve(participant);\n };\n\n private onParticipantDisconnected = (participant: RemoteParticipant) => {\n if (participant.identity !== this.participantIdentity) {\n return;\n }\n this.participantAvailableFuture = new Future<RemoteParticipant>();\n if (\n this.inputOptions.closeOnDisconnect &&\n participant.disconnectReason &&\n CLOSE_ON_DISCONNECT_REASONS.includes(participant.disconnectReason)\n ) {\n this.logger.info(\n {\n participant: participant.identity,\n reason: DisconnectReason[participant.disconnectReason],\n },\n 'closing agent session due to participant disconnect ' +\n '(disable via `RoomInputOptions.closeOnDisconnect=False`)',\n );\n this.agentSession._closeSoon({\n reason: CloseReason.PARTICIPANT_DISCONNECTED,\n });\n }\n };\n\n private onUserInputTranscribed = (ev: UserInputTranscribedEvent) => {\n this.userTranscriptWriter.write(ev).catch((error) => {\n this.logger.error({ error }, 'Failed to write transcript event to stream');\n });\n };\n\n private onAgentStateChanged = async (ev: AgentStateChangedEvent) => {\n if (this.room.isConnected && this.room.localParticipant) {\n await this.room.localParticipant.setAttributes({\n [`lk.agent.state`]: ev.newState,\n });\n }\n };\n\n private onUserTextInput = (reader: TextStreamReader, participantInfo: { identity: string }) => {\n if (this.participantIdentity && participantInfo.identity !== this.participantIdentity) {\n return;\n }\n\n const participant = this.room.remoteParticipants.get(participantInfo.identity);\n if (!participant) {\n this.logger.warn('participant not found, ignoring text input');\n return;\n }\n\n const readText = async () => {\n const text = await reader.readAll();\n\n const textInputResult = this.inputOptions.textInputCallback!(this.agentSession, {\n text,\n info: reader.info,\n participantIdentity: participantInfo.identity,\n });\n\n // check if callback is a Promise\n if (textInputResult instanceof Promise) {\n await textInputResult;\n }\n };\n\n readText().catch((error) => {\n this.logger.error({ error }, 'Error reading text input');\n });\n };\n\n private async forwardUserTranscript(signal: AbortSignal): Promise<void> {\n const reader = this.userTranscriptStream.readable.getReader();\n try {\n while (!signal.aborted) {\n const { done, value } = await reader.read();\n if (done) break;\n\n const event = value;\n // IMPORTANT: need to await here to avoid race condition\n await this.userTranscriptOutput?.captureText(event.transcript);\n if (event.isFinal) {\n this.userTranscriptOutput?.flush();\n }\n }\n } catch (error) {\n this.logger.error({ error }, 'Error processing transcript stream');\n }\n }\n\n private createTranscriptionOutput(options: {\n isDeltaStream: boolean;\n participant: Participant | string | null;\n }) {\n return new ParalellTextOutput([\n new ParticipantLegacyTranscriptionOutput(\n this.room,\n options.isDeltaStream,\n options.participant,\n ),\n new ParticipantTranscriptionOutput(this.room, options.isDeltaStream, options.participant),\n ]);\n }\n\n private updateTranscriptionOutput({\n output,\n participant,\n }: {\n output?: ParalellTextOutput;\n participant: string | null;\n }) {\n if (!output) {\n return;\n }\n\n for (const sink of output._sinks) {\n if (\n sink instanceof ParticipantLegacyTranscriptionOutput ||\n sink instanceof ParticipantTranscriptionOutput\n ) {\n sink.setParticipant(participant);\n }\n }\n }\n\n get audioOutput(): AudioOutput | undefined {\n if (!this.transcriptionSynchronizer) {\n return this.participantAudioOutput;\n }\n\n return this.transcriptionSynchronizer.audioOutput;\n }\n\n get transcriptionOutput(): TextOutput | undefined {\n if (!this.transcriptionSynchronizer) {\n return this.agentTranscriptOutput;\n }\n\n return this.transcriptionSynchronizer.textOutput;\n }\n\n get isParticipantAvailable(): boolean {\n return this.participantAvailableFuture.done;\n }\n\n get rtcRoom(): Room {\n return this.room;\n }\n\n get linkedParticipant(): RemoteParticipant | undefined {\n if (!this.isParticipantAvailable) {\n return undefined;\n }\n\n return this.participantAvailableFuture.result;\n }\n\n get localParticipant(): Participant | undefined {\n return this.room.localParticipant ?? undefined;\n }\n\n /** Switch to a different participant */\n setParticipant(participantIdentity: string | null) {\n this.logger.debug({ participantIdentity }, 'setting participant');\n if (participantIdentity === null) {\n this.unsetParticipant();\n return;\n }\n\n if (this.participantIdentity !== participantIdentity) {\n this.participantAvailableFuture = new Future<RemoteParticipant>();\n\n // check if new participant is already connected\n for (const participant of this.room.remoteParticipants.values()) {\n if (participant.identity === participantIdentity) {\n this.participantAvailableFuture.resolve(participant);\n break;\n }\n }\n }\n\n // update participant identity and handlers\n this.participantIdentity = participantIdentity;\n this.audioInput?.setParticipant(participantIdentity);\n this.updateTranscriptionOutput({\n output: this.userTranscriptOutput,\n participant: participantIdentity,\n });\n }\n\n unsetParticipant() {\n this.participantIdentity = null;\n this.participantAvailableFuture = new Future<RemoteParticipant>();\n this.audioInput?.setParticipant(null);\n this.updateTranscriptionOutput({\n output: this.userTranscriptOutput,\n participant: null,\n });\n }\n\n start() {\n // -- create inputs --\n\n if (this.inputOptions.textEnabled) {\n try {\n this.room.registerTextStreamHandler(TOPIC_CHAT, this.onUserTextInput);\n this.textStreamHandlerRegistered = true;\n } catch (error) {\n if (this.inputOptions.textEnabled) {\n this.logger.warn(`text stream handler for topic \"${TOPIC_CHAT}\" already set, ignoring`);\n }\n }\n }\n\n if (this.inputOptions.audioEnabled) {\n this.audioInput = new ParticipantAudioInputStream({\n room: this.room,\n sampleRate: this.inputOptions.audioSampleRate,\n numChannels: this.inputOptions.audioNumChannels,\n noiseCancellation: this.inputOptions.noiseCancellation,\n });\n }\n\n // -- create outputs --\n if (this.outputOptions.audioEnabled) {\n this.participantAudioOutput = new ParticipantAudioOutput(this.room, {\n sampleRate: this.outputOptions.audioSampleRate,\n numChannels: this.outputOptions.audioNumChannels,\n trackPublishOptions: this.outputOptions.audioPublishOptions,\n });\n }\n if (this.outputOptions.transcriptionEnabled) {\n this.userTranscriptOutput = this.createTranscriptionOutput({\n isDeltaStream: false,\n participant: this.participantIdentity,\n });\n // Start the transcript forwarding\n this.forwardUserTranscriptTask = Task.from((controller) =>\n this.forwardUserTranscript(controller.signal),\n );\n this.agentTranscriptOutput = this.createTranscriptionOutput({\n isDeltaStream: true,\n participant: null,\n });\n\n // use the RoomIO's audio output if available, otherwise use the agent's audio output\n // TODO(AJS-176): check for agent output\n const audioOutput = this.participantAudioOutput;\n if (this.outputOptions.syncTranscription && audioOutput) {\n this.transcriptionSynchronizer = new TranscriptionSynchronizer(\n audioOutput,\n this.agentTranscriptOutput,\n );\n }\n }\n\n // -- set the room event handlers --\n this.room.on(RoomEvent.ParticipantConnected, this.onParticipantConnected);\n this.room.on(RoomEvent.ConnectionStateChanged, this.onConnectionStateChanged);\n this.room.on(RoomEvent.ParticipantDisconnected, this.onParticipantDisconnected);\n if (this.room.isConnected) {\n this.onConnectionStateChanged(ConnectionState.CONN_CONNECTED);\n }\n\n this.initTask = Task.from((controller) => this.init(controller.signal));\n\n // attach the agent to the session\n if (this.audioInput) {\n this.agentSession.input.audio = this.audioInput;\n }\n if (this.audioOutput) {\n this.agentSession.output.audio = this.audioOutput;\n }\n if (this.transcriptionOutput) {\n this.agentSession.output.transcription = this.transcriptionOutput;\n }\n\n this.agentSession.on(AgentSessionEventTypes.AgentStateChanged, this.onAgentStateChanged);\n this.agentSession.on(AgentSessionEventTypes.UserInputTranscribed, this.onUserInputTranscribed);\n }\n\n async close() {\n this.room.off(RoomEvent.ParticipantConnected, this.onParticipantConnected);\n this.room.off(RoomEvent.ConnectionStateChanged, this.onConnectionStateChanged);\n this.room.off(RoomEvent.ParticipantDisconnected, this.onParticipantDisconnected);\n this.agentSession.off(AgentSessionEventTypes.UserInputTranscribed, this.onUserInputTranscribed);\n this.agentSession.off(AgentSessionEventTypes.AgentStateChanged, this.onAgentStateChanged);\n\n if (this.textStreamHandlerRegistered) {\n this.room.unregisterTextStreamHandler(TOPIC_CHAT);\n this.textStreamHandlerRegistered = false;\n }\n\n await this.initTask?.cancelAndWait();\n\n // Close stream FIRST so reader.read() in forwardUserTranscript can exit.\n // This is a workaround for a race condition in the stream API.\n this.userTranscriptWriter.close();\n await this.forwardUserTranscriptTask?.cancelAndWait();\n\n await this.audioInput?.close();\n await this.participantAudioOutput?.close();\n await this.transcriptionSynchronizer?.close();\n }\n}\n"],"mappings":"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","names":["participant"]}
@@ -1,7 +1,7 @@
1
1
  import { type AudioFrame, type FrameProcessor, type NoiseCancellationOptions, type Participant, ParticipantKind, type RemoteParticipant, type Room, TrackPublishOptions } from '@livekit/rtc-node';
2
2
  import { type AgentSession } from '../agent_session.js';
3
- import type { TextInputCallback } from '../client_events.js';
4
3
  import type { AudioOutput, TextOutput } from '../io.js';
4
+ import type { TextInputCallback } from '../remote_session.js';
5
5
  export declare const DEFAULT_TEXT_INPUT_CALLBACK: TextInputCallback;
6
6
  export interface RoomInputOptions {
7
7
  audioSampleRate: number;
@@ -53,6 +53,7 @@ export declare class RoomIO {
53
53
  private agentTranscriptOutput?;
54
54
  private transcriptionSynchronizer?;
55
55
  private participantIdentity;
56
+ private textStreamHandlerRegistered;
56
57
  private participantAvailableFuture;
57
58
  private roomConnectedFuture;
58
59
  private userTranscriptStream;
@@ -73,6 +74,7 @@ export declare class RoomIO {
73
74
  private onParticipantDisconnected;
74
75
  private onUserInputTranscribed;
75
76
  private onAgentStateChanged;
77
+ private onUserTextInput;
76
78
  private forwardUserTranscript;
77
79
  private createTranscriptionOutput;
78
80
  private updateTranscriptionOutput;
@@ -1,7 +1,7 @@
1
1
  import { type AudioFrame, type FrameProcessor, type NoiseCancellationOptions, type Participant, ParticipantKind, type RemoteParticipant, type Room, TrackPublishOptions } from '@livekit/rtc-node';
2
2
  import { type AgentSession } from '../agent_session.js';
3
- import type { TextInputCallback } from '../client_events.js';
4
3
  import type { AudioOutput, TextOutput } from '../io.js';
4
+ import type { TextInputCallback } from '../remote_session.js';
5
5
  export declare const DEFAULT_TEXT_INPUT_CALLBACK: TextInputCallback;
6
6
  export interface RoomInputOptions {
7
7
  audioSampleRate: number;
@@ -53,6 +53,7 @@ export declare class RoomIO {
53
53
  private agentTranscriptOutput?;
54
54
  private transcriptionSynchronizer?;
55
55
  private participantIdentity;
56
+ private textStreamHandlerRegistered;
56
57
  private participantAvailableFuture;
57
58
  private roomConnectedFuture;
58
59
  private userTranscriptStream;
@@ -73,6 +74,7 @@ export declare class RoomIO {
73
74
  private onParticipantDisconnected;
74
75
  private onUserInputTranscribed;
75
76
  private onAgentStateChanged;
77
+ private onUserTextInput;
76
78
  private forwardUserTranscript;
77
79
  private createTranscriptionOutput;
78
80
  private updateTranscriptionOutput;
@@ -1 +1 @@
1
- {"version":3,"file":"room_io.d.ts","sourceRoot":"","sources":["../../../src/voice/room_io/room_io.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"room_io.d.ts","sourceRoot":"","sources":["../../../src/voice/room_io/room_io.ts"],"names":[],"mappings":"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"}
@@ -6,7 +6,7 @@ import {
6
6
  TrackPublishOptions,
7
7
  TrackSource
8
8
  } from "@livekit/rtc-node";
9
- import { ATTRIBUTE_PUBLISH_ON_BEHALF } from "../../constants.js";
9
+ import { ATTRIBUTE_PUBLISH_ON_BEHALF, TOPIC_CHAT } from "../../constants.js";
10
10
  import { log } from "../../log.js";
11
11
  import { IdentityTransform } from "../../stream/identity_transform.js";
12
12
  import { Future, Task, waitForAbort } from "../../utils.js";
@@ -65,6 +65,7 @@ class RoomIO {
65
65
  agentTranscriptOutput;
66
66
  transcriptionSynchronizer;
67
67
  participantIdentity = null;
68
+ textStreamHandlerRegistered = false;
68
69
  participantAvailableFuture = new Future();
69
70
  roomConnectedFuture = new Future();
70
71
  // Use stream API for transcript queue
@@ -170,6 +171,30 @@ class RoomIO {
170
171
  });
171
172
  }
172
173
  };
174
+ onUserTextInput = (reader, participantInfo) => {
175
+ if (this.participantIdentity && participantInfo.identity !== this.participantIdentity) {
176
+ return;
177
+ }
178
+ const participant = this.room.remoteParticipants.get(participantInfo.identity);
179
+ if (!participant) {
180
+ this.logger.warn("participant not found, ignoring text input");
181
+ return;
182
+ }
183
+ const readText = async () => {
184
+ const text = await reader.readAll();
185
+ const textInputResult = this.inputOptions.textInputCallback(this.agentSession, {
186
+ text,
187
+ info: reader.info,
188
+ participantIdentity: participantInfo.identity
189
+ });
190
+ if (textInputResult instanceof Promise) {
191
+ await textInputResult;
192
+ }
193
+ };
194
+ readText().catch((error) => {
195
+ this.logger.error({ error }, "Error reading text input");
196
+ });
197
+ };
173
198
  async forwardUserTranscript(signal) {
174
199
  var _a, _b;
175
200
  const reader = this.userTranscriptStream.readable.getReader();
@@ -272,6 +297,16 @@ class RoomIO {
272
297
  });
273
298
  }
274
299
  start() {
300
+ if (this.inputOptions.textEnabled) {
301
+ try {
302
+ this.room.registerTextStreamHandler(TOPIC_CHAT, this.onUserTextInput);
303
+ this.textStreamHandlerRegistered = true;
304
+ } catch (error) {
305
+ if (this.inputOptions.textEnabled) {
306
+ this.logger.warn(`text stream handler for topic "${TOPIC_CHAT}" already set, ignoring`);
307
+ }
308
+ }
309
+ }
275
310
  if (this.inputOptions.audioEnabled) {
276
311
  this.audioInput = new ParticipantAudioInputStream({
277
312
  room: this.room,
@@ -333,6 +368,10 @@ class RoomIO {
333
368
  this.room.off(RoomEvent.ParticipantDisconnected, this.onParticipantDisconnected);
334
369
  this.agentSession.off(AgentSessionEventTypes.UserInputTranscribed, this.onUserInputTranscribed);
335
370
  this.agentSession.off(AgentSessionEventTypes.AgentStateChanged, this.onAgentStateChanged);
371
+ if (this.textStreamHandlerRegistered) {
372
+ this.room.unregisterTextStreamHandler(TOPIC_CHAT);
373
+ this.textStreamHandlerRegistered = false;
374
+ }
336
375
  await ((_a = this.initTask) == null ? void 0 : _a.cancelAndWait());
337
376
  this.userTranscriptWriter.close();
338
377
  await ((_b = this.forwardUserTranscriptTask) == null ? void 0 : _b.cancelAndWait());
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/voice/room_io/room_io.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport {\n type AudioFrame,\n ConnectionState,\n DisconnectReason,\n type FrameProcessor,\n type NoiseCancellationOptions,\n type Participant,\n ParticipantKind,\n type RemoteParticipant,\n type Room,\n RoomEvent,\n TrackPublishOptions,\n TrackSource,\n} from '@livekit/rtc-node';\nimport type { WritableStreamDefaultWriter } from 'node:stream/web';\nimport { ATTRIBUTE_PUBLISH_ON_BEHALF } from '../../constants.js';\nimport { log } from '../../log.js';\nimport { IdentityTransform } from '../../stream/identity_transform.js';\nimport { Future, Task, waitForAbort } from '../../utils.js';\nimport { type AgentSession } from '../agent_session.js';\nimport type { TextInputCallback } from '../client_events.js';\nimport {\n AgentSessionEventTypes,\n type AgentStateChangedEvent,\n CloseReason,\n type UserInputTranscribedEvent,\n} from '../events.js';\nimport type { AudioOutput, TextOutput } from '../io.js';\nimport { TranscriptionSynchronizer } from '../transcription/synchronizer.js';\nimport { ParticipantAudioInputStream } from './_input.js';\nimport {\n ParalellTextOutput,\n ParticipantAudioOutput,\n ParticipantLegacyTranscriptionOutput,\n ParticipantTranscriptionOutput,\n} from './_output.js';\n\nexport const DEFAULT_TEXT_INPUT_CALLBACK: TextInputCallback = (sess, ev) => {\n sess.interrupt();\n sess.generateReply({ userInput: ev.text });\n};\n\nconst DEFAULT_PARTICIPANT_KINDS: ParticipantKind[] = [\n ParticipantKind.CONNECTOR,\n ParticipantKind.SIP,\n ParticipantKind.STANDARD,\n];\n\nconst CLOSE_ON_DISCONNECT_REASONS: DisconnectReason[] = [\n DisconnectReason.CLIENT_INITIATED,\n DisconnectReason.ROOM_DELETED,\n DisconnectReason.USER_REJECTED,\n];\n\nexport interface RoomInputOptions {\n audioSampleRate: number;\n audioNumChannels: number;\n /** If not given, default to True. */\n textEnabled: boolean;\n /** If not given, default to True. */\n audioEnabled: boolean;\n /** If not given, default to False. */\n videoEnabled: boolean;\n /** The participant to link to. If not provided, link to the first participant.\n Can be overridden by the `participant` argument of RoomIO constructor or `set_participant`.\n */\n participantIdentity?: string;\n noiseCancellation?: NoiseCancellationOptions | FrameProcessor<AudioFrame>;\n textInputCallback?: TextInputCallback;\n /** Participant kinds accepted for auto subscription. If not provided,\n accept `DEFAULT_PARTICIPANT_KINDS`\n */\n participantKinds?: ParticipantKind[];\n /** Close the AgentSession if the linked participant disconnects with reasons in\n CLIENT_INITIATED, ROOM_DELETED, or USER_REJECTED.\n */\n closeOnDisconnect: boolean;\n}\n\nexport interface RoomOutputOptions {\n /** If not given, default to True. */\n transcriptionEnabled: boolean;\n /** If not given, default to True. */\n audioEnabled: boolean;\n audioSampleRate: number;\n audioNumChannels: number;\n /** False to disable transcription synchronization with audio output.\n Otherwise, transcription is emitted as quickly as available.\n */\n syncTranscription: boolean;\n /** The name of the audio track to publish. If not provided, default to \"roomio_audio\".\n */\n audioPublishOptions: TrackPublishOptions;\n}\n\nconst DEFAULT_ROOM_INPUT_OPTIONS: RoomInputOptions = {\n audioSampleRate: 24000,\n audioNumChannels: 1,\n textEnabled: true,\n audioEnabled: true,\n videoEnabled: false,\n textInputCallback: DEFAULT_TEXT_INPUT_CALLBACK,\n closeOnDisconnect: true,\n};\n\nconst DEFAULT_ROOM_OUTPUT_OPTIONS: RoomOutputOptions = {\n audioSampleRate: 24000,\n audioNumChannels: 1,\n transcriptionEnabled: true,\n audioEnabled: true,\n syncTranscription: true,\n audioPublishOptions: new TrackPublishOptions({ source: TrackSource.SOURCE_MICROPHONE }),\n};\n\nexport class RoomIO {\n private agentSession: AgentSession;\n private room: Room;\n private inputOptions: RoomInputOptions;\n private outputOptions: RoomOutputOptions;\n\n private audioInput?: ParticipantAudioInputStream;\n private participantAudioOutput?: ParticipantAudioOutput;\n private userTranscriptOutput?: ParalellTextOutput;\n private agentTranscriptOutput?: ParalellTextOutput;\n private transcriptionSynchronizer?: TranscriptionSynchronizer;\n private participantIdentity: string | null = null;\n\n private participantAvailableFuture: Future<RemoteParticipant> = new Future();\n private roomConnectedFuture: Future<void> = new Future();\n\n // Use stream API for transcript queue\n private userTranscriptStream = new IdentityTransform<UserInputTranscribedEvent>();\n private userTranscriptWriter: WritableStreamDefaultWriter<UserInputTranscribedEvent>;\n private forwardUserTranscriptTask?: Task<void>;\n private initTask?: Task<void>;\n\n private logger = log();\n\n constructor({\n agentSession,\n room,\n participant = null,\n inputOptions,\n outputOptions,\n }: {\n agentSession: AgentSession;\n room: Room;\n participant?: RemoteParticipant | string | null;\n inputOptions?: Partial<RoomInputOptions>;\n outputOptions?: Partial<RoomOutputOptions>;\n }) {\n this.agentSession = agentSession;\n this.room = room;\n this.inputOptions = { ...DEFAULT_ROOM_INPUT_OPTIONS, ...inputOptions };\n this.outputOptions = { ...DEFAULT_ROOM_OUTPUT_OPTIONS, ...outputOptions };\n\n this.userTranscriptWriter = this.userTranscriptStream.writable.getWriter();\n\n this.participantIdentity = participant\n ? typeof participant === 'string'\n ? participant\n : participant.identity\n : this.inputOptions.participantIdentity ?? null;\n }\n private async init(signal: AbortSignal): Promise<void> {\n await Promise.race([this.roomConnectedFuture.await, waitForAbort(signal)]);\n if (signal.aborted) {\n return;\n }\n\n for (const participant of this.room.remoteParticipants.values()) {\n this.onParticipantConnected(participant);\n }\n if (signal.aborted) {\n return;\n }\n\n const participant = await Promise.race([\n this.participantAvailableFuture.await,\n waitForAbort(signal),\n ]);\n\n if (!participant) {\n return;\n }\n\n this.setParticipant(participant.identity);\n\n // init agent outputs\n this.updateTranscriptionOutput({\n output: this.agentTranscriptOutput,\n participant: this.room.localParticipant?.identity ?? null,\n });\n\n await this.participantAudioOutput?.start(signal);\n }\n\n private onConnectionStateChanged = (state: ConnectionState) => {\n this.logger.debug({ state }, 'connection state changed');\n if (\n state === ConnectionState.CONN_CONNECTED &&\n this.room.isConnected &&\n !this.roomConnectedFuture.done\n ) {\n this.roomConnectedFuture.resolve();\n }\n };\n\n private onParticipantConnected = (participant: RemoteParticipant) => {\n if (this.participantAvailableFuture.done) {\n return;\n }\n\n if (this.participantIdentity) {\n if (participant.identity !== this.participantIdentity) {\n return;\n }\n } else if (\n // otherwise, skip participants that are marked as publishing for this agent\n participant.attributes?.[ATTRIBUTE_PUBLISH_ON_BEHALF] === this.room.localParticipant?.identity\n ) {\n return;\n }\n\n const acceptedKinds = this.inputOptions.participantKinds ?? DEFAULT_PARTICIPANT_KINDS;\n if (participant.info.kind !== undefined && !acceptedKinds.includes(participant.info.kind)) {\n return;\n }\n\n this.participantAvailableFuture.resolve(participant);\n };\n\n private onParticipantDisconnected = (participant: RemoteParticipant) => {\n if (participant.identity !== this.participantIdentity) {\n return;\n }\n this.participantAvailableFuture = new Future<RemoteParticipant>();\n if (\n this.inputOptions.closeOnDisconnect &&\n participant.disconnectReason &&\n CLOSE_ON_DISCONNECT_REASONS.includes(participant.disconnectReason)\n ) {\n this.logger.info(\n {\n participant: participant.identity,\n reason: DisconnectReason[participant.disconnectReason],\n },\n 'closing agent session due to participant disconnect ' +\n '(disable via `RoomInputOptions.closeOnDisconnect=False`)',\n );\n this.agentSession._closeSoon({\n reason: CloseReason.PARTICIPANT_DISCONNECTED,\n });\n }\n };\n\n private onUserInputTranscribed = (ev: UserInputTranscribedEvent) => {\n this.userTranscriptWriter.write(ev).catch((error) => {\n this.logger.error({ error }, 'Failed to write transcript event to stream');\n });\n };\n\n private onAgentStateChanged = async (ev: AgentStateChangedEvent) => {\n if (this.room.isConnected && this.room.localParticipant) {\n await this.room.localParticipant.setAttributes({\n [`lk.agent.state`]: ev.newState,\n });\n }\n };\n\n private async forwardUserTranscript(signal: AbortSignal): Promise<void> {\n const reader = this.userTranscriptStream.readable.getReader();\n try {\n while (!signal.aborted) {\n const { done, value } = await reader.read();\n if (done) break;\n\n const event = value;\n // IMPORTANT: need to await here to avoid race condition\n await this.userTranscriptOutput?.captureText(event.transcript);\n if (event.isFinal) {\n this.userTranscriptOutput?.flush();\n }\n }\n } catch (error) {\n this.logger.error({ error }, 'Error processing transcript stream');\n }\n }\n\n private createTranscriptionOutput(options: {\n isDeltaStream: boolean;\n participant: Participant | string | null;\n }) {\n return new ParalellTextOutput([\n new ParticipantLegacyTranscriptionOutput(\n this.room,\n options.isDeltaStream,\n options.participant,\n ),\n new ParticipantTranscriptionOutput(this.room, options.isDeltaStream, options.participant),\n ]);\n }\n\n private updateTranscriptionOutput({\n output,\n participant,\n }: {\n output?: ParalellTextOutput;\n participant: string | null;\n }) {\n if (!output) {\n return;\n }\n\n for (const sink of output._sinks) {\n if (\n sink instanceof ParticipantLegacyTranscriptionOutput ||\n sink instanceof ParticipantTranscriptionOutput\n ) {\n sink.setParticipant(participant);\n }\n }\n }\n\n get audioOutput(): AudioOutput | undefined {\n if (!this.transcriptionSynchronizer) {\n return this.participantAudioOutput;\n }\n\n return this.transcriptionSynchronizer.audioOutput;\n }\n\n get transcriptionOutput(): TextOutput | undefined {\n if (!this.transcriptionSynchronizer) {\n return this.agentTranscriptOutput;\n }\n\n return this.transcriptionSynchronizer.textOutput;\n }\n\n get isParticipantAvailable(): boolean {\n return this.participantAvailableFuture.done;\n }\n\n get rtcRoom(): Room {\n return this.room;\n }\n\n get linkedParticipant(): RemoteParticipant | undefined {\n if (!this.isParticipantAvailable) {\n return undefined;\n }\n\n return this.participantAvailableFuture.result;\n }\n\n get localParticipant(): Participant | undefined {\n return this.room.localParticipant ?? undefined;\n }\n\n /** Switch to a different participant */\n setParticipant(participantIdentity: string | null) {\n this.logger.debug({ participantIdentity }, 'setting participant');\n if (participantIdentity === null) {\n this.unsetParticipant();\n return;\n }\n\n if (this.participantIdentity !== participantIdentity) {\n this.participantAvailableFuture = new Future<RemoteParticipant>();\n\n // check if new participant is already connected\n for (const participant of this.room.remoteParticipants.values()) {\n if (participant.identity === participantIdentity) {\n this.participantAvailableFuture.resolve(participant);\n break;\n }\n }\n }\n\n // update participant identity and handlers\n this.participantIdentity = participantIdentity;\n this.audioInput?.setParticipant(participantIdentity);\n this.updateTranscriptionOutput({\n output: this.userTranscriptOutput,\n participant: participantIdentity,\n });\n }\n\n unsetParticipant() {\n this.participantIdentity = null;\n this.participantAvailableFuture = new Future<RemoteParticipant>();\n this.audioInput?.setParticipant(null);\n this.updateTranscriptionOutput({\n output: this.userTranscriptOutput,\n participant: null,\n });\n }\n\n start() {\n // -- create inputs --\n if (this.inputOptions.audioEnabled) {\n this.audioInput = new ParticipantAudioInputStream({\n room: this.room,\n sampleRate: this.inputOptions.audioSampleRate,\n numChannels: this.inputOptions.audioNumChannels,\n noiseCancellation: this.inputOptions.noiseCancellation,\n });\n }\n\n // -- create outputs --\n if (this.outputOptions.audioEnabled) {\n this.participantAudioOutput = new ParticipantAudioOutput(this.room, {\n sampleRate: this.outputOptions.audioSampleRate,\n numChannels: this.outputOptions.audioNumChannels,\n trackPublishOptions: this.outputOptions.audioPublishOptions,\n });\n }\n if (this.outputOptions.transcriptionEnabled) {\n this.userTranscriptOutput = this.createTranscriptionOutput({\n isDeltaStream: false,\n participant: this.participantIdentity,\n });\n // Start the transcript forwarding\n this.forwardUserTranscriptTask = Task.from((controller) =>\n this.forwardUserTranscript(controller.signal),\n );\n this.agentTranscriptOutput = this.createTranscriptionOutput({\n isDeltaStream: true,\n participant: null,\n });\n\n // use the RoomIO's audio output if available, otherwise use the agent's audio output\n // TODO(AJS-176): check for agent output\n const audioOutput = this.participantAudioOutput;\n if (this.outputOptions.syncTranscription && audioOutput) {\n this.transcriptionSynchronizer = new TranscriptionSynchronizer(\n audioOutput,\n this.agentTranscriptOutput,\n );\n }\n }\n\n // -- set the room event handlers --\n this.room.on(RoomEvent.ParticipantConnected, this.onParticipantConnected);\n this.room.on(RoomEvent.ConnectionStateChanged, this.onConnectionStateChanged);\n this.room.on(RoomEvent.ParticipantDisconnected, this.onParticipantDisconnected);\n if (this.room.isConnected) {\n this.onConnectionStateChanged(ConnectionState.CONN_CONNECTED);\n }\n\n this.initTask = Task.from((controller) => this.init(controller.signal));\n\n // attach the agent to the session\n if (this.audioInput) {\n this.agentSession.input.audio = this.audioInput;\n }\n if (this.audioOutput) {\n this.agentSession.output.audio = this.audioOutput;\n }\n if (this.transcriptionOutput) {\n this.agentSession.output.transcription = this.transcriptionOutput;\n }\n\n this.agentSession.on(AgentSessionEventTypes.AgentStateChanged, this.onAgentStateChanged);\n this.agentSession.on(AgentSessionEventTypes.UserInputTranscribed, this.onUserInputTranscribed);\n }\n\n async close() {\n this.room.off(RoomEvent.ParticipantConnected, this.onParticipantConnected);\n this.room.off(RoomEvent.ConnectionStateChanged, this.onConnectionStateChanged);\n this.room.off(RoomEvent.ParticipantDisconnected, this.onParticipantDisconnected);\n this.agentSession.off(AgentSessionEventTypes.UserInputTranscribed, this.onUserInputTranscribed);\n this.agentSession.off(AgentSessionEventTypes.AgentStateChanged, this.onAgentStateChanged);\n\n await this.initTask?.cancelAndWait();\n\n // Close stream FIRST so reader.read() in forwardUserTranscript can exit.\n // This is a workaround for a race condition in the stream API.\n this.userTranscriptWriter.close();\n await this.forwardUserTranscriptTask?.cancelAndWait();\n\n await this.audioInput?.close();\n await this.participantAudioOutput?.close();\n await this.transcriptionSynchronizer?.close();\n }\n}\n"],"mappings":"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","names":["participant"]}
1
+ {"version":3,"sources":["../../../src/voice/room_io/room_io.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { TextStreamReader } from '@livekit/rtc-node';\nimport {\n type AudioFrame,\n ConnectionState,\n DisconnectReason,\n type FrameProcessor,\n type NoiseCancellationOptions,\n type Participant,\n ParticipantKind,\n type RemoteParticipant,\n type Room,\n RoomEvent,\n TrackPublishOptions,\n TrackSource,\n} from '@livekit/rtc-node';\nimport type { WritableStreamDefaultWriter } from 'node:stream/web';\nimport { ATTRIBUTE_PUBLISH_ON_BEHALF, TOPIC_CHAT } from '../../constants.js';\nimport { log } from '../../log.js';\nimport { IdentityTransform } from '../../stream/identity_transform.js';\nimport { Future, Task, waitForAbort } from '../../utils.js';\nimport { type AgentSession } from '../agent_session.js';\nimport {\n AgentSessionEventTypes,\n type AgentStateChangedEvent,\n CloseReason,\n type UserInputTranscribedEvent,\n} from '../events.js';\nimport type { AudioOutput, TextOutput } from '../io.js';\nimport type { TextInputCallback } from '../remote_session.js';\nimport { TranscriptionSynchronizer } from '../transcription/synchronizer.js';\nimport { ParticipantAudioInputStream } from './_input.js';\nimport {\n ParalellTextOutput,\n ParticipantAudioOutput,\n ParticipantLegacyTranscriptionOutput,\n ParticipantTranscriptionOutput,\n} from './_output.js';\n\nexport const DEFAULT_TEXT_INPUT_CALLBACK: TextInputCallback = (sess, ev) => {\n sess.interrupt();\n sess.generateReply({ userInput: ev.text });\n};\n\nconst DEFAULT_PARTICIPANT_KINDS: ParticipantKind[] = [\n ParticipantKind.CONNECTOR,\n ParticipantKind.SIP,\n ParticipantKind.STANDARD,\n];\n\nconst CLOSE_ON_DISCONNECT_REASONS: DisconnectReason[] = [\n DisconnectReason.CLIENT_INITIATED,\n DisconnectReason.ROOM_DELETED,\n DisconnectReason.USER_REJECTED,\n];\n\nexport interface RoomInputOptions {\n audioSampleRate: number;\n audioNumChannels: number;\n /** If not given, default to True. */\n textEnabled: boolean;\n /** If not given, default to True. */\n audioEnabled: boolean;\n /** If not given, default to False. */\n videoEnabled: boolean;\n /** The participant to link to. If not provided, link to the first participant.\n Can be overridden by the `participant` argument of RoomIO constructor or `set_participant`.\n */\n participantIdentity?: string;\n noiseCancellation?: NoiseCancellationOptions | FrameProcessor<AudioFrame>;\n textInputCallback?: TextInputCallback;\n /** Participant kinds accepted for auto subscription. If not provided,\n accept `DEFAULT_PARTICIPANT_KINDS`\n */\n participantKinds?: ParticipantKind[];\n /** Close the AgentSession if the linked participant disconnects with reasons in\n CLIENT_INITIATED, ROOM_DELETED, or USER_REJECTED.\n */\n closeOnDisconnect: boolean;\n}\n\nexport interface RoomOutputOptions {\n /** If not given, default to True. */\n transcriptionEnabled: boolean;\n /** If not given, default to True. */\n audioEnabled: boolean;\n audioSampleRate: number;\n audioNumChannels: number;\n /** False to disable transcription synchronization with audio output.\n Otherwise, transcription is emitted as quickly as available.\n */\n syncTranscription: boolean;\n /** The name of the audio track to publish. If not provided, default to \"roomio_audio\".\n */\n audioPublishOptions: TrackPublishOptions;\n}\n\nconst DEFAULT_ROOM_INPUT_OPTIONS: RoomInputOptions = {\n audioSampleRate: 24000,\n audioNumChannels: 1,\n textEnabled: true,\n audioEnabled: true,\n videoEnabled: false,\n textInputCallback: DEFAULT_TEXT_INPUT_CALLBACK,\n closeOnDisconnect: true,\n};\n\nconst DEFAULT_ROOM_OUTPUT_OPTIONS: RoomOutputOptions = {\n audioSampleRate: 24000,\n audioNumChannels: 1,\n transcriptionEnabled: true,\n audioEnabled: true,\n syncTranscription: true,\n audioPublishOptions: new TrackPublishOptions({ source: TrackSource.SOURCE_MICROPHONE }),\n};\n\nexport class RoomIO {\n private agentSession: AgentSession;\n private room: Room;\n private inputOptions: RoomInputOptions;\n private outputOptions: RoomOutputOptions;\n\n private audioInput?: ParticipantAudioInputStream;\n private participantAudioOutput?: ParticipantAudioOutput;\n private userTranscriptOutput?: ParalellTextOutput;\n private agentTranscriptOutput?: ParalellTextOutput;\n private transcriptionSynchronizer?: TranscriptionSynchronizer;\n private participantIdentity: string | null = null;\n private textStreamHandlerRegistered = false;\n\n private participantAvailableFuture: Future<RemoteParticipant> = new Future();\n private roomConnectedFuture: Future<void> = new Future();\n\n // Use stream API for transcript queue\n private userTranscriptStream = new IdentityTransform<UserInputTranscribedEvent>();\n private userTranscriptWriter: WritableStreamDefaultWriter<UserInputTranscribedEvent>;\n private forwardUserTranscriptTask?: Task<void>;\n private initTask?: Task<void>;\n\n private logger = log();\n\n constructor({\n agentSession,\n room,\n participant = null,\n inputOptions,\n outputOptions,\n }: {\n agentSession: AgentSession;\n room: Room;\n participant?: RemoteParticipant | string | null;\n inputOptions?: Partial<RoomInputOptions>;\n outputOptions?: Partial<RoomOutputOptions>;\n }) {\n this.agentSession = agentSession;\n this.room = room;\n this.inputOptions = { ...DEFAULT_ROOM_INPUT_OPTIONS, ...inputOptions };\n this.outputOptions = { ...DEFAULT_ROOM_OUTPUT_OPTIONS, ...outputOptions };\n\n this.userTranscriptWriter = this.userTranscriptStream.writable.getWriter();\n\n this.participantIdentity = participant\n ? typeof participant === 'string'\n ? participant\n : participant.identity\n : this.inputOptions.participantIdentity ?? null;\n }\n private async init(signal: AbortSignal): Promise<void> {\n await Promise.race([this.roomConnectedFuture.await, waitForAbort(signal)]);\n if (signal.aborted) {\n return;\n }\n\n for (const participant of this.room.remoteParticipants.values()) {\n this.onParticipantConnected(participant);\n }\n if (signal.aborted) {\n return;\n }\n\n const participant = await Promise.race([\n this.participantAvailableFuture.await,\n waitForAbort(signal),\n ]);\n\n if (!participant) {\n return;\n }\n\n this.setParticipant(participant.identity);\n\n // init agent outputs\n this.updateTranscriptionOutput({\n output: this.agentTranscriptOutput,\n participant: this.room.localParticipant?.identity ?? null,\n });\n\n await this.participantAudioOutput?.start(signal);\n }\n\n private onConnectionStateChanged = (state: ConnectionState) => {\n this.logger.debug({ state }, 'connection state changed');\n if (\n state === ConnectionState.CONN_CONNECTED &&\n this.room.isConnected &&\n !this.roomConnectedFuture.done\n ) {\n this.roomConnectedFuture.resolve();\n }\n };\n\n private onParticipantConnected = (participant: RemoteParticipant) => {\n if (this.participantAvailableFuture.done) {\n return;\n }\n\n if (this.participantIdentity) {\n if (participant.identity !== this.participantIdentity) {\n return;\n }\n } else if (\n // otherwise, skip participants that are marked as publishing for this agent\n participant.attributes?.[ATTRIBUTE_PUBLISH_ON_BEHALF] === this.room.localParticipant?.identity\n ) {\n return;\n }\n\n const acceptedKinds = this.inputOptions.participantKinds ?? DEFAULT_PARTICIPANT_KINDS;\n if (participant.info.kind !== undefined && !acceptedKinds.includes(participant.info.kind)) {\n return;\n }\n\n this.participantAvailableFuture.resolve(participant);\n };\n\n private onParticipantDisconnected = (participant: RemoteParticipant) => {\n if (participant.identity !== this.participantIdentity) {\n return;\n }\n this.participantAvailableFuture = new Future<RemoteParticipant>();\n if (\n this.inputOptions.closeOnDisconnect &&\n participant.disconnectReason &&\n CLOSE_ON_DISCONNECT_REASONS.includes(participant.disconnectReason)\n ) {\n this.logger.info(\n {\n participant: participant.identity,\n reason: DisconnectReason[participant.disconnectReason],\n },\n 'closing agent session due to participant disconnect ' +\n '(disable via `RoomInputOptions.closeOnDisconnect=False`)',\n );\n this.agentSession._closeSoon({\n reason: CloseReason.PARTICIPANT_DISCONNECTED,\n });\n }\n };\n\n private onUserInputTranscribed = (ev: UserInputTranscribedEvent) => {\n this.userTranscriptWriter.write(ev).catch((error) => {\n this.logger.error({ error }, 'Failed to write transcript event to stream');\n });\n };\n\n private onAgentStateChanged = async (ev: AgentStateChangedEvent) => {\n if (this.room.isConnected && this.room.localParticipant) {\n await this.room.localParticipant.setAttributes({\n [`lk.agent.state`]: ev.newState,\n });\n }\n };\n\n private onUserTextInput = (reader: TextStreamReader, participantInfo: { identity: string }) => {\n if (this.participantIdentity && participantInfo.identity !== this.participantIdentity) {\n return;\n }\n\n const participant = this.room.remoteParticipants.get(participantInfo.identity);\n if (!participant) {\n this.logger.warn('participant not found, ignoring text input');\n return;\n }\n\n const readText = async () => {\n const text = await reader.readAll();\n\n const textInputResult = this.inputOptions.textInputCallback!(this.agentSession, {\n text,\n info: reader.info,\n participantIdentity: participantInfo.identity,\n });\n\n // check if callback is a Promise\n if (textInputResult instanceof Promise) {\n await textInputResult;\n }\n };\n\n readText().catch((error) => {\n this.logger.error({ error }, 'Error reading text input');\n });\n };\n\n private async forwardUserTranscript(signal: AbortSignal): Promise<void> {\n const reader = this.userTranscriptStream.readable.getReader();\n try {\n while (!signal.aborted) {\n const { done, value } = await reader.read();\n if (done) break;\n\n const event = value;\n // IMPORTANT: need to await here to avoid race condition\n await this.userTranscriptOutput?.captureText(event.transcript);\n if (event.isFinal) {\n this.userTranscriptOutput?.flush();\n }\n }\n } catch (error) {\n this.logger.error({ error }, 'Error processing transcript stream');\n }\n }\n\n private createTranscriptionOutput(options: {\n isDeltaStream: boolean;\n participant: Participant | string | null;\n }) {\n return new ParalellTextOutput([\n new ParticipantLegacyTranscriptionOutput(\n this.room,\n options.isDeltaStream,\n options.participant,\n ),\n new ParticipantTranscriptionOutput(this.room, options.isDeltaStream, options.participant),\n ]);\n }\n\n private updateTranscriptionOutput({\n output,\n participant,\n }: {\n output?: ParalellTextOutput;\n participant: string | null;\n }) {\n if (!output) {\n return;\n }\n\n for (const sink of output._sinks) {\n if (\n sink instanceof ParticipantLegacyTranscriptionOutput ||\n sink instanceof ParticipantTranscriptionOutput\n ) {\n sink.setParticipant(participant);\n }\n }\n }\n\n get audioOutput(): AudioOutput | undefined {\n if (!this.transcriptionSynchronizer) {\n return this.participantAudioOutput;\n }\n\n return this.transcriptionSynchronizer.audioOutput;\n }\n\n get transcriptionOutput(): TextOutput | undefined {\n if (!this.transcriptionSynchronizer) {\n return this.agentTranscriptOutput;\n }\n\n return this.transcriptionSynchronizer.textOutput;\n }\n\n get isParticipantAvailable(): boolean {\n return this.participantAvailableFuture.done;\n }\n\n get rtcRoom(): Room {\n return this.room;\n }\n\n get linkedParticipant(): RemoteParticipant | undefined {\n if (!this.isParticipantAvailable) {\n return undefined;\n }\n\n return this.participantAvailableFuture.result;\n }\n\n get localParticipant(): Participant | undefined {\n return this.room.localParticipant ?? undefined;\n }\n\n /** Switch to a different participant */\n setParticipant(participantIdentity: string | null) {\n this.logger.debug({ participantIdentity }, 'setting participant');\n if (participantIdentity === null) {\n this.unsetParticipant();\n return;\n }\n\n if (this.participantIdentity !== participantIdentity) {\n this.participantAvailableFuture = new Future<RemoteParticipant>();\n\n // check if new participant is already connected\n for (const participant of this.room.remoteParticipants.values()) {\n if (participant.identity === participantIdentity) {\n this.participantAvailableFuture.resolve(participant);\n break;\n }\n }\n }\n\n // update participant identity and handlers\n this.participantIdentity = participantIdentity;\n this.audioInput?.setParticipant(participantIdentity);\n this.updateTranscriptionOutput({\n output: this.userTranscriptOutput,\n participant: participantIdentity,\n });\n }\n\n unsetParticipant() {\n this.participantIdentity = null;\n this.participantAvailableFuture = new Future<RemoteParticipant>();\n this.audioInput?.setParticipant(null);\n this.updateTranscriptionOutput({\n output: this.userTranscriptOutput,\n participant: null,\n });\n }\n\n start() {\n // -- create inputs --\n\n if (this.inputOptions.textEnabled) {\n try {\n this.room.registerTextStreamHandler(TOPIC_CHAT, this.onUserTextInput);\n this.textStreamHandlerRegistered = true;\n } catch (error) {\n if (this.inputOptions.textEnabled) {\n this.logger.warn(`text stream handler for topic \"${TOPIC_CHAT}\" already set, ignoring`);\n }\n }\n }\n\n if (this.inputOptions.audioEnabled) {\n this.audioInput = new ParticipantAudioInputStream({\n room: this.room,\n sampleRate: this.inputOptions.audioSampleRate,\n numChannels: this.inputOptions.audioNumChannels,\n noiseCancellation: this.inputOptions.noiseCancellation,\n });\n }\n\n // -- create outputs --\n if (this.outputOptions.audioEnabled) {\n this.participantAudioOutput = new ParticipantAudioOutput(this.room, {\n sampleRate: this.outputOptions.audioSampleRate,\n numChannels: this.outputOptions.audioNumChannels,\n trackPublishOptions: this.outputOptions.audioPublishOptions,\n });\n }\n if (this.outputOptions.transcriptionEnabled) {\n this.userTranscriptOutput = this.createTranscriptionOutput({\n isDeltaStream: false,\n participant: this.participantIdentity,\n });\n // Start the transcript forwarding\n this.forwardUserTranscriptTask = Task.from((controller) =>\n this.forwardUserTranscript(controller.signal),\n );\n this.agentTranscriptOutput = this.createTranscriptionOutput({\n isDeltaStream: true,\n participant: null,\n });\n\n // use the RoomIO's audio output if available, otherwise use the agent's audio output\n // TODO(AJS-176): check for agent output\n const audioOutput = this.participantAudioOutput;\n if (this.outputOptions.syncTranscription && audioOutput) {\n this.transcriptionSynchronizer = new TranscriptionSynchronizer(\n audioOutput,\n this.agentTranscriptOutput,\n );\n }\n }\n\n // -- set the room event handlers --\n this.room.on(RoomEvent.ParticipantConnected, this.onParticipantConnected);\n this.room.on(RoomEvent.ConnectionStateChanged, this.onConnectionStateChanged);\n this.room.on(RoomEvent.ParticipantDisconnected, this.onParticipantDisconnected);\n if (this.room.isConnected) {\n this.onConnectionStateChanged(ConnectionState.CONN_CONNECTED);\n }\n\n this.initTask = Task.from((controller) => this.init(controller.signal));\n\n // attach the agent to the session\n if (this.audioInput) {\n this.agentSession.input.audio = this.audioInput;\n }\n if (this.audioOutput) {\n this.agentSession.output.audio = this.audioOutput;\n }\n if (this.transcriptionOutput) {\n this.agentSession.output.transcription = this.transcriptionOutput;\n }\n\n this.agentSession.on(AgentSessionEventTypes.AgentStateChanged, this.onAgentStateChanged);\n this.agentSession.on(AgentSessionEventTypes.UserInputTranscribed, this.onUserInputTranscribed);\n }\n\n async close() {\n this.room.off(RoomEvent.ParticipantConnected, this.onParticipantConnected);\n this.room.off(RoomEvent.ConnectionStateChanged, this.onConnectionStateChanged);\n this.room.off(RoomEvent.ParticipantDisconnected, this.onParticipantDisconnected);\n this.agentSession.off(AgentSessionEventTypes.UserInputTranscribed, this.onUserInputTranscribed);\n this.agentSession.off(AgentSessionEventTypes.AgentStateChanged, this.onAgentStateChanged);\n\n if (this.textStreamHandlerRegistered) {\n this.room.unregisterTextStreamHandler(TOPIC_CHAT);\n this.textStreamHandlerRegistered = false;\n }\n\n await this.initTask?.cancelAndWait();\n\n // Close stream FIRST so reader.read() in forwardUserTranscript can exit.\n // This is a workaround for a race condition in the stream API.\n this.userTranscriptWriter.close();\n await this.forwardUserTranscriptTask?.cancelAndWait();\n\n await this.audioInput?.close();\n await this.participantAudioOutput?.close();\n await this.transcriptionSynchronizer?.close();\n }\n}\n"],"mappings":"AAIA;AAAA,EAEE;AAAA,EACA;AAAA,EAIA;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,SAAS,6BAA6B,kBAAkB;AACxD,SAAS,WAAW;AACpB,SAAS,yBAAyB;AAClC,SAAS,QAAQ,MAAM,oBAAoB;AAC3C,eAAkC;AAClC;AAAA,EACE;AAAA,EAEA;AAAA,OAEK;AAGP,SAAS,iCAAiC;AAC1C,SAAS,mCAAmC;AAC5C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEA,MAAM,8BAAiD,CAAC,MAAM,OAAO;AAC1E,OAAK,UAAU;AACf,OAAK,cAAc,EAAE,WAAW,GAAG,KAAK,CAAC;AAC3C;AAEA,MAAM,4BAA+C;AAAA,EACnD,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,gBAAgB;AAClB;AAEA,MAAM,8BAAkD;AAAA,EACtD,iBAAiB;AAAA,EACjB,iBAAiB;AAAA,EACjB,iBAAiB;AACnB;AA2CA,MAAM,6BAA+C;AAAA,EACnD,iBAAiB;AAAA,EACjB,kBAAkB;AAAA,EAClB,aAAa;AAAA,EACb,cAAc;AAAA,EACd,cAAc;AAAA,EACd,mBAAmB;AAAA,EACnB,mBAAmB;AACrB;AAEA,MAAM,8BAAiD;AAAA,EACrD,iBAAiB;AAAA,EACjB,kBAAkB;AAAA,EAClB,sBAAsB;AAAA,EACtB,cAAc;AAAA,EACd,mBAAmB;AAAA,EACnB,qBAAqB,IAAI,oBAAoB,EAAE,QAAQ,YAAY,kBAAkB,CAAC;AACxF;AAEO,MAAM,OAAO;AAAA,EACV;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,sBAAqC;AAAA,EACrC,8BAA8B;AAAA,EAE9B,6BAAwD,IAAI,OAAO;AAAA,EACnE,sBAAoC,IAAI,OAAO;AAAA;AAAA,EAG/C,uBAAuB,IAAI,kBAA6C;AAAA,EACxE;AAAA,EACA;AAAA,EACA;AAAA,EAEA,SAAS,IAAI;AAAA,EAErB,YAAY;AAAA,IACV;AAAA,IACA;AAAA,IACA,cAAc;AAAA,IACd;AAAA,IACA;AAAA,EACF,GAMG;AACD,SAAK,eAAe;AACpB,SAAK,OAAO;AACZ,SAAK,eAAe,EAAE,GAAG,4BAA4B,GAAG,aAAa;AACrE,SAAK,gBAAgB,EAAE,GAAG,6BAA6B,GAAG,cAAc;AAExE,SAAK,uBAAuB,KAAK,qBAAqB,SAAS,UAAU;AAEzE,SAAK,sBAAsB,cACvB,OAAO,gBAAgB,WACrB,cACA,YAAY,WACd,KAAK,aAAa,uBAAuB;AAAA,EAC/C;AAAA,EACA,MAAc,KAAK,QAAoC;AAzKzD;AA0KI,UAAM,QAAQ,KAAK,CAAC,KAAK,oBAAoB,OAAO,aAAa,MAAM,CAAC,CAAC;AACzE,QAAI,OAAO,SAAS;AAClB;AAAA,IACF;AAEA,eAAWA,gBAAe,KAAK,KAAK,mBAAmB,OAAO,GAAG;AAC/D,WAAK,uBAAuBA,YAAW;AAAA,IACzC;AACA,QAAI,OAAO,SAAS;AAClB;AAAA,IACF;AAEA,UAAM,cAAc,MAAM,QAAQ,KAAK;AAAA,MACrC,KAAK,2BAA2B;AAAA,MAChC,aAAa,MAAM;AAAA,IACrB,CAAC;AAED,QAAI,CAAC,aAAa;AAChB;AAAA,IACF;AAEA,SAAK,eAAe,YAAY,QAAQ;AAGxC,SAAK,0BAA0B;AAAA,MAC7B,QAAQ,KAAK;AAAA,MACb,eAAa,UAAK,KAAK,qBAAV,mBAA4B,aAAY;AAAA,IACvD,CAAC;AAED,YAAM,UAAK,2BAAL,mBAA6B,MAAM;AAAA,EAC3C;AAAA,EAEQ,2BAA2B,CAAC,UAA2B;AAC7D,SAAK,OAAO,MAAM,EAAE,MAAM,GAAG,0BAA0B;AACvD,QACE,UAAU,gBAAgB,kBAC1B,KAAK,KAAK,eACV,CAAC,KAAK,oBAAoB,MAC1B;AACA,WAAK,oBAAoB,QAAQ;AAAA,IACnC;AAAA,EACF;AAAA,EAEQ,yBAAyB,CAAC,gBAAmC;AArNvE;AAsNI,QAAI,KAAK,2BAA2B,MAAM;AACxC;AAAA,IACF;AAEA,QAAI,KAAK,qBAAqB;AAC5B,UAAI,YAAY,aAAa,KAAK,qBAAqB;AACrD;AAAA,MACF;AAAA,IACF;AAAA;AAAA,QAEE,iBAAY,eAAZ,mBAAyB,oCAAiC,UAAK,KAAK,qBAAV,mBAA4B;AAAA,MACtF;AACA;AAAA,IACF;AAEA,UAAM,gBAAgB,KAAK,aAAa,oBAAoB;AAC5D,QAAI,YAAY,KAAK,SAAS,UAAa,CAAC,cAAc,SAAS,YAAY,KAAK,IAAI,GAAG;AACzF;AAAA,IACF;AAEA,SAAK,2BAA2B,QAAQ,WAAW;AAAA,EACrD;AAAA,EAEQ,4BAA4B,CAAC,gBAAmC;AACtE,QAAI,YAAY,aAAa,KAAK,qBAAqB;AACrD;AAAA,IACF;AACA,SAAK,6BAA6B,IAAI,OAA0B;AAChE,QACE,KAAK,aAAa,qBAClB,YAAY,oBACZ,4BAA4B,SAAS,YAAY,gBAAgB,GACjE;AACA,WAAK,OAAO;AAAA,QACV;AAAA,UACE,aAAa,YAAY;AAAA,UACzB,QAAQ,iBAAiB,YAAY,gBAAgB;AAAA,QACvD;AAAA,QACA;AAAA,MAEF;AACA,WAAK,aAAa,WAAW;AAAA,QAC3B,QAAQ,YAAY;AAAA,MACtB,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEQ,yBAAyB,CAAC,OAAkC;AAClE,SAAK,qBAAqB,MAAM,EAAE,EAAE,MAAM,CAAC,UAAU;AACnD,WAAK,OAAO,MAAM,EAAE,MAAM,GAAG,4CAA4C;AAAA,IAC3E,CAAC;AAAA,EACH;AAAA,EAEQ,sBAAsB,OAAO,OAA+B;AAClE,QAAI,KAAK,KAAK,eAAe,KAAK,KAAK,kBAAkB;AACvD,YAAM,KAAK,KAAK,iBAAiB,cAAc;AAAA,QAC7C,CAAC,gBAAgB,GAAG,GAAG;AAAA,MACzB,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEQ,kBAAkB,CAAC,QAA0B,oBAA0C;AAC7F,QAAI,KAAK,uBAAuB,gBAAgB,aAAa,KAAK,qBAAqB;AACrF;AAAA,IACF;AAEA,UAAM,cAAc,KAAK,KAAK,mBAAmB,IAAI,gBAAgB,QAAQ;AAC7E,QAAI,CAAC,aAAa;AAChB,WAAK,OAAO,KAAK,4CAA4C;AAC7D;AAAA,IACF;AAEA,UAAM,WAAW,YAAY;AAC3B,YAAM,OAAO,MAAM,OAAO,QAAQ;AAElC,YAAM,kBAAkB,KAAK,aAAa,kBAAmB,KAAK,cAAc;AAAA,QAC9E;AAAA,QACA,MAAM,OAAO;AAAA,QACb,qBAAqB,gBAAgB;AAAA,MACvC,CAAC;AAGD,UAAI,2BAA2B,SAAS;AACtC,cAAM;AAAA,MACR;AAAA,IACF;AAEA,aAAS,EAAE,MAAM,CAAC,UAAU;AAC1B,WAAK,OAAO,MAAM,EAAE,MAAM,GAAG,0BAA0B;AAAA,IACzD,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,sBAAsB,QAAoC;AAlT1E;AAmTI,UAAM,SAAS,KAAK,qBAAqB,SAAS,UAAU;AAC5D,QAAI;AACF,aAAO,CAAC,OAAO,SAAS;AACtB,cAAM,EAAE,MAAM,MAAM,IAAI,MAAM,OAAO,KAAK;AAC1C,YAAI,KAAM;AAEV,cAAM,QAAQ;AAEd,gBAAM,UAAK,yBAAL,mBAA2B,YAAY,MAAM;AACnD,YAAI,MAAM,SAAS;AACjB,qBAAK,yBAAL,mBAA2B;AAAA,QAC7B;AAAA,MACF;AAAA,IACF,SAAS,OAAO;AACd,WAAK,OAAO,MAAM,EAAE,MAAM,GAAG,oCAAoC;AAAA,IACnE;AAAA,EACF;AAAA,EAEQ,0BAA0B,SAG/B;AACD,WAAO,IAAI,mBAAmB;AAAA,MAC5B,IAAI;AAAA,QACF,KAAK;AAAA,QACL,QAAQ;AAAA,QACR,QAAQ;AAAA,MACV;AAAA,MACA,IAAI,+BAA+B,KAAK,MAAM,QAAQ,eAAe,QAAQ,WAAW;AAAA,IAC1F,CAAC;AAAA,EACH;AAAA,EAEQ,0BAA0B;AAAA,IAChC;AAAA,IACA;AAAA,EACF,GAGG;AACD,QAAI,CAAC,QAAQ;AACX;AAAA,IACF;AAEA,eAAW,QAAQ,OAAO,QAAQ;AAChC,UACE,gBAAgB,wCAChB,gBAAgB,gCAChB;AACA,aAAK,eAAe,WAAW;AAAA,MACjC;AAAA,IACF;AAAA,EACF;AAAA,EAEA,IAAI,cAAuC;AACzC,QAAI,CAAC,KAAK,2BAA2B;AACnC,aAAO,KAAK;AAAA,IACd;AAEA,WAAO,KAAK,0BAA0B;AAAA,EACxC;AAAA,EAEA,IAAI,sBAA8C;AAChD,QAAI,CAAC,KAAK,2BAA2B;AACnC,aAAO,KAAK;AAAA,IACd;AAEA,WAAO,KAAK,0BAA0B;AAAA,EACxC;AAAA,EAEA,IAAI,yBAAkC;AACpC,WAAO,KAAK,2BAA2B;AAAA,EACzC;AAAA,EAEA,IAAI,UAAgB;AAClB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,oBAAmD;AACrD,QAAI,CAAC,KAAK,wBAAwB;AAChC,aAAO;AAAA,IACT;AAEA,WAAO,KAAK,2BAA2B;AAAA,EACzC;AAAA,EAEA,IAAI,mBAA4C;AAC9C,WAAO,KAAK,KAAK,oBAAoB;AAAA,EACvC;AAAA;AAAA,EAGA,eAAe,qBAAoC;AA7YrD;AA8YI,SAAK,OAAO,MAAM,EAAE,oBAAoB,GAAG,qBAAqB;AAChE,QAAI,wBAAwB,MAAM;AAChC,WAAK,iBAAiB;AACtB;AAAA,IACF;AAEA,QAAI,KAAK,wBAAwB,qBAAqB;AACpD,WAAK,6BAA6B,IAAI,OAA0B;AAGhE,iBAAW,eAAe,KAAK,KAAK,mBAAmB,OAAO,GAAG;AAC/D,YAAI,YAAY,aAAa,qBAAqB;AAChD,eAAK,2BAA2B,QAAQ,WAAW;AACnD;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAGA,SAAK,sBAAsB;AAC3B,eAAK,eAAL,mBAAiB,eAAe;AAChC,SAAK,0BAA0B;AAAA,MAC7B,QAAQ,KAAK;AAAA,MACb,aAAa;AAAA,IACf,CAAC;AAAA,EACH;AAAA,EAEA,mBAAmB;AAzarB;AA0aI,SAAK,sBAAsB;AAC3B,SAAK,6BAA6B,IAAI,OAA0B;AAChE,eAAK,eAAL,mBAAiB,eAAe;AAChC,SAAK,0BAA0B;AAAA,MAC7B,QAAQ,KAAK;AAAA,MACb,aAAa;AAAA,IACf,CAAC;AAAA,EACH;AAAA,EAEA,QAAQ;AAGN,QAAI,KAAK,aAAa,aAAa;AACjC,UAAI;AACF,aAAK,KAAK,0BAA0B,YAAY,KAAK,eAAe;AACpE,aAAK,8BAA8B;AAAA,MACrC,SAAS,OAAO;AACd,YAAI,KAAK,aAAa,aAAa;AACjC,eAAK,OAAO,KAAK,kCAAkC,UAAU,yBAAyB;AAAA,QACxF;AAAA,MACF;AAAA,IACF;AAEA,QAAI,KAAK,aAAa,cAAc;AAClC,WAAK,aAAa,IAAI,4BAA4B;AAAA,QAChD,MAAM,KAAK;AAAA,QACX,YAAY,KAAK,aAAa;AAAA,QAC9B,aAAa,KAAK,aAAa;AAAA,QAC/B,mBAAmB,KAAK,aAAa;AAAA,MACvC,CAAC;AAAA,IACH;AAGA,QAAI,KAAK,cAAc,cAAc;AACnC,WAAK,yBAAyB,IAAI,uBAAuB,KAAK,MAAM;AAAA,QAClE,YAAY,KAAK,cAAc;AAAA,QAC/B,aAAa,KAAK,cAAc;AAAA,QAChC,qBAAqB,KAAK,cAAc;AAAA,MAC1C,CAAC;AAAA,IACH;AACA,QAAI,KAAK,cAAc,sBAAsB;AAC3C,WAAK,uBAAuB,KAAK,0BAA0B;AAAA,QACzD,eAAe;AAAA,QACf,aAAa,KAAK;AAAA,MACpB,CAAC;AAED,WAAK,4BAA4B,KAAK;AAAA,QAAK,CAAC,eAC1C,KAAK,sBAAsB,WAAW,MAAM;AAAA,MAC9C;AACA,WAAK,wBAAwB,KAAK,0BAA0B;AAAA,QAC1D,eAAe;AAAA,QACf,aAAa;AAAA,MACf,CAAC;AAID,YAAM,cAAc,KAAK;AACzB,UAAI,KAAK,cAAc,qBAAqB,aAAa;AACvD,aAAK,4BAA4B,IAAI;AAAA,UACnC;AAAA,UACA,KAAK;AAAA,QACP;AAAA,MACF;AAAA,IACF;AAGA,SAAK,KAAK,GAAG,UAAU,sBAAsB,KAAK,sBAAsB;AACxE,SAAK,KAAK,GAAG,UAAU,wBAAwB,KAAK,wBAAwB;AAC5E,SAAK,KAAK,GAAG,UAAU,yBAAyB,KAAK,yBAAyB;AAC9E,QAAI,KAAK,KAAK,aAAa;AACzB,WAAK,yBAAyB,gBAAgB,cAAc;AAAA,IAC9D;AAEA,SAAK,WAAW,KAAK,KAAK,CAAC,eAAe,KAAK,KAAK,WAAW,MAAM,CAAC;AAGtE,QAAI,KAAK,YAAY;AACnB,WAAK,aAAa,MAAM,QAAQ,KAAK;AAAA,IACvC;AACA,QAAI,KAAK,aAAa;AACpB,WAAK,aAAa,OAAO,QAAQ,KAAK;AAAA,IACxC;AACA,QAAI,KAAK,qBAAqB;AAC5B,WAAK,aAAa,OAAO,gBAAgB,KAAK;AAAA,IAChD;AAEA,SAAK,aAAa,GAAG,uBAAuB,mBAAmB,KAAK,mBAAmB;AACvF,SAAK,aAAa,GAAG,uBAAuB,sBAAsB,KAAK,sBAAsB;AAAA,EAC/F;AAAA,EAEA,MAAM,QAAQ;AApgBhB;AAqgBI,SAAK,KAAK,IAAI,UAAU,sBAAsB,KAAK,sBAAsB;AACzE,SAAK,KAAK,IAAI,UAAU,wBAAwB,KAAK,wBAAwB;AAC7E,SAAK,KAAK,IAAI,UAAU,yBAAyB,KAAK,yBAAyB;AAC/E,SAAK,aAAa,IAAI,uBAAuB,sBAAsB,KAAK,sBAAsB;AAC9F,SAAK,aAAa,IAAI,uBAAuB,mBAAmB,KAAK,mBAAmB;AAExF,QAAI,KAAK,6BAA6B;AACpC,WAAK,KAAK,4BAA4B,UAAU;AAChD,WAAK,8BAA8B;AAAA,IACrC;AAEA,YAAM,UAAK,aAAL,mBAAe;AAIrB,SAAK,qBAAqB,MAAM;AAChC,YAAM,UAAK,8BAAL,mBAAgC;AAEtC,YAAM,UAAK,eAAL,mBAAiB;AACvB,YAAM,UAAK,2BAAL,mBAA6B;AACnC,YAAM,UAAK,8BAAL,mBAAgC;AAAA,EACxC;AACF;","names":["participant"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/voice/turn_config/interruption.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\n/**\n * Configuration for interruption handling.\n */\nexport interface InterruptionOptions {\n /**\n * Whether interruptions are enabled.\n * @defaultValue true\n */\n enabled: boolean;\n /**\n * Interruption handling strategy. `\"adaptive\"` for ML-based detection, `\"vad\"` for simple\n * voice-activity detection. `undefined` means auto-detect.\n * @defaultValue undefined\n */\n mode: 'adaptive' | 'vad' | false | undefined;\n /**\n * When `true`, buffered audio is dropped while the agent is speaking and cannot be interrupted.\n * @defaultValue true\n */\n discardAudioIfUninterruptible: boolean;\n /**\n * Minimum speech length in milliseconds to register as an interruption.\n * @defaultValue 500\n */\n minDuration: number;\n /**\n * Minimum number of words to consider an interruption, only used if STT is enabled.\n * @defaultValue 0\n */\n minWords: number;\n /**\n * If set, emit an `agentFalseInterruption` event after this amount of time if the user is\n * silent and no user transcript is detected after the interruption. Set to `undefined` to\n * disable. The value is in milliseconds.\n * @defaultValue 2000\n */\n falseInterruptionTimeout: number;\n /**\n * Whether to resume the false interruption after the `falseInterruptionTimeout`.\n * @defaultValue true\n */\n resumeFalseInterruption: boolean;\n}\n\nexport const defaultInterruptionOptions = {\n enabled: true,\n mode: undefined,\n discardAudioIfUninterruptible: true,\n minDuration: 500,\n minWords: 0,\n falseInterruptionTimeout: 2000,\n resumeFalseInterruption: true,\n} as const satisfies InterruptionOptions;\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AA+CO,MAAM,6BAA6B;AAAA,EACxC,SAAS;AAAA,EACT,MAAM;AAAA,EACN,+BAA+B;AAAA,EAC/B,aAAa;AAAA,EACb,UAAU;AAAA,EACV,0BAA0B;AAAA,EAC1B,yBAAyB;AAC3B;","names":[]}
1
+ {"version":3,"sources":["../../../src/voice/turn_config/interruption.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\n/**\n * Configuration for interruption handling.\n */\nexport interface InterruptionOptions {\n /**\n * Whether interruptions are enabled.\n * @defaultValue true\n */\n enabled: boolean;\n /**\n * Interruption handling strategy. `\"adaptive\"` for ML-based detection, `\"vad\"` for simple\n * voice-activity detection. `undefined` means auto-detect.\n * @defaultValue undefined\n */\n mode: 'adaptive' | 'vad' | undefined;\n /**\n * When `true`, buffered audio is dropped while the agent is speaking and cannot be interrupted.\n * @defaultValue true\n */\n discardAudioIfUninterruptible: boolean;\n /**\n * Minimum speech length in milliseconds to register as an interruption.\n * @defaultValue 500\n */\n minDuration: number;\n /**\n * Minimum number of words to consider an interruption, only used if STT is enabled.\n * @defaultValue 0\n */\n minWords: number;\n /**\n * If set, emit an `agentFalseInterruption` event after this amount of time if the user is\n * silent and no user transcript is detected after the interruption. Set to `undefined` to\n * disable. The value is in milliseconds.\n * @defaultValue 2000\n */\n falseInterruptionTimeout: number;\n /**\n * Whether to resume the false interruption after the `falseInterruptionTimeout`.\n * @defaultValue true\n */\n resumeFalseInterruption: boolean;\n}\n\nexport const defaultInterruptionOptions = {\n enabled: true,\n mode: undefined,\n discardAudioIfUninterruptible: true,\n minDuration: 500,\n minWords: 0,\n falseInterruptionTimeout: 2000,\n resumeFalseInterruption: true,\n} as const satisfies InterruptionOptions;\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AA+CO,MAAM,6BAA6B;AAAA,EACxC,SAAS;AAAA,EACT,MAAM;AAAA,EACN,+BAA+B;AAAA,EAC/B,aAAa;AAAA,EACb,UAAU;AAAA,EACV,0BAA0B;AAAA,EAC1B,yBAAyB;AAC3B;","names":[]}
@@ -12,7 +12,7 @@ export interface InterruptionOptions {
12
12
  * voice-activity detection. `undefined` means auto-detect.
13
13
  * @defaultValue undefined
14
14
  */
15
- mode: 'adaptive' | 'vad' | false | undefined;
15
+ mode: 'adaptive' | 'vad' | undefined;
16
16
  /**
17
17
  * When `true`, buffered audio is dropped while the agent is speaking and cannot be interrupted.
18
18
  * @defaultValue true
@@ -12,7 +12,7 @@ export interface InterruptionOptions {
12
12
  * voice-activity detection. `undefined` means auto-detect.
13
13
  * @defaultValue undefined
14
14
  */
15
- mode: 'adaptive' | 'vad' | false | undefined;
15
+ mode: 'adaptive' | 'vad' | undefined;
16
16
  /**
17
17
  * When `true`, buffered audio is dropped while the agent is speaking and cannot be interrupted.
18
18
  * @defaultValue true
@@ -1 +1 @@
1
- {"version":3,"file":"interruption.d.ts","sourceRoot":"","sources":["../../../src/voice/turn_config/interruption.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB;;;;OAIG;IACH,IAAI,EAAE,UAAU,GAAG,KAAK,GAAG,KAAK,GAAG,SAAS,CAAC;IAC7C;;;OAGG;IACH,6BAA6B,EAAE,OAAO,CAAC;IACvC;;;OAGG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;;OAKG;IACH,wBAAwB,EAAE,MAAM,CAAC;IACjC;;;OAGG;IACH,uBAAuB,EAAE,OAAO,CAAC;CAClC;AAED,eAAO,MAAM,0BAA0B;;;;;;;;CAQC,CAAC"}
1
+ {"version":3,"file":"interruption.d.ts","sourceRoot":"","sources":["../../../src/voice/turn_config/interruption.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB;;;;OAIG;IACH,IAAI,EAAE,UAAU,GAAG,KAAK,GAAG,SAAS,CAAC;IACrC;;;OAGG;IACH,6BAA6B,EAAE,OAAO,CAAC;IACvC;;;OAGG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;;OAKG;IACH,wBAAwB,EAAE,MAAM,CAAC;IACjC;;;OAGG;IACH,uBAAuB,EAAE,OAAO,CAAC;CAClC;AAED,eAAO,MAAM,0BAA0B;;;;;;;;CAQC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/voice/turn_config/interruption.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\n/**\n * Configuration for interruption handling.\n */\nexport interface InterruptionOptions {\n /**\n * Whether interruptions are enabled.\n * @defaultValue true\n */\n enabled: boolean;\n /**\n * Interruption handling strategy. `\"adaptive\"` for ML-based detection, `\"vad\"` for simple\n * voice-activity detection. `undefined` means auto-detect.\n * @defaultValue undefined\n */\n mode: 'adaptive' | 'vad' | false | undefined;\n /**\n * When `true`, buffered audio is dropped while the agent is speaking and cannot be interrupted.\n * @defaultValue true\n */\n discardAudioIfUninterruptible: boolean;\n /**\n * Minimum speech length in milliseconds to register as an interruption.\n * @defaultValue 500\n */\n minDuration: number;\n /**\n * Minimum number of words to consider an interruption, only used if STT is enabled.\n * @defaultValue 0\n */\n minWords: number;\n /**\n * If set, emit an `agentFalseInterruption` event after this amount of time if the user is\n * silent and no user transcript is detected after the interruption. Set to `undefined` to\n * disable. The value is in milliseconds.\n * @defaultValue 2000\n */\n falseInterruptionTimeout: number;\n /**\n * Whether to resume the false interruption after the `falseInterruptionTimeout`.\n * @defaultValue true\n */\n resumeFalseInterruption: boolean;\n}\n\nexport const defaultInterruptionOptions = {\n enabled: true,\n mode: undefined,\n discardAudioIfUninterruptible: true,\n minDuration: 500,\n minWords: 0,\n falseInterruptionTimeout: 2000,\n resumeFalseInterruption: true,\n} as const satisfies InterruptionOptions;\n"],"mappings":"AA+CO,MAAM,6BAA6B;AAAA,EACxC,SAAS;AAAA,EACT,MAAM;AAAA,EACN,+BAA+B;AAAA,EAC/B,aAAa;AAAA,EACb,UAAU;AAAA,EACV,0BAA0B;AAAA,EAC1B,yBAAyB;AAC3B;","names":[]}
1
+ {"version":3,"sources":["../../../src/voice/turn_config/interruption.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\n/**\n * Configuration for interruption handling.\n */\nexport interface InterruptionOptions {\n /**\n * Whether interruptions are enabled.\n * @defaultValue true\n */\n enabled: boolean;\n /**\n * Interruption handling strategy. `\"adaptive\"` for ML-based detection, `\"vad\"` for simple\n * voice-activity detection. `undefined` means auto-detect.\n * @defaultValue undefined\n */\n mode: 'adaptive' | 'vad' | undefined;\n /**\n * When `true`, buffered audio is dropped while the agent is speaking and cannot be interrupted.\n * @defaultValue true\n */\n discardAudioIfUninterruptible: boolean;\n /**\n * Minimum speech length in milliseconds to register as an interruption.\n * @defaultValue 500\n */\n minDuration: number;\n /**\n * Minimum number of words to consider an interruption, only used if STT is enabled.\n * @defaultValue 0\n */\n minWords: number;\n /**\n * If set, emit an `agentFalseInterruption` event after this amount of time if the user is\n * silent and no user transcript is detected after the interruption. Set to `undefined` to\n * disable. The value is in milliseconds.\n * @defaultValue 2000\n */\n falseInterruptionTimeout: number;\n /**\n * Whether to resume the false interruption after the `falseInterruptionTimeout`.\n * @defaultValue true\n */\n resumeFalseInterruption: boolean;\n}\n\nexport const defaultInterruptionOptions = {\n enabled: true,\n mode: undefined,\n discardAudioIfUninterruptible: true,\n minDuration: 500,\n minWords: 0,\n falseInterruptionTimeout: 2000,\n resumeFalseInterruption: true,\n} as const satisfies InterruptionOptions;\n"],"mappings":"AA+CO,MAAM,6BAA6B;AAAA,EACxC,SAAS;AAAA,EACT,MAAM;AAAA,EACN,+BAA+B;AAAA,EAC/B,aAAa;AAAA,EACb,UAAU;AAAA,EACV,0BAA0B;AAAA,EAC1B,yBAAyB;AAC3B;","names":[]}
@@ -20,6 +20,7 @@ var utils_exports = {};
20
20
  __export(utils_exports, {
21
21
  mergeWithDefaults: () => mergeWithDefaults,
22
22
  migrateLegacyOptions: () => migrateLegacyOptions,
23
+ migrateTurnHandling: () => migrateTurnHandling,
23
24
  stripUndefined: () => stripUndefined
24
25
  });
25
26
  module.exports = __toCommonJS(utils_exports);
@@ -28,55 +29,82 @@ var import_agent_session = require("../agent_session.cjs");
28
29
  var import_endpointing = require("./endpointing.cjs");
29
30
  var import_interruption = require("./interruption.cjs");
30
31
  var import_turn_handling = require("./turn_handling.cjs");
32
+ const defaultSessionOptions = {
33
+ maxToolSteps: 3,
34
+ preemptiveGeneration: true,
35
+ userAwayTimeout: 15,
36
+ aecWarmupDuration: 3e3,
37
+ turnHandling: {},
38
+ useTtsAlignedTranscript: true
39
+ };
40
+ const defaultLegacyVoiceOptions = {
41
+ minEndpointingDelay: import_turn_handling.defaultTurnHandlingOptions.endpointing.minDelay,
42
+ maxEndpointingDelay: import_turn_handling.defaultTurnHandlingOptions.endpointing.maxDelay,
43
+ maxToolSteps: defaultSessionOptions.maxToolSteps,
44
+ preemptiveGeneration: defaultSessionOptions.preemptiveGeneration
45
+ };
31
46
  function migrateLegacyOptions(legacyOptions) {
32
- var _a, _b;
47
+ var _a, _b, _c;
33
48
  const logger = (0, import_log.log)();
34
- const { voiceOptions, turnDetection, options: sessionOptions, ...rest } = legacyOptions;
35
- if (voiceOptions !== void 0 && sessionOptions !== void 0) {
49
+ const {
50
+ voiceOptions,
51
+ turnDetection,
52
+ stt,
53
+ vad,
54
+ llm,
55
+ tts,
56
+ userData,
57
+ connOptions,
58
+ ...sessionOptions
59
+ } = legacyOptions;
60
+ if (voiceOptions !== void 0) {
36
61
  logger.warn(
37
- "Both voiceOptions and options have been supplied as part of the AgentSessionOptions, voiceOptions will be merged with options taking precedence"
62
+ "voiceOptions is deprecated, use top-level SessionOptions fields on AgentSessionOptions instead"
38
63
  );
39
64
  }
40
- const originalTurnDetection = ((_a = sessionOptions == null ? void 0 : sessionOptions.turnHandling) == null ? void 0 : _a.turnDetection) ?? ((_b = voiceOptions == null ? void 0 : voiceOptions.turnHandling) == null ? void 0 : _b.turnDetection) ?? turnDetection;
41
- const cloneableVoiceOptions = voiceOptions ? {
42
- ...voiceOptions,
43
- turnHandling: voiceOptions.turnHandling ? { ...voiceOptions.turnHandling, turnDetection: void 0 } : voiceOptions.turnHandling
44
- } : voiceOptions;
45
- const cloneableSessionOptions = sessionOptions ? {
46
- ...sessionOptions,
47
- turnHandling: sessionOptions.turnHandling ? { ...sessionOptions.turnHandling, turnDetection: void 0 } : sessionOptions.turnHandling
48
- } : sessionOptions;
49
- const mergedOptions = structuredClone({ ...cloneableVoiceOptions, ...cloneableSessionOptions });
50
65
  const turnHandling = {
51
66
  interruption: {
52
- discardAudioIfUninterruptible: mergedOptions == null ? void 0 : mergedOptions.discardAudioIfUninterruptible,
53
- minDuration: mergedOptions == null ? void 0 : mergedOptions.minInterruptionDuration,
54
- minWords: mergedOptions == null ? void 0 : mergedOptions.minInterruptionWords
67
+ discardAudioIfUninterruptible: voiceOptions == null ? void 0 : voiceOptions.discardAudioIfUninterruptible,
68
+ minDuration: voiceOptions == null ? void 0 : voiceOptions.minInterruptionDuration,
69
+ minWords: voiceOptions == null ? void 0 : voiceOptions.minInterruptionWords,
70
+ ...(_a = sessionOptions.turnHandling) == null ? void 0 : _a.interruption
55
71
  },
56
72
  endpointing: {
57
- minDelay: mergedOptions == null ? void 0 : mergedOptions.minEndpointingDelay,
58
- maxDelay: mergedOptions == null ? void 0 : mergedOptions.maxEndpointingDelay
73
+ minDelay: voiceOptions == null ? void 0 : voiceOptions.minEndpointingDelay,
74
+ maxDelay: voiceOptions == null ? void 0 : voiceOptions.maxEndpointingDelay,
75
+ ...(_b = sessionOptions.turnHandling) == null ? void 0 : _b.endpointing
59
76
  },
60
- ...mergedOptions.turnHandling,
61
- // Restore original turnDetection after spread to preserve class instance with methods
62
- // (structuredClone converts class instances to plain objects, losing prototype methods)
63
- turnDetection: originalTurnDetection
77
+ turnDetection: ((_c = sessionOptions == null ? void 0 : sessionOptions.turnHandling) == null ? void 0 : _c.turnDetection) ?? turnDetection
64
78
  };
65
- if ((mergedOptions == null ? void 0 : mergedOptions.allowInterruptions) === false) {
79
+ if ((voiceOptions == null ? void 0 : voiceOptions.allowInterruptions) === false && turnHandling.interruption.enabled === void 0) {
66
80
  turnHandling.interruption.enabled = false;
67
81
  }
68
- const optionsWithDefaults = {
69
- ...import_agent_session.defaultSessionOptions,
70
- ...mergedOptions,
71
- turnHandling: mergeWithDefaults(turnHandling)
72
- };
73
- const newAgentSessionOptions = {
74
- ...rest,
75
- options: optionsWithDefaults,
76
- voiceOptions: optionsWithDefaults,
77
- turnDetection: turnHandling.turnDetection
82
+ const migratedVoiceOptions = {};
83
+ if ((voiceOptions == null ? void 0 : voiceOptions.maxToolSteps) !== void 0) {
84
+ migratedVoiceOptions.maxToolSteps = voiceOptions.maxToolSteps;
85
+ }
86
+ if ((voiceOptions == null ? void 0 : voiceOptions.preemptiveGeneration) !== void 0) {
87
+ migratedVoiceOptions.preemptiveGeneration = voiceOptions.preemptiveGeneration;
88
+ }
89
+ if ((voiceOptions == null ? void 0 : voiceOptions.userAwayTimeout) !== void 0) {
90
+ migratedVoiceOptions.userAwayTimeout = voiceOptions.userAwayTimeout;
91
+ }
92
+ const legacyVoiceOptions = { ...defaultLegacyVoiceOptions, ...voiceOptions };
93
+ const agentSessionOptions = {
94
+ stt,
95
+ vad,
96
+ llm,
97
+ tts,
98
+ userData,
99
+ connOptions,
100
+ ...defaultSessionOptions,
101
+ ...migratedVoiceOptions,
102
+ ...sessionOptions,
103
+ turnHandling: mergeWithDefaults(turnHandling),
104
+ // repopulate the deprecated voice options with migrated options for backwards compatibility
105
+ voiceOptions: legacyVoiceOptions
78
106
  };
79
- return newAgentSessionOptions;
107
+ return { agentSessionOptions, legacyVoiceOptions };
80
108
  }
81
109
  function stripUndefined(obj) {
82
110
  return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== void 0));
@@ -88,10 +116,42 @@ function mergeWithDefaults(config) {
88
116
  interruption: { ...import_interruption.defaultInterruptionOptions, ...stripUndefined(config.interruption) }
89
117
  };
90
118
  }
119
+ function migrateTurnHandling(opts) {
120
+ if (opts.turnHandling !== void 0) {
121
+ return opts.turnHandling;
122
+ }
123
+ const migrated = {};
124
+ const endpointing = {};
125
+ if (opts.minEndpointingDelay !== void 0) {
126
+ endpointing.minDelay = opts.minEndpointingDelay;
127
+ }
128
+ if (opts.maxEndpointingDelay !== void 0) {
129
+ endpointing.maxDelay = opts.maxEndpointingDelay;
130
+ }
131
+ if (Object.keys(endpointing).length > 0) {
132
+ migrated.endpointing = endpointing;
133
+ }
134
+ const interruption = {};
135
+ if (opts.allowInterruptions === false) {
136
+ interruption.enabled = false;
137
+ }
138
+ if (Object.keys(interruption).length > 0) {
139
+ migrated.interruption = interruption;
140
+ }
141
+ if (opts.turnDetection !== void 0) {
142
+ migrated.turnDetection = opts.turnDetection;
143
+ }
144
+ return {
145
+ ...migrated.endpointing ? { endpointing: migrated.endpointing } : {},
146
+ ...migrated.interruption ? { interruption: migrated.interruption } : {},
147
+ ...migrated.turnDetection !== void 0 ? { turnDetection: migrated.turnDetection } : {}
148
+ };
149
+ }
91
150
  // Annotate the CommonJS export names for ESM import in node:
92
151
  0 && (module.exports = {
93
152
  mergeWithDefaults,
94
153
  migrateLegacyOptions,
154
+ migrateTurnHandling,
95
155
  stripUndefined
96
156
  });
97
157
  //# sourceMappingURL=utils.cjs.map