@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/audio_recognition.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { ParticipantKind } from '@livekit/rtc-node';\nimport { AudioFrame } from '@livekit/rtc-node';\nimport {\n type Context,\n ROOT_CONTEXT,\n type Span,\n context as otelContext,\n trace,\n} from '@opentelemetry/api';\nimport type { WritableStreamDefaultWriter } from 'node:stream/web';\nimport { ReadableStream } from 'node:stream/web';\nimport { InterruptionDetectionError } from '../inference/interruption/errors.js';\nimport type { AdaptiveInterruptionDetector } from '../inference/interruption/interruption_detector.js';\nimport { InterruptionStreamSentinel } from '../inference/interruption/interruption_stream.js';\nimport {\n type InterruptionSentinel,\n type OverlappingSpeechEvent,\n} from '../inference/interruption/types.js';\nimport { type ChatContext } from '../llm/chat_context.js';\nimport { log } from '../log.js';\nimport { DeferredReadableStream, isStreamReaderReleaseError } from '../stream/deferred_stream.js';\nimport { IdentityTransform } from '../stream/identity_transform.js';\nimport { mergeReadableStreams } from '../stream/merge_readable_streams.js';\nimport { type StreamChannel, createStreamChannel } from '../stream/stream_channel.js';\nimport { type SpeechEvent, SpeechEventType } from '../stt/stt.js';\nimport { traceTypes, tracer } from '../telemetry/index.js';\nimport { Task, delay, waitForAbort } from '../utils.js';\nimport { type VAD, type VADEvent, VADEventType } from '../vad.js';\nimport type { TurnDetectionMode } from './agent_session.js';\nimport type { STTNode } from './io.js';\nimport { setParticipantSpanAttributes } from './utils.js';\n\nexport interface EndOfTurnInfo {\n /** The new transcript text from the user's speech. */\n newTranscript: string;\n /** Confidence score of the transcript (0-1). */\n transcriptConfidence: number;\n /** Delay from speech stop to final transcription in milliseconds. */\n transcriptionDelay: number;\n /** Delay from speech stop to end of utterance detection in milliseconds. */\n endOfUtteranceDelay: number;\n /** Timestamp when user started speaking (milliseconds since epoch). */\n startedSpeakingAt: number | undefined;\n /** Timestamp when user stopped speaking (milliseconds since epoch). */\n stoppedSpeakingAt: number | undefined;\n}\n\nexport interface PreemptiveGenerationInfo {\n newTranscript: string;\n transcriptConfidence: number;\n}\n\nexport interface RecognitionHooks {\n onInterruption: (ev: OverlappingSpeechEvent) => void;\n onStartOfSpeech: (ev: VADEvent) => void;\n onVADInferenceDone: (ev: VADEvent) => void;\n onEndOfSpeech: (ev: VADEvent) => void;\n onInterimTranscript: (ev: SpeechEvent) => void;\n onFinalTranscript: (ev: SpeechEvent) => void;\n onEndOfTurn: (info: EndOfTurnInfo) => Promise<boolean>;\n onPreemptiveGeneration: (info: PreemptiveGenerationInfo) => void;\n\n retrieveChatCtx: () => ChatContext;\n}\n\nexport interface _TurnDetector {\n /** The model name used by this turn detector. */\n readonly model: string;\n /** The provider name for this turn detector. */\n readonly provider: string;\n unlikelyThreshold: (language?: string) => Promise<number | undefined>;\n supportsLanguage: (language?: string) => Promise<boolean>;\n predictEndOfTurn(chatCtx: ChatContext, timeout?: number): Promise<number>;\n}\n\nexport interface AudioRecognitionOptions {\n /** Hooks for recognition events. */\n recognitionHooks: RecognitionHooks;\n /** Speech-to-text node. */\n stt?: STTNode;\n /** Voice activity detection. */\n vad?: VAD;\n /** Turn detector for end-of-turn prediction. */\n turnDetector?: _TurnDetector;\n /** Turn detection mode. */\n turnDetectionMode?: TurnDetectionMode;\n interruptionDetection?: AdaptiveInterruptionDetector;\n /** Minimum endpointing delay in milliseconds. */\n minEndpointingDelay: number;\n /** Maximum endpointing delay in milliseconds. */\n maxEndpointingDelay: number;\n /** Root span context for tracing. */\n rootSpanContext?: Context;\n /** STT model name for tracing */\n sttModel?: string;\n /** STT provider name for tracing */\n sttProvider?: string;\n /** Getter for linked participant for span attribution */\n getLinkedParticipant?: () => ParticipantLike | undefined;\n}\n\n/**\n * Minimal participant shape for span attribution.\n * Compatible with both `LocalParticipant` and `RemoteParticipant` from `@livekit/rtc-node`.\n */\nexport interface ParticipantLike {\n sid: string | undefined;\n identity: string;\n kind: ParticipantKind;\n}\n\n// TODO add ability to update stt/vad/interruption-detection\nexport class AudioRecognition {\n private hooks: RecognitionHooks;\n private stt?: STTNode;\n private vad?: VAD;\n private turnDetector?: _TurnDetector;\n private turnDetectionMode?: TurnDetectionMode;\n private minEndpointingDelay: number;\n private maxEndpointingDelay: number;\n private lastLanguage?: string;\n private rootSpanContext?: Context;\n private sttModel?: string;\n private sttProvider?: string;\n private getLinkedParticipant?: () => ParticipantLike | undefined;\n\n private deferredInputStream: DeferredReadableStream<AudioFrame>;\n private logger = log();\n private lastFinalTranscriptTime = 0;\n private audioTranscript = '';\n private audioInterimTranscript = '';\n private audioPreflightTranscript = '';\n private finalTranscriptConfidence: number[] = [];\n private lastSpeakingTime: number | undefined;\n private speechStartTime: number | undefined;\n private userTurnCommitted = false;\n private speaking = false;\n private sampleRate?: number;\n\n private userTurnSpan?: Span;\n\n private vadInputStream: ReadableStream<AudioFrame>;\n private sttInputStream: ReadableStream<AudioFrame>;\n private silenceAudioTransform = new IdentityTransform<AudioFrame>();\n private silenceAudioWriter: WritableStreamDefaultWriter<AudioFrame>;\n\n // all cancellable tasks\n private bounceEOUTask?: Task<void>;\n private commitUserTurnTask?: Task<void>;\n private vadTask?: Task<void>;\n private sttTask?: Task<void>;\n private interruptionTask?: Task<void>;\n\n // interruption detection\n private interruptionDetection?: AdaptiveInterruptionDetector;\n private _inputStartedAt?: number;\n private ignoreUserTranscriptUntil?: number;\n private transcriptBuffer: SpeechEvent[];\n private isInterruptionEnabled: boolean;\n private isAgentSpeaking: boolean;\n private interruptionStreamChannel?: StreamChannel<InterruptionSentinel | AudioFrame>;\n\n constructor(opts: AudioRecognitionOptions) {\n this.hooks = opts.recognitionHooks;\n this.stt = opts.stt;\n this.vad = opts.vad;\n this.turnDetector = opts.turnDetector;\n this.turnDetectionMode = opts.turnDetectionMode;\n this.minEndpointingDelay = opts.minEndpointingDelay;\n this.maxEndpointingDelay = opts.maxEndpointingDelay;\n this.lastLanguage = undefined;\n this.rootSpanContext = opts.rootSpanContext;\n this.sttModel = opts.sttModel;\n this.sttProvider = opts.sttProvider;\n this.getLinkedParticipant = opts.getLinkedParticipant;\n\n this.deferredInputStream = new DeferredReadableStream<AudioFrame>();\n this.interruptionDetection = opts.interruptionDetection;\n this.transcriptBuffer = [];\n this.isInterruptionEnabled = !!(opts.interruptionDetection && opts.vad);\n this.isAgentSpeaking = false;\n\n if (opts.interruptionDetection) {\n const [vadInputStream, teedInput] = this.deferredInputStream.stream.tee();\n const [inputStream, sttInputStream] = teedInput.tee();\n this.vadInputStream = vadInputStream;\n this.sttInputStream = mergeReadableStreams(\n sttInputStream,\n this.silenceAudioTransform.readable,\n );\n this.interruptionStreamChannel = createStreamChannel();\n this.interruptionStreamChannel.addStreamInput(inputStream);\n } else {\n const [vadInputStream, sttInputStream] = this.deferredInputStream.stream.tee();\n this.vadInputStream = vadInputStream;\n this.sttInputStream = mergeReadableStreams(\n sttInputStream,\n this.silenceAudioTransform.readable,\n );\n }\n this.silenceAudioWriter = this.silenceAudioTransform.writable.getWriter();\n }\n\n /**\n * Current transcript of the user's speech, including interim transcript if available.\n */\n get currentTranscript(): string {\n if (this.audioInterimTranscript) {\n return `${this.audioTranscript} ${this.audioInterimTranscript}`.trim();\n }\n return this.audioTranscript;\n }\n\n /** @internal */\n get inputStartedAt() {\n return this._inputStartedAt;\n }\n\n /** @internal */\n updateOptions(options: { turnDetection: TurnDetectionMode | undefined }): void {\n this.turnDetectionMode = options.turnDetection;\n }\n\n async start() {\n this.vadTask = Task.from(({ signal }) => this.createVadTask(this.vad, signal));\n this.vadTask.result.catch((err) => {\n this.logger.error(`Error running VAD task: ${err}`);\n });\n\n this.sttTask = Task.from(({ signal }) => this.createSttTask(this.stt, signal));\n this.sttTask.result.catch((err) => {\n this.logger.error(`Error running STT task: ${err}`);\n });\n\n this.interruptionTask = Task.from(({ signal }) =>\n this.createInterruptionTask(this.interruptionDetection, signal),\n );\n this.interruptionTask.result.catch((err) => {\n this.logger.error(`Error running interruption task: ${err}`);\n });\n }\n\n async stop() {\n await this.sttTask?.cancelAndWait();\n await this.vadTask?.cancelAndWait();\n await this.interruptionTask?.cancelAndWait();\n }\n\n async onStartOfAgentSpeech() {\n this.isAgentSpeaking = true;\n return this.trySendInterruptionSentinel(InterruptionStreamSentinel.agentSpeechStarted());\n }\n\n async onEndOfAgentSpeech(ignoreUserTranscriptUntil: number) {\n if (!this.isInterruptionEnabled) {\n this.isAgentSpeaking = false;\n return;\n }\n\n const inputOpen = await this.trySendInterruptionSentinel(\n InterruptionStreamSentinel.agentSpeechEnded(),\n );\n if (!inputOpen) {\n this.isAgentSpeaking = false;\n return;\n }\n\n if (this.isAgentSpeaking) {\n if (this.ignoreUserTranscriptUntil === undefined) {\n this.onEndOfOverlapSpeech(Date.now());\n }\n this.ignoreUserTranscriptUntil = this.ignoreUserTranscriptUntil\n ? Math.min(ignoreUserTranscriptUntil, this.ignoreUserTranscriptUntil)\n : ignoreUserTranscriptUntil;\n\n // flush held transcripts if possible\n await this.flushHeldTranscripts();\n }\n this.isAgentSpeaking = false;\n }\n\n /** Start interruption inference when agent is speaking and overlap speech starts. */\n async onStartOfOverlapSpeech(speechDuration: number, startedAt: number, userSpeakingSpan?: Span) {\n if (this.isAgentSpeaking) {\n this.trySendInterruptionSentinel(\n InterruptionStreamSentinel.overlapSpeechStarted(\n speechDuration,\n startedAt,\n userSpeakingSpan,\n ),\n );\n }\n }\n\n /** End interruption inference when overlap speech ends. */\n async onEndOfOverlapSpeech(endedAt: number, userSpeakingSpan?: Span) {\n if (!this.isInterruptionEnabled) {\n return;\n }\n if (userSpeakingSpan && userSpeakingSpan.isRecording()) {\n userSpeakingSpan.setAttribute(traceTypes.ATTR_IS_INTERRUPTION, 'false');\n }\n\n return this.trySendInterruptionSentinel(InterruptionStreamSentinel.overlapSpeechEnded(endedAt));\n }\n\n /**\n * Flush held transcripts whose *end time* is after the ignoreUserTranscriptUntil timestamp.\n * If the event has no timestamps, we assume it is the same as the next valid event.\n */\n private async flushHeldTranscripts() {\n if (\n !this.isInterruptionEnabled ||\n this.ignoreUserTranscriptUntil === undefined ||\n this.transcriptBuffer.length === 0\n ) {\n return;\n }\n\n if (!this._inputStartedAt) {\n this.transcriptBuffer = [];\n this.ignoreUserTranscriptUntil = undefined;\n return;\n }\n\n let emitFromIndex: number | null = null;\n let shouldFlush = false;\n\n for (let i = 0; i < this.transcriptBuffer.length; i++) {\n const ev = this.transcriptBuffer[i];\n if (!ev || !ev.alternatives || ev.alternatives.length === 0) {\n emitFromIndex = Math.min(emitFromIndex ?? i, i);\n continue;\n }\n const firstAlternative = ev.alternatives[0];\n if (\n firstAlternative.startTime === firstAlternative.endTime &&\n firstAlternative.startTime === 0\n ) {\n this.transcriptBuffer = [];\n this.ignoreUserTranscriptUntil = undefined;\n return;\n }\n\n if (this.#alternativeEndsBeforeIgnoreWindow(firstAlternative)) {\n emitFromIndex = null;\n } else {\n emitFromIndex = Math.min(emitFromIndex ?? i, i);\n shouldFlush = true;\n break;\n }\n }\n\n const eventsToEmit =\n emitFromIndex !== null && shouldFlush ? this.transcriptBuffer.slice(emitFromIndex) : [];\n\n this.transcriptBuffer = [];\n this.ignoreUserTranscriptUntil = undefined;\n\n for (const event of eventsToEmit) {\n this.logger.trace(\n {\n event: event.type,\n },\n 're-emitting held user transcript',\n );\n this.onSTTEvent(event);\n }\n }\n\n #alternativeEndsBeforeIgnoreWindow(\n alternative: NonNullable<SpeechEvent['alternatives']>[number],\n ): boolean {\n if (\n this.ignoreUserTranscriptUntil === undefined ||\n !this._inputStartedAt ||\n alternative.startTime <= 0\n ) {\n return false;\n }\n\n // `SpeechData.startTime` is in seconds relative to audio start, while `inputStartedAt` and\n // `ignoreUserTranscriptUntil` are epoch milliseconds.\n return alternative.startTime * 1000 + this._inputStartedAt < this.ignoreUserTranscriptUntil;\n }\n\n private shouldHoldSttEvent(ev: SpeechEvent): boolean {\n if (!this.isInterruptionEnabled) {\n return false;\n }\n if (this.isAgentSpeaking) {\n return true;\n }\n\n // reset when the user starts speaking after the agent speech\n if (ev.type === SpeechEventType.START_OF_SPEECH) {\n this.ignoreUserTranscriptUntil = undefined;\n this.transcriptBuffer = [];\n return false;\n }\n\n if (this.ignoreUserTranscriptUntil === undefined) {\n return false;\n }\n // sentinel events are always held until we have something concrete to release them\n if (!ev.alternatives || ev.alternatives.length === 0) {\n return true;\n }\n\n const alternative = ev.alternatives[0];\n\n if (\n alternative.startTime !== alternative.endTime &&\n this.#alternativeEndsBeforeIgnoreWindow(alternative)\n ) {\n return true;\n }\n return false;\n }\n\n private async trySendInterruptionSentinel(\n frame: AudioFrame | InterruptionSentinel,\n ): Promise<boolean> {\n if (\n this.isInterruptionEnabled &&\n this.interruptionStreamChannel &&\n !this.interruptionStreamChannel.closed\n ) {\n try {\n await this.interruptionStreamChannel.write(frame);\n return true;\n } catch (e: unknown) {\n this.logger.warn(\n `could not forward interruption sentinel: ${e instanceof Error ? e.message : String(e)}`,\n );\n }\n }\n return false;\n }\n\n private ensureUserTurnSpan(startTime?: number): Span {\n if (this.userTurnSpan && this.userTurnSpan.isRecording()) {\n return this.userTurnSpan;\n }\n\n this.userTurnSpan = tracer.startSpan({\n name: 'user_turn',\n context: this.rootSpanContext,\n startTime,\n });\n\n const participant = this.getLinkedParticipant?.();\n if (participant) {\n setParticipantSpanAttributes(this.userTurnSpan, participant);\n }\n\n if (this.sttModel) {\n this.userTurnSpan.setAttribute(traceTypes.ATTR_GEN_AI_REQUEST_MODEL, this.sttModel);\n }\n if (this.sttProvider) {\n this.userTurnSpan.setAttribute(traceTypes.ATTR_GEN_AI_PROVIDER_NAME, this.sttProvider);\n }\n\n return this.userTurnSpan;\n }\n\n private userTurnContext(span: Span): Context {\n const base = this.rootSpanContext ?? ROOT_CONTEXT;\n return trace.setSpan(base, span);\n }\n\n private async onSTTEvent(ev: SpeechEvent) {\n if (\n this.turnDetectionMode === 'manual' &&\n this.userTurnCommitted &&\n (this.bounceEOUTask === undefined ||\n this.bounceEOUTask.done ||\n ev.type == SpeechEventType.INTERIM_TRANSCRIPT)\n ) {\n // ignore stt event if user turn already committed and EOU task is done\n // or it's an interim transcript\n this.logger.debug(\n {\n userTurnCommitted: this.userTurnCommitted,\n eouTaskDone: this.bounceEOUTask?.done,\n evType: ev.type,\n turnDetectionMode: this.turnDetectionMode,\n },\n 'ignoring stt event',\n );\n return;\n }\n\n // handle interruption detection\n // - hold the event until the ignore_user_transcript_until expires\n // - release only relevant events\n // - allow RECOGNITION_USAGE to pass through immediately\n\n if (ev.type !== SpeechEventType.RECOGNITION_USAGE && this.isInterruptionEnabled) {\n if (this.shouldHoldSttEvent(ev)) {\n this.logger.trace(\n { event: ev.type, ignoreUserTranscriptUntil: this.ignoreUserTranscriptUntil },\n 'holding STT event until ignore_user_transcript_until expires',\n );\n this.transcriptBuffer.push(ev);\n return;\n } else {\n await this.flushHeldTranscripts();\n // no return here to allow the new event to be processed normally\n }\n }\n\n switch (ev.type) {\n case SpeechEventType.FINAL_TRANSCRIPT:\n const transcript = ev.alternatives?.[0]?.text;\n const confidence = ev.alternatives?.[0]?.confidence ?? 0;\n this.lastLanguage = ev.alternatives?.[0]?.language;\n\n if (!transcript) {\n // stt final transcript received but no transcript\n return;\n }\n\n this.hooks.onFinalTranscript(ev);\n\n this.logger.debug(\n {\n user_transcript: transcript,\n language: this.lastLanguage,\n },\n 'received user transcript',\n );\n\n this.lastFinalTranscriptTime = Date.now();\n this.audioTranscript += ` ${transcript}`;\n this.audioTranscript = this.audioTranscript.trimStart();\n this.finalTranscriptConfidence.push(confidence);\n const transcriptChanged = this.audioTranscript !== this.audioPreflightTranscript;\n this.audioInterimTranscript = '';\n this.audioPreflightTranscript = '';\n\n if (!this.vad || this.lastSpeakingTime === undefined) {\n // vad disabled, use stt timestamp\n // TODO: this would screw up transcription latency metrics\n // but we'll live with it for now.\n // the correct way is to ensure STT fires SpeechEventType.END_OF_SPEECH\n // and using that timestamp for lastSpeakingTime\n this.lastSpeakingTime = Date.now();\n }\n\n if (this.vadBaseTurnDetection || this.userTurnCommitted) {\n if (transcriptChanged) {\n this.logger.debug(\n { transcript: this.audioTranscript },\n 'triggering preemptive generation (FINAL_TRANSCRIPT)',\n );\n this.hooks.onPreemptiveGeneration({\n newTranscript: this.audioTranscript,\n transcriptConfidence:\n this.finalTranscriptConfidence.length > 0\n ? this.finalTranscriptConfidence.reduce((a, b) => a + b, 0) /\n this.finalTranscriptConfidence.length\n : 0,\n });\n }\n\n if (!this.speaking) {\n const chatCtx = this.hooks.retrieveChatCtx();\n this.logger.debug('running EOU detection on stt FINAL_TRANSCRIPT');\n this.runEOUDetection(chatCtx);\n }\n }\n break;\n case SpeechEventType.PREFLIGHT_TRANSCRIPT:\n this.hooks.onInterimTranscript(ev);\n const preflightTranscript = ev.alternatives?.[0]?.text ?? '';\n const preflightConfidence = ev.alternatives?.[0]?.confidence ?? 0;\n const preflightLanguage = ev.alternatives?.[0]?.language;\n\n const MIN_LANGUAGE_DETECTION_LENGTH = 5;\n if (\n !this.lastLanguage ||\n (preflightLanguage && preflightTranscript.length > MIN_LANGUAGE_DETECTION_LENGTH)\n ) {\n this.lastLanguage = preflightLanguage;\n }\n\n if (!preflightTranscript) {\n return;\n }\n\n this.logger.debug(\n {\n user_transcript: preflightTranscript,\n language: this.lastLanguage,\n },\n 'received user preflight transcript',\n );\n\n // still need to increment it as it's used for turn detection,\n this.lastFinalTranscriptTime = Date.now();\n // preflight transcript includes all pre-committed transcripts (including final transcript from the previous STT run)\n this.audioPreflightTranscript =\n `${this.audioTranscript} ${preflightTranscript}`.trimStart();\n this.audioInterimTranscript = preflightTranscript;\n\n if (!this.vad || this.lastSpeakingTime === undefined) {\n // vad disabled, use stt timestamp\n this.lastSpeakingTime = Date.now();\n }\n\n if (this.turnDetectionMode !== 'manual' || this.userTurnCommitted) {\n const confidenceVals = [...this.finalTranscriptConfidence, preflightConfidence];\n this.logger.debug(\n {\n transcript:\n this.audioPreflightTranscript.length > 100\n ? this.audioPreflightTranscript.slice(0, 100) + '...'\n : this.audioPreflightTranscript,\n },\n 'triggering preemptive generation (PREFLIGHT_TRANSCRIPT)',\n );\n this.hooks.onPreemptiveGeneration({\n newTranscript: this.audioPreflightTranscript,\n transcriptConfidence:\n confidenceVals.length > 0\n ? confidenceVals.reduce((a, b) => a + b, 0) / confidenceVals.length\n : 0,\n });\n }\n break;\n case SpeechEventType.INTERIM_TRANSCRIPT:\n this.logger.debug({ transcript: ev.alternatives?.[0]?.text }, 'interim transcript');\n this.hooks.onInterimTranscript(ev);\n this.audioInterimTranscript = ev.alternatives?.[0]?.text ?? '';\n break;\n case SpeechEventType.START_OF_SPEECH:\n if (this.turnDetectionMode !== 'stt') break;\n {\n const span = this.ensureUserTurnSpan(Date.now());\n const ctx = this.userTurnContext(span);\n otelContext.with(ctx, () => {\n this.hooks.onStartOfSpeech({\n type: VADEventType.START_OF_SPEECH,\n samplesIndex: 0,\n timestamp: Date.now(),\n speechDuration: 0,\n silenceDuration: 0,\n frames: [],\n probability: 0,\n inferenceDuration: 0,\n speaking: true,\n rawAccumulatedSilence: 0,\n rawAccumulatedSpeech: 0,\n });\n });\n }\n this.speaking = true;\n this.lastSpeakingTime = Date.now();\n\n this.bounceEOUTask?.cancel();\n break;\n case SpeechEventType.END_OF_SPEECH:\n if (this.turnDetectionMode !== 'stt') break;\n {\n const span = this.ensureUserTurnSpan();\n const ctx = this.userTurnContext(span);\n otelContext.with(ctx, () => {\n this.hooks.onEndOfSpeech({\n type: VADEventType.END_OF_SPEECH,\n samplesIndex: 0,\n timestamp: Date.now(),\n speechDuration: 0,\n silenceDuration: 0,\n frames: [],\n probability: 0,\n inferenceDuration: 0,\n speaking: false,\n rawAccumulatedSilence: 0,\n rawAccumulatedSpeech: 0,\n });\n });\n }\n this.speaking = false;\n this.userTurnCommitted = true;\n this.lastSpeakingTime = Date.now();\n\n if (!this.speaking) {\n const chatCtx = this.hooks.retrieveChatCtx();\n this.logger.debug('running EOU detection on stt END_OF_SPEECH');\n this.runEOUDetection(chatCtx);\n }\n }\n }\n\n private onOverlapSpeechEvent(ev: OverlappingSpeechEvent) {\n if (ev.isInterruption) {\n this.hooks.onInterruption(ev);\n }\n }\n\n private runEOUDetection(chatCtx: ChatContext) {\n this.logger.debug(\n {\n stt: this.stt,\n audioTranscript: this.audioTranscript,\n turnDetectionMode: this.turnDetectionMode,\n },\n 'running EOU detection',\n );\n\n if (this.stt && !this.audioTranscript && this.turnDetectionMode !== 'manual') {\n // stt enabled but no transcript yet\n this.logger.debug('skipping EOU detection');\n return;\n }\n\n chatCtx = chatCtx.copy();\n chatCtx.addMessage({ role: 'user', content: this.audioTranscript });\n\n const turnDetector =\n // disable EOU model if manual turn detection enabled\n this.audioTranscript && this.turnDetectionMode !== 'manual' ? this.turnDetector : undefined;\n\n const bounceEOUTask =\n (\n lastSpeakingTime: number | undefined,\n lastFinalTranscriptTime: number,\n speechStartTime: number | undefined,\n ) =>\n async (controller: AbortController) => {\n let endpointingDelay = this.minEndpointingDelay;\n\n const userTurnSpan = this.ensureUserTurnSpan();\n const userTurnCtx = this.userTurnContext(userTurnSpan);\n\n if (turnDetector) {\n await tracer.startActiveSpan(\n async (span) => {\n this.logger.debug('Running turn detector model');\n\n let endOfTurnProbability = 0.0;\n let unlikelyThreshold: number | undefined;\n\n if (!(await turnDetector.supportsLanguage(this.lastLanguage))) {\n this.logger.debug(`Turn detector does not support language ${this.lastLanguage}`);\n } else {\n try {\n endOfTurnProbability = await turnDetector.predictEndOfTurn(chatCtx);\n unlikelyThreshold = await turnDetector.unlikelyThreshold(this.lastLanguage);\n\n this.logger.debug(\n { endOfTurnProbability, unlikelyThreshold, language: this.lastLanguage },\n 'end of turn probability',\n );\n\n if (unlikelyThreshold && endOfTurnProbability < unlikelyThreshold) {\n endpointingDelay = this.maxEndpointingDelay;\n }\n } catch (error) {\n this.logger.error(error, 'Error predicting end of turn');\n }\n }\n\n span.setAttribute(\n traceTypes.ATTR_CHAT_CTX,\n JSON.stringify(chatCtx.toJSON({ excludeTimestamp: false })),\n );\n span.setAttribute(traceTypes.ATTR_EOU_PROBABILITY, endOfTurnProbability);\n span.setAttribute(traceTypes.ATTR_EOU_UNLIKELY_THRESHOLD, unlikelyThreshold ?? 0);\n span.setAttribute(traceTypes.ATTR_EOU_DELAY, endpointingDelay);\n span.setAttribute(traceTypes.ATTR_EOU_LANGUAGE, this.lastLanguage ?? '');\n },\n {\n name: 'eou_detection',\n context: userTurnCtx,\n },\n );\n }\n\n let extraSleep = endpointingDelay;\n if (lastSpeakingTime !== undefined) {\n extraSleep += lastSpeakingTime - Date.now();\n }\n\n if (extraSleep > 0) {\n // add delay to see if there's a potential upcoming EOU task that cancels this one\n await delay(Math.max(extraSleep, 0), { signal: controller.signal });\n }\n\n this.logger.debug({ transcript: this.audioTranscript }, 'end of user turn');\n\n const confidenceAvg =\n this.finalTranscriptConfidence.length > 0\n ? this.finalTranscriptConfidence.reduce((a, b) => a + b, 0) /\n this.finalTranscriptConfidence.length\n : 0;\n\n let startedSpeakingAt: number | undefined;\n let stoppedSpeakingAt: number | undefined;\n let transcriptionDelay: number | undefined;\n let endOfUtteranceDelay: number | undefined;\n\n // sometimes, we can't calculate the metrics because VAD was unreliable.\n // in this case, we just ignore the calculation, it's better than providing likely wrong values\n if (\n lastFinalTranscriptTime !== 0 &&\n lastSpeakingTime !== undefined &&\n speechStartTime !== undefined\n ) {\n startedSpeakingAt = speechStartTime;\n stoppedSpeakingAt = lastSpeakingTime;\n transcriptionDelay = Math.max(lastFinalTranscriptTime - lastSpeakingTime, 0);\n endOfUtteranceDelay = Date.now() - lastSpeakingTime;\n }\n\n const committed = await this.hooks.onEndOfTurn({\n newTranscript: this.audioTranscript,\n transcriptConfidence: confidenceAvg,\n transcriptionDelay: transcriptionDelay ?? 0,\n endOfUtteranceDelay: endOfUtteranceDelay ?? 0,\n startedSpeakingAt,\n stoppedSpeakingAt,\n });\n\n if (committed) {\n this._endUserTurnSpan({\n transcript: this.audioTranscript,\n confidence: confidenceAvg,\n transcriptionDelay: transcriptionDelay ?? 0,\n endOfUtteranceDelay: endOfUtteranceDelay ?? 0,\n });\n\n // clear the transcript if the user turn was committed\n this.audioTranscript = '';\n this.finalTranscriptConfidence = [];\n this.lastSpeakingTime = undefined;\n this.lastFinalTranscriptTime = 0;\n this.speechStartTime = undefined;\n }\n\n this.userTurnCommitted = false;\n };\n\n // cancel any existing EOU task\n this.bounceEOUTask?.cancel();\n // copy the values before awaiting (the values can change)\n this.bounceEOUTask = Task.from(\n bounceEOUTask(this.lastSpeakingTime, this.lastFinalTranscriptTime, this.speechStartTime),\n );\n\n this.bounceEOUTask.result\n .then(() => {\n this.logger.debug('EOU detection task completed');\n })\n .catch((err: unknown) => {\n if (err instanceof Error && err.message.includes('This operation was aborted')) {\n // ignore aborted errors\n return;\n }\n this.logger.error(err, 'Error in EOU detection task:');\n });\n }\n\n private async createSttTask(stt: STTNode | undefined, signal: AbortSignal) {\n if (!stt) return;\n\n this.logger.debug('createSttTask: create stt stream from stt node');\n\n const sttStream = await stt(this.sttInputStream, {});\n\n if (signal.aborted || sttStream === null) return;\n\n if (sttStream instanceof ReadableStream) {\n const reader = sttStream.getReader();\n\n signal.addEventListener('abort', async () => {\n try {\n reader.releaseLock();\n await sttStream?.cancel();\n } catch (e) {\n this.logger.debug('createSttTask: error during abort handler:', e);\n }\n });\n\n try {\n while (true) {\n if (signal.aborted) break;\n\n const { done, value: ev } = await reader.read();\n if (done) break;\n\n if (typeof ev === 'string') {\n throw new Error('STT node must yield SpeechEvent');\n } else {\n await this.onSTTEvent(ev);\n }\n }\n } catch (e) {\n if (isStreamReaderReleaseError(e)) {\n return;\n }\n this.logger.error({ error: e }, 'createSttTask: error reading sttStream');\n } finally {\n reader.releaseLock();\n try {\n await sttStream.cancel();\n } catch (e) {\n this.logger.debug(\n 'createSttTask: error cancelling sttStream (may already be cancelled):',\n e,\n );\n }\n }\n }\n }\n\n private async createVadTask(vad: VAD | undefined, signal: AbortSignal) {\n if (!vad) return;\n\n const vadStream = vad.stream();\n vadStream.updateInputStream(this.vadInputStream);\n\n const abortHandler = () => {\n vadStream.detachInputStream();\n vadStream.close();\n signal.removeEventListener('abort', abortHandler);\n };\n signal.addEventListener('abort', abortHandler);\n\n try {\n for await (const ev of vadStream) {\n if (signal.aborted) break;\n\n switch (ev.type) {\n case VADEventType.START_OF_SPEECH:\n this.logger.debug('VAD task: START_OF_SPEECH');\n {\n const startTime = Date.now() - ev.speechDuration;\n const span = this.ensureUserTurnSpan(startTime);\n const ctx = this.userTurnContext(span);\n otelContext.with(ctx, () => this.hooks.onStartOfSpeech(ev));\n }\n this.speaking = true;\n\n // Capture sample rate from the first VAD event if not already set\n if (ev.frames.length > 0 && ev.frames[0]) {\n this.sampleRate = ev.frames[0].sampleRate;\n }\n\n this.bounceEOUTask?.cancel();\n break;\n case VADEventType.INFERENCE_DONE:\n this.hooks.onVADInferenceDone(ev);\n // for metrics, get the \"earliest\" signal of speech as possible\n if (ev.rawAccumulatedSpeech > 0.0) {\n this.lastSpeakingTime = Date.now();\n\n if (this.speechStartTime === undefined) {\n // Backdate speechStartTime to the actual start of accumulated speech.\n // ev.rawAccumulatedSpeech is in ms (VADEvent durations are all ms in TS).\n this.speechStartTime = Date.now() - ev.rawAccumulatedSpeech;\n }\n }\n break;\n case VADEventType.END_OF_SPEECH:\n this.logger.debug('VAD task: END_OF_SPEECH');\n {\n const span = this.ensureUserTurnSpan();\n const ctx = this.userTurnContext(span);\n otelContext.with(ctx, () => this.hooks.onEndOfSpeech(ev));\n }\n\n // when VAD fires END_OF_SPEECH, it already waited for the silence_duration\n this.speaking = false;\n\n if (\n this.vadBaseTurnDetection ||\n (this.turnDetectionMode === 'stt' && this.userTurnCommitted)\n ) {\n const chatCtx = this.hooks.retrieveChatCtx();\n this.runEOUDetection(chatCtx);\n }\n break;\n }\n }\n } catch (e) {\n this.logger.error(e, 'Error in VAD task');\n } finally {\n this.logger.debug('VAD task closed');\n }\n }\n\n private async createInterruptionTask(\n interruptionDetection: AdaptiveInterruptionDetector | undefined,\n signal: AbortSignal,\n ) {\n if (!interruptionDetection || !this.interruptionStreamChannel) return;\n\n const stream = interruptionDetection.createStream();\n const inputReader = this.interruptionStreamChannel.stream().getReader();\n\n const cleanup = async () => {\n try {\n signal.removeEventListener('abort', abortHandler);\n eventReader.releaseLock();\n await stream.close();\n } catch (e) {\n this.logger.debug('createInterruptionTask: error during abort handler:', e);\n }\n };\n\n // Forward input frames/sentinels to the interruption stream\n const forwardTask = (async () => {\n try {\n const abortPromise = waitForAbort(signal);\n while (!signal.aborted) {\n const res = await Promise.race([inputReader.read(), abortPromise]);\n if (!res) break;\n const { value, done } = res;\n if (done) break;\n // Backdate to the actual start of the audio frame, not when it was received.\n if (value instanceof AudioFrame) {\n const frameDurationMs = (value.samplesPerChannel / value.sampleRate) * 1000;\n this._inputStartedAt ??= Date.now() - frameDurationMs;\n } else {\n this._inputStartedAt ??= Date.now();\n }\n await stream.pushFrame(value);\n }\n } finally {\n inputReader.releaseLock();\n }\n })();\n\n // Read output events from the interruption stream\n const eventReader = stream.stream().getReader();\n const abortHandler = async () => {\n await cleanup();\n };\n signal.addEventListener('abort', abortHandler);\n\n try {\n const abortPromise = waitForAbort(signal);\n\n while (!signal.aborted) {\n const res = await Promise.race([eventReader.read(), abortPromise]);\n if (!res) break;\n const { done, value: ev } = res;\n if (done) break;\n this.onOverlapSpeechEvent(ev);\n }\n } catch (e) {\n if (!signal.aborted) {\n const cause = e instanceof Error ? e : new Error(String(e));\n interruptionDetection.emitError(\n new InterruptionDetectionError(\n cause.message,\n Date.now(),\n interruptionDetection.label,\n false,\n ),\n );\n this.logger.error(e, 'Error in interruption task');\n }\n } finally {\n await cleanup();\n await forwardTask;\n this.logger.debug('Interruption task closed');\n }\n }\n\n setInputAudioStream(audioStream: ReadableStream<AudioFrame>) {\n this.deferredInputStream.setSource(audioStream);\n }\n\n detachInputAudioStream() {\n this.deferredInputStream.detachSource();\n }\n\n clearUserTurn() {\n this.audioTranscript = '';\n this.audioInterimTranscript = '';\n this.audioPreflightTranscript = '';\n this.finalTranscriptConfidence = [];\n this.userTurnCommitted = false;\n\n this.sttTask?.cancelAndWait().finally(() => {\n this.sttTask = Task.from(({ signal }) => this.createSttTask(this.stt, signal));\n this.sttTask.result.catch((err) => {\n this.logger.error(`Error running STT task: ${err}`);\n });\n });\n }\n\n commitUserTurn(audioDetached: boolean) {\n const commitUserTurnTask =\n (delayDuration: number = 500) =>\n async (controller: AbortController) => {\n if (Date.now() - this.lastFinalTranscriptTime > delayDuration) {\n // flush the stt by pushing silence\n if (audioDetached && this.sampleRate !== undefined) {\n const numSamples = Math.floor(this.sampleRate * 0.5);\n const silence = new Int16Array(numSamples * 2);\n const silenceFrame = new AudioFrame(silence, this.sampleRate, 1, numSamples);\n this.silenceAudioWriter.write(silenceFrame);\n }\n\n // wait for the final transcript to be available\n await delay(delayDuration, { signal: controller.signal });\n }\n\n if (this.audioInterimTranscript) {\n // append interim transcript in case the final transcript is not ready\n this.audioTranscript = `${this.audioTranscript} ${this.audioInterimTranscript}`.trim();\n }\n this.audioInterimTranscript = '';\n\n const chatCtx = this.hooks.retrieveChatCtx();\n this.logger.debug('running EOU detection on commitUserTurn');\n this.runEOUDetection(chatCtx);\n this.userTurnCommitted = true;\n };\n\n // cancel any existing commit user turn task\n this.commitUserTurnTask?.cancel();\n this.commitUserTurnTask = Task.from(commitUserTurnTask());\n\n this.commitUserTurnTask.result\n .then(() => {\n this.logger.debug('User turn committed');\n })\n .catch((err: unknown) => {\n if (err instanceof Error && err.name === 'AbortError') {\n this.logger.debug('User turn commit task cancelled');\n return;\n }\n this.logger.error(err, 'Error in user turn commit task:');\n });\n }\n\n async close() {\n this.detachInputAudioStream();\n this.silenceAudioWriter.releaseLock();\n await this.commitUserTurnTask?.cancelAndWait();\n await this.sttTask?.cancelAndWait();\n await this.vadTask?.cancelAndWait();\n await this.bounceEOUTask?.cancelAndWait();\n await this.interruptionTask?.cancelAndWait();\n await this.interruptionStreamChannel?.close();\n }\n\n private _endUserTurnSpan({\n transcript,\n confidence,\n transcriptionDelay,\n endOfUtteranceDelay,\n }: {\n transcript: string;\n confidence: number;\n transcriptionDelay: number;\n endOfUtteranceDelay: number;\n }): void {\n if (this.userTurnSpan) {\n this.userTurnSpan.setAttributes({\n [traceTypes.ATTR_USER_TRANSCRIPT]: transcript,\n [traceTypes.ATTR_TRANSCRIPT_CONFIDENCE]: confidence,\n [traceTypes.ATTR_TRANSCRIPTION_DELAY]: transcriptionDelay,\n [traceTypes.ATTR_END_OF_TURN_DELAY]: endOfUtteranceDelay,\n });\n this.userTurnSpan.end();\n this.userTurnSpan = undefined;\n }\n }\n\n private get vadBaseTurnDetection() {\n if (typeof this.turnDetectionMode === 'object') {\n return false;\n }\n\n if (this.turnDetectionMode === undefined || this.turnDetectionMode === 'vad') {\n return true;\n }\n\n return false;\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/voice/audio_recognition.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { ParticipantKind } from '@livekit/rtc-node';\nimport { AudioFrame } from '@livekit/rtc-node';\nimport {\n type Context,\n ROOT_CONTEXT,\n type Span,\n context as otelContext,\n trace,\n} from '@opentelemetry/api';\nimport type { WritableStreamDefaultWriter } from 'node:stream/web';\nimport { ReadableStream } from 'node:stream/web';\nimport { isAPIError } from '../_exceptions.js';\nimport { apiConnectDefaults, intervalForRetry } from '../inference/interruption/defaults.js';\nimport { InterruptionDetectionError } from '../inference/interruption/errors.js';\nimport type { AdaptiveInterruptionDetector } from '../inference/interruption/interruption_detector.js';\nimport { InterruptionStreamSentinel } from '../inference/interruption/interruption_stream.js';\nimport {\n type InterruptionSentinel,\n type OverlappingSpeechEvent,\n} from '../inference/interruption/types.js';\nimport type { LanguageCode } from '../language.js';\nimport { type ChatContext } from '../llm/chat_context.js';\nimport { log } from '../log.js';\nimport { DeferredReadableStream, isStreamReaderReleaseError } from '../stream/deferred_stream.js';\nimport { IdentityTransform } from '../stream/identity_transform.js';\nimport { mergeReadableStreams } from '../stream/merge_readable_streams.js';\nimport { type StreamChannel, createStreamChannel } from '../stream/stream_channel.js';\nimport { type SpeechEvent, SpeechEventType } from '../stt/stt.js';\nimport { traceTypes, tracer } from '../telemetry/index.js';\nimport { Task, delay, waitForAbort } from '../utils.js';\nimport { type VAD, type VADEvent, VADEventType } from '../vad.js';\nimport type { TurnDetectionMode } from './agent_session.js';\nimport type { STTNode } from './io.js';\nimport { setParticipantSpanAttributes } from './utils.js';\n\nexport interface EndOfTurnInfo {\n /** The new transcript text from the user's speech. */\n newTranscript: string;\n /** Confidence score of the transcript (0-1). */\n transcriptConfidence: number;\n /** Delay from speech stop to final transcription in milliseconds. */\n transcriptionDelay: number;\n /** Delay from speech stop to end of utterance detection in milliseconds. */\n endOfUtteranceDelay: number;\n /** Timestamp when user started speaking (milliseconds since epoch). */\n startedSpeakingAt: number | undefined;\n /** Timestamp when user stopped speaking (milliseconds since epoch). */\n stoppedSpeakingAt: number | undefined;\n}\n\nexport interface PreemptiveGenerationInfo {\n newTranscript: string;\n transcriptConfidence: number;\n}\n\nexport interface RecognitionHooks {\n onInterruption: (ev: OverlappingSpeechEvent) => void;\n onStartOfSpeech: (ev: VADEvent) => void;\n onVADInferenceDone: (ev: VADEvent) => void;\n onEndOfSpeech: (ev: VADEvent) => void;\n onInterimTranscript: (ev: SpeechEvent) => void;\n onFinalTranscript: (ev: SpeechEvent) => void;\n onEndOfTurn: (info: EndOfTurnInfo) => Promise<boolean>;\n onPreemptiveGeneration: (info: PreemptiveGenerationInfo) => void;\n\n retrieveChatCtx: () => ChatContext;\n}\n\nexport interface _TurnDetector {\n /** The model name used by this turn detector. */\n readonly model: string;\n /** The provider name for this turn detector. */\n readonly provider: string;\n unlikelyThreshold: (language?: LanguageCode) => Promise<number | undefined>;\n supportsLanguage: (language?: LanguageCode) => Promise<boolean>;\n predictEndOfTurn(chatCtx: ChatContext, timeout?: number): Promise<number>;\n}\n\nexport interface AudioRecognitionOptions {\n /** Hooks for recognition events. */\n recognitionHooks: RecognitionHooks;\n /** Speech-to-text node. */\n stt?: STTNode;\n /** Voice activity detection. */\n vad?: VAD;\n /** Turn detector for end-of-turn prediction. */\n turnDetector?: _TurnDetector;\n /** Turn detection mode. */\n turnDetectionMode?: TurnDetectionMode;\n interruptionDetection?: AdaptiveInterruptionDetector;\n /** Minimum endpointing delay in milliseconds. */\n minEndpointingDelay: number;\n /** Maximum endpointing delay in milliseconds. */\n maxEndpointingDelay: number;\n /** Root span context for tracing. */\n rootSpanContext?: Context;\n /** STT model name for tracing */\n sttModel?: string;\n /** STT provider name for tracing */\n sttProvider?: string;\n /** Getter for linked participant for span attribution */\n getLinkedParticipant?: () => ParticipantLike | undefined;\n}\n\n/**\n * Minimal participant shape for span attribution.\n * Compatible with both `LocalParticipant` and `RemoteParticipant` from `@livekit/rtc-node`.\n */\nexport interface ParticipantLike {\n sid: string | undefined;\n identity: string;\n kind: ParticipantKind;\n}\n\n// TODO add ability to update stt/vad/interruption-detection\nexport class AudioRecognition {\n private hooks: RecognitionHooks;\n private stt?: STTNode;\n private vad?: VAD;\n private turnDetector?: _TurnDetector;\n private turnDetectionMode?: TurnDetectionMode;\n private minEndpointingDelay: number;\n private maxEndpointingDelay: number;\n private lastLanguage?: LanguageCode;\n private rootSpanContext?: Context;\n private sttModel?: string;\n private sttProvider?: string;\n private getLinkedParticipant?: () => ParticipantLike | undefined;\n\n private deferredInputStream: DeferredReadableStream<AudioFrame>;\n private logger = log();\n private lastFinalTranscriptTime = 0;\n private audioTranscript = '';\n private audioInterimTranscript = '';\n private audioPreflightTranscript = '';\n private finalTranscriptConfidence: number[] = [];\n private lastSpeakingTime: number | undefined;\n private speechStartTime: number | undefined;\n private userTurnCommitted = false;\n private speaking = false;\n private sampleRate?: number;\n\n private userTurnSpan?: Span;\n\n private vadInputStream: ReadableStream<AudioFrame>;\n private sttInputStream: ReadableStream<AudioFrame>;\n private silenceAudioTransform = new IdentityTransform<AudioFrame>();\n private silenceAudioWriter: WritableStreamDefaultWriter<AudioFrame>;\n\n // all cancellable tasks\n private bounceEOUTask?: Task<void>;\n private commitUserTurnTask?: Task<void>;\n private vadTask?: Task<void>;\n private sttTask?: Task<void>;\n private interruptionTask?: Task<void>;\n\n // interruption detection\n private interruptionDetection?: AdaptiveInterruptionDetector;\n private _inputStartedAt?: number;\n private ignoreUserTranscriptUntil?: number;\n private transcriptBuffer: SpeechEvent[];\n private isInterruptionEnabled: boolean;\n private isAgentSpeaking: boolean;\n private interruptionStreamChannel?: StreamChannel<InterruptionSentinel | AudioFrame>;\n\n constructor(opts: AudioRecognitionOptions) {\n this.hooks = opts.recognitionHooks;\n this.stt = opts.stt;\n this.vad = opts.vad;\n this.turnDetector = opts.turnDetector;\n this.turnDetectionMode = opts.turnDetectionMode;\n this.minEndpointingDelay = opts.minEndpointingDelay;\n this.maxEndpointingDelay = opts.maxEndpointingDelay;\n this.lastLanguage = undefined;\n this.rootSpanContext = opts.rootSpanContext;\n this.sttModel = opts.sttModel;\n this.sttProvider = opts.sttProvider;\n this.getLinkedParticipant = opts.getLinkedParticipant;\n\n this.deferredInputStream = new DeferredReadableStream<AudioFrame>();\n this.interruptionDetection = opts.interruptionDetection;\n this.transcriptBuffer = [];\n this.isInterruptionEnabled = !!(opts.interruptionDetection && opts.vad);\n this.isAgentSpeaking = false;\n\n if (opts.interruptionDetection) {\n const [vadInputStream, teedInput] = this.deferredInputStream.stream.tee();\n const [inputStream, sttInputStream] = teedInput.tee();\n this.vadInputStream = vadInputStream;\n this.sttInputStream = mergeReadableStreams(\n sttInputStream,\n this.silenceAudioTransform.readable,\n );\n this.interruptionStreamChannel = createStreamChannel();\n this.interruptionStreamChannel.addStreamInput(inputStream);\n } else {\n const [vadInputStream, sttInputStream] = this.deferredInputStream.stream.tee();\n this.vadInputStream = vadInputStream;\n this.sttInputStream = mergeReadableStreams(\n sttInputStream,\n this.silenceAudioTransform.readable,\n );\n }\n this.silenceAudioWriter = this.silenceAudioTransform.writable.getWriter();\n }\n\n /**\n * Current transcript of the user's speech, including interim transcript if available.\n */\n get currentTranscript(): string {\n if (this.audioInterimTranscript) {\n return `${this.audioTranscript} ${this.audioInterimTranscript}`.trim();\n }\n return this.audioTranscript;\n }\n\n /** @internal */\n get inputStartedAt() {\n return this._inputStartedAt;\n }\n\n /** @internal */\n updateOptions(options: { turnDetection: TurnDetectionMode | undefined }): void {\n this.turnDetectionMode = options.turnDetection;\n }\n\n async start() {\n this.vadTask = Task.from(({ signal }) => this.createVadTask(this.vad, signal));\n this.vadTask.result.catch((err) => {\n this.logger.error(`Error running VAD task: ${err}`);\n });\n\n this.sttTask = Task.from(({ signal }) => this.createSttTask(this.stt, signal));\n this.sttTask.result.catch((err) => {\n this.logger.error(`Error running STT task: ${err}`);\n });\n\n this.interruptionTask = Task.from(({ signal }) =>\n this.createInterruptionTask(this.interruptionDetection, signal),\n );\n this.interruptionTask.result.catch((err) => {\n this.logger.error(`Error running interruption task: ${err}`);\n });\n }\n\n async stop() {\n await this.sttTask?.cancelAndWait();\n await this.vadTask?.cancelAndWait();\n await this.interruptionTask?.cancelAndWait();\n }\n\n async disableInterruptionDetection(): Promise<void> {\n this.isInterruptionEnabled = false;\n this.interruptionDetection = undefined;\n await this.interruptionTask?.cancelAndWait();\n this.interruptionTask = undefined;\n await this.interruptionStreamChannel?.close();\n this.interruptionStreamChannel = undefined;\n }\n\n async onStartOfAgentSpeech() {\n this.isAgentSpeaking = true;\n return this.trySendInterruptionSentinel(InterruptionStreamSentinel.agentSpeechStarted());\n }\n\n async onEndOfAgentSpeech(ignoreUserTranscriptUntil: number) {\n if (!this.isInterruptionEnabled) {\n this.isAgentSpeaking = false;\n return;\n }\n\n const inputOpen = await this.trySendInterruptionSentinel(\n InterruptionStreamSentinel.agentSpeechEnded(),\n );\n if (!inputOpen) {\n this.isAgentSpeaking = false;\n return;\n }\n\n if (this.isAgentSpeaking) {\n if (this.ignoreUserTranscriptUntil === undefined) {\n this.onEndOfOverlapSpeech(Date.now());\n }\n this.ignoreUserTranscriptUntil = this.ignoreUserTranscriptUntil\n ? Math.min(ignoreUserTranscriptUntil, this.ignoreUserTranscriptUntil)\n : ignoreUserTranscriptUntil;\n\n // flush held transcripts if possible\n await this.flushHeldTranscripts();\n }\n this.isAgentSpeaking = false;\n }\n\n /** Start interruption inference when agent is speaking and overlap speech starts. */\n async onStartOfOverlapSpeech(speechDuration: number, startedAt: number, userSpeakingSpan?: Span) {\n if (this.isAgentSpeaking) {\n this.trySendInterruptionSentinel(\n InterruptionStreamSentinel.overlapSpeechStarted(\n speechDuration,\n startedAt,\n userSpeakingSpan,\n ),\n );\n }\n }\n\n /** End interruption inference when overlap speech ends. */\n async onEndOfOverlapSpeech(endedAt: number, userSpeakingSpan?: Span) {\n if (!this.isInterruptionEnabled) {\n return;\n }\n if (userSpeakingSpan && userSpeakingSpan.isRecording()) {\n userSpeakingSpan.setAttribute(traceTypes.ATTR_IS_INTERRUPTION, 'false');\n }\n\n return this.trySendInterruptionSentinel(InterruptionStreamSentinel.overlapSpeechEnded(endedAt));\n }\n\n /**\n * Flush held transcripts whose *end time* is after the ignoreUserTranscriptUntil timestamp.\n * If the event has no timestamps, we assume it is the same as the next valid event.\n */\n private async flushHeldTranscripts() {\n if (\n !this.isInterruptionEnabled ||\n this.ignoreUserTranscriptUntil === undefined ||\n this.transcriptBuffer.length === 0\n ) {\n return;\n }\n\n if (!this._inputStartedAt) {\n this.transcriptBuffer = [];\n this.ignoreUserTranscriptUntil = undefined;\n return;\n }\n\n let emitFromIndex: number | null = null;\n let shouldFlush = false;\n\n for (let i = 0; i < this.transcriptBuffer.length; i++) {\n const ev = this.transcriptBuffer[i];\n if (!ev || !ev.alternatives || ev.alternatives.length === 0) {\n emitFromIndex = Math.min(emitFromIndex ?? i, i);\n continue;\n }\n const firstAlternative = ev.alternatives[0];\n if (\n firstAlternative.startTime === firstAlternative.endTime &&\n firstAlternative.startTime === 0\n ) {\n this.transcriptBuffer = [];\n this.ignoreUserTranscriptUntil = undefined;\n return;\n }\n\n if (this.#alternativeEndsBeforeIgnoreWindow(firstAlternative)) {\n emitFromIndex = null;\n } else {\n emitFromIndex = Math.min(emitFromIndex ?? i, i);\n shouldFlush = true;\n break;\n }\n }\n\n const eventsToEmit =\n emitFromIndex !== null && shouldFlush ? this.transcriptBuffer.slice(emitFromIndex) : [];\n\n this.transcriptBuffer = [];\n this.ignoreUserTranscriptUntil = undefined;\n\n for (const event of eventsToEmit) {\n this.logger.trace(\n {\n event: event.type,\n },\n 're-emitting held user transcript',\n );\n this.onSTTEvent(event);\n }\n }\n\n #alternativeEndsBeforeIgnoreWindow(\n alternative: NonNullable<SpeechEvent['alternatives']>[number],\n ): boolean {\n if (\n this.ignoreUserTranscriptUntil === undefined ||\n !this._inputStartedAt ||\n alternative.startTime <= 0\n ) {\n return false;\n }\n\n // `SpeechData.startTime` is in seconds relative to audio start, while `inputStartedAt` and\n // `ignoreUserTranscriptUntil` are epoch milliseconds.\n return alternative.startTime * 1000 + this._inputStartedAt < this.ignoreUserTranscriptUntil;\n }\n\n private shouldHoldSttEvent(ev: SpeechEvent): boolean {\n if (!this.isInterruptionEnabled) {\n return false;\n }\n if (this.isAgentSpeaking) {\n return true;\n }\n\n // reset when the user starts speaking after the agent speech\n if (ev.type === SpeechEventType.START_OF_SPEECH) {\n this.ignoreUserTranscriptUntil = undefined;\n this.transcriptBuffer = [];\n return false;\n }\n\n if (this.ignoreUserTranscriptUntil === undefined) {\n return false;\n }\n // sentinel events are always held until we have something concrete to release them\n if (!ev.alternatives || ev.alternatives.length === 0) {\n return true;\n }\n\n const alternative = ev.alternatives[0];\n\n if (\n alternative.startTime !== alternative.endTime &&\n this.#alternativeEndsBeforeIgnoreWindow(alternative)\n ) {\n return true;\n }\n return false;\n }\n\n private async trySendInterruptionSentinel(\n frame: AudioFrame | InterruptionSentinel,\n ): Promise<boolean> {\n if (\n this.isInterruptionEnabled &&\n this.interruptionStreamChannel &&\n !this.interruptionStreamChannel.closed\n ) {\n try {\n await this.interruptionStreamChannel.write(frame);\n return true;\n } catch (e: unknown) {\n this.logger.warn(\n `could not forward interruption sentinel: ${e instanceof Error ? e.message : String(e)}`,\n );\n }\n }\n return false;\n }\n\n private ensureUserTurnSpan(startTime?: number): Span {\n if (this.userTurnSpan && this.userTurnSpan.isRecording()) {\n return this.userTurnSpan;\n }\n\n this.userTurnSpan = tracer.startSpan({\n name: 'user_turn',\n context: this.rootSpanContext,\n startTime,\n });\n\n const participant = this.getLinkedParticipant?.();\n if (participant) {\n setParticipantSpanAttributes(this.userTurnSpan, participant);\n }\n\n if (this.sttModel) {\n this.userTurnSpan.setAttribute(traceTypes.ATTR_GEN_AI_REQUEST_MODEL, this.sttModel);\n }\n if (this.sttProvider) {\n this.userTurnSpan.setAttribute(traceTypes.ATTR_GEN_AI_PROVIDER_NAME, this.sttProvider);\n }\n\n return this.userTurnSpan;\n }\n\n private userTurnContext(span: Span): Context {\n const base = this.rootSpanContext ?? ROOT_CONTEXT;\n return trace.setSpan(base, span);\n }\n\n private async onSTTEvent(ev: SpeechEvent) {\n if (\n this.turnDetectionMode === 'manual' &&\n this.userTurnCommitted &&\n (this.bounceEOUTask === undefined ||\n this.bounceEOUTask.done ||\n ev.type == SpeechEventType.INTERIM_TRANSCRIPT)\n ) {\n // ignore stt event if user turn already committed and EOU task is done\n // or it's an interim transcript\n this.logger.debug(\n {\n userTurnCommitted: this.userTurnCommitted,\n eouTaskDone: this.bounceEOUTask?.done,\n evType: ev.type,\n turnDetectionMode: this.turnDetectionMode,\n },\n 'ignoring stt event',\n );\n return;\n }\n\n // handle interruption detection\n // - hold the event until the ignore_user_transcript_until expires\n // - release only relevant events\n // - allow RECOGNITION_USAGE to pass through immediately\n\n if (ev.type !== SpeechEventType.RECOGNITION_USAGE && this.isInterruptionEnabled) {\n if (this.shouldHoldSttEvent(ev)) {\n this.logger.trace(\n { event: ev.type, ignoreUserTranscriptUntil: this.ignoreUserTranscriptUntil },\n 'holding STT event until ignore_user_transcript_until expires',\n );\n this.transcriptBuffer.push(ev);\n return;\n } else {\n await this.flushHeldTranscripts();\n // no return here to allow the new event to be processed normally\n }\n }\n\n switch (ev.type) {\n case SpeechEventType.FINAL_TRANSCRIPT:\n const transcript = ev.alternatives?.[0]?.text;\n const confidence = ev.alternatives?.[0]?.confidence ?? 0;\n this.lastLanguage = ev.alternatives?.[0]?.language;\n\n if (!transcript) {\n // stt final transcript received but no transcript\n return;\n }\n\n this.hooks.onFinalTranscript(ev);\n\n this.logger.debug(\n {\n user_transcript: transcript,\n language: this.lastLanguage,\n },\n 'received user transcript',\n );\n\n this.lastFinalTranscriptTime = Date.now();\n this.audioTranscript += ` ${transcript}`;\n this.audioTranscript = this.audioTranscript.trimStart();\n this.finalTranscriptConfidence.push(confidence);\n const transcriptChanged = this.audioTranscript !== this.audioPreflightTranscript;\n this.audioInterimTranscript = '';\n this.audioPreflightTranscript = '';\n\n if (!this.vad || this.lastSpeakingTime === undefined) {\n // vad disabled, use stt timestamp\n // TODO: this would screw up transcription latency metrics\n // but we'll live with it for now.\n // the correct way is to ensure STT fires SpeechEventType.END_OF_SPEECH\n // and using that timestamp for lastSpeakingTime\n this.lastSpeakingTime = Date.now();\n }\n\n if (this.vadBaseTurnDetection || this.userTurnCommitted) {\n if (transcriptChanged) {\n this.logger.debug(\n { transcript: this.audioTranscript },\n 'triggering preemptive generation (FINAL_TRANSCRIPT)',\n );\n this.hooks.onPreemptiveGeneration({\n newTranscript: this.audioTranscript,\n transcriptConfidence:\n this.finalTranscriptConfidence.length > 0\n ? this.finalTranscriptConfidence.reduce((a, b) => a + b, 0) /\n this.finalTranscriptConfidence.length\n : 0,\n });\n }\n\n if (!this.speaking) {\n const chatCtx = this.hooks.retrieveChatCtx();\n this.logger.debug('running EOU detection on stt FINAL_TRANSCRIPT');\n this.runEOUDetection(chatCtx);\n }\n }\n break;\n case SpeechEventType.PREFLIGHT_TRANSCRIPT:\n this.hooks.onInterimTranscript(ev);\n const preflightTranscript = ev.alternatives?.[0]?.text ?? '';\n const preflightConfidence = ev.alternatives?.[0]?.confidence ?? 0;\n const preflightLanguage = ev.alternatives?.[0]?.language;\n\n const MIN_LANGUAGE_DETECTION_LENGTH = 5;\n if (\n !this.lastLanguage ||\n (preflightLanguage && preflightTranscript.length > MIN_LANGUAGE_DETECTION_LENGTH)\n ) {\n this.lastLanguage = preflightLanguage;\n }\n\n if (!preflightTranscript) {\n return;\n }\n\n this.logger.debug(\n {\n user_transcript: preflightTranscript,\n language: this.lastLanguage,\n },\n 'received user preflight transcript',\n );\n\n // still need to increment it as it's used for turn detection,\n this.lastFinalTranscriptTime = Date.now();\n // preflight transcript includes all pre-committed transcripts (including final transcript from the previous STT run)\n this.audioPreflightTranscript =\n `${this.audioTranscript} ${preflightTranscript}`.trimStart();\n this.audioInterimTranscript = preflightTranscript;\n\n if (!this.vad || this.lastSpeakingTime === undefined) {\n // vad disabled, use stt timestamp\n this.lastSpeakingTime = Date.now();\n }\n\n if (this.turnDetectionMode !== 'manual' || this.userTurnCommitted) {\n const confidenceVals = [...this.finalTranscriptConfidence, preflightConfidence];\n this.logger.debug(\n {\n transcript:\n this.audioPreflightTranscript.length > 100\n ? this.audioPreflightTranscript.slice(0, 100) + '...'\n : this.audioPreflightTranscript,\n },\n 'triggering preemptive generation (PREFLIGHT_TRANSCRIPT)',\n );\n this.hooks.onPreemptiveGeneration({\n newTranscript: this.audioPreflightTranscript,\n transcriptConfidence:\n confidenceVals.length > 0\n ? confidenceVals.reduce((a, b) => a + b, 0) / confidenceVals.length\n : 0,\n });\n }\n break;\n case SpeechEventType.INTERIM_TRANSCRIPT:\n this.logger.debug({ transcript: ev.alternatives?.[0]?.text }, 'interim transcript');\n this.hooks.onInterimTranscript(ev);\n this.audioInterimTranscript = ev.alternatives?.[0]?.text ?? '';\n break;\n case SpeechEventType.START_OF_SPEECH:\n if (this.turnDetectionMode !== 'stt') break;\n {\n const span = this.ensureUserTurnSpan(Date.now());\n const ctx = this.userTurnContext(span);\n otelContext.with(ctx, () => {\n this.hooks.onStartOfSpeech({\n type: VADEventType.START_OF_SPEECH,\n samplesIndex: 0,\n timestamp: Date.now(),\n speechDuration: 0,\n silenceDuration: 0,\n frames: [],\n probability: 0,\n inferenceDuration: 0,\n speaking: true,\n rawAccumulatedSilence: 0,\n rawAccumulatedSpeech: 0,\n });\n });\n }\n this.speaking = true;\n this.lastSpeakingTime = Date.now();\n\n this.bounceEOUTask?.cancel();\n break;\n case SpeechEventType.END_OF_SPEECH:\n if (this.turnDetectionMode !== 'stt') break;\n {\n const span = this.ensureUserTurnSpan();\n const ctx = this.userTurnContext(span);\n otelContext.with(ctx, () => {\n this.hooks.onEndOfSpeech({\n type: VADEventType.END_OF_SPEECH,\n samplesIndex: 0,\n timestamp: Date.now(),\n speechDuration: 0,\n silenceDuration: 0,\n frames: [],\n probability: 0,\n inferenceDuration: 0,\n speaking: false,\n rawAccumulatedSilence: 0,\n rawAccumulatedSpeech: 0,\n });\n });\n }\n this.speaking = false;\n this.userTurnCommitted = true;\n this.lastSpeakingTime = Date.now();\n\n if (!this.speaking) {\n const chatCtx = this.hooks.retrieveChatCtx();\n this.logger.debug('running EOU detection on stt END_OF_SPEECH');\n this.runEOUDetection(chatCtx);\n }\n }\n }\n\n private onOverlapSpeechEvent(ev: OverlappingSpeechEvent) {\n if (ev.isInterruption) {\n this.hooks.onInterruption(ev);\n }\n }\n\n private runEOUDetection(chatCtx: ChatContext) {\n this.logger.debug(\n {\n stt: this.stt,\n audioTranscript: this.audioTranscript,\n turnDetectionMode: this.turnDetectionMode,\n },\n 'running EOU detection',\n );\n\n if (this.stt && !this.audioTranscript && this.turnDetectionMode !== 'manual') {\n // stt enabled but no transcript yet\n this.logger.debug('skipping EOU detection');\n return;\n }\n\n chatCtx = chatCtx.copy();\n chatCtx.addMessage({ role: 'user', content: this.audioTranscript });\n\n const turnDetector =\n // disable EOU model if manual turn detection enabled\n this.audioTranscript && this.turnDetectionMode !== 'manual' ? this.turnDetector : undefined;\n\n const bounceEOUTask =\n (\n lastSpeakingTime: number | undefined,\n lastFinalTranscriptTime: number,\n speechStartTime: number | undefined,\n ) =>\n async (controller: AbortController) => {\n let endpointingDelay = this.minEndpointingDelay;\n\n const userTurnSpan = this.ensureUserTurnSpan();\n const userTurnCtx = this.userTurnContext(userTurnSpan);\n\n if (turnDetector) {\n await tracer.startActiveSpan(\n async (span) => {\n this.logger.debug('Running turn detector model');\n\n let endOfTurnProbability = 0.0;\n let unlikelyThreshold: number | undefined;\n\n if (!(await turnDetector.supportsLanguage(this.lastLanguage))) {\n this.logger.debug(`Turn detector does not support language ${this.lastLanguage}`);\n } else {\n try {\n endOfTurnProbability = await turnDetector.predictEndOfTurn(chatCtx);\n unlikelyThreshold = await turnDetector.unlikelyThreshold(this.lastLanguage);\n\n this.logger.debug(\n { endOfTurnProbability, unlikelyThreshold, language: this.lastLanguage },\n 'end of turn probability',\n );\n\n if (unlikelyThreshold && endOfTurnProbability < unlikelyThreshold) {\n endpointingDelay = this.maxEndpointingDelay;\n }\n } catch (error) {\n this.logger.error(error, 'Error predicting end of turn');\n }\n }\n\n span.setAttribute(\n traceTypes.ATTR_CHAT_CTX,\n JSON.stringify(chatCtx.toJSON({ excludeTimestamp: false })),\n );\n span.setAttribute(traceTypes.ATTR_EOU_PROBABILITY, endOfTurnProbability);\n span.setAttribute(traceTypes.ATTR_EOU_UNLIKELY_THRESHOLD, unlikelyThreshold ?? 0);\n span.setAttribute(traceTypes.ATTR_EOU_DELAY, endpointingDelay);\n span.setAttribute(traceTypes.ATTR_EOU_LANGUAGE, this.lastLanguage ?? '');\n },\n {\n name: 'eou_detection',\n context: userTurnCtx,\n },\n );\n }\n\n let extraSleep = endpointingDelay;\n if (lastSpeakingTime !== undefined) {\n extraSleep += lastSpeakingTime - Date.now();\n }\n\n if (extraSleep > 0) {\n // add delay to see if there's a potential upcoming EOU task that cancels this one\n await delay(Math.max(extraSleep, 0), { signal: controller.signal });\n }\n\n this.logger.debug({ transcript: this.audioTranscript }, 'end of user turn');\n\n const confidenceAvg =\n this.finalTranscriptConfidence.length > 0\n ? this.finalTranscriptConfidence.reduce((a, b) => a + b, 0) /\n this.finalTranscriptConfidence.length\n : 0;\n\n let startedSpeakingAt: number | undefined;\n let stoppedSpeakingAt: number | undefined;\n let transcriptionDelay: number | undefined;\n let endOfUtteranceDelay: number | undefined;\n\n // sometimes, we can't calculate the metrics because VAD was unreliable.\n // in this case, we just ignore the calculation, it's better than providing likely wrong values\n if (\n lastFinalTranscriptTime !== 0 &&\n lastSpeakingTime !== undefined &&\n speechStartTime !== undefined\n ) {\n startedSpeakingAt = speechStartTime;\n stoppedSpeakingAt = lastSpeakingTime;\n transcriptionDelay = Math.max(lastFinalTranscriptTime - lastSpeakingTime, 0);\n endOfUtteranceDelay = Date.now() - lastSpeakingTime;\n }\n\n const committed = await this.hooks.onEndOfTurn({\n newTranscript: this.audioTranscript,\n transcriptConfidence: confidenceAvg,\n transcriptionDelay: transcriptionDelay ?? 0,\n endOfUtteranceDelay: endOfUtteranceDelay ?? 0,\n startedSpeakingAt,\n stoppedSpeakingAt,\n });\n\n if (committed) {\n this._endUserTurnSpan({\n transcript: this.audioTranscript,\n confidence: confidenceAvg,\n transcriptionDelay: transcriptionDelay ?? 0,\n endOfUtteranceDelay: endOfUtteranceDelay ?? 0,\n });\n\n // clear the transcript if the user turn was committed\n this.audioTranscript = '';\n this.finalTranscriptConfidence = [];\n this.lastSpeakingTime = undefined;\n this.lastFinalTranscriptTime = 0;\n this.speechStartTime = undefined;\n }\n\n this.userTurnCommitted = false;\n };\n\n // cancel any existing EOU task\n this.bounceEOUTask?.cancel();\n // copy the values before awaiting (the values can change)\n this.bounceEOUTask = Task.from(\n bounceEOUTask(this.lastSpeakingTime, this.lastFinalTranscriptTime, this.speechStartTime),\n );\n\n this.bounceEOUTask.result\n .then(() => {\n this.logger.debug('EOU detection task completed');\n })\n .catch((err: unknown) => {\n if (err instanceof Error && err.message.includes('This operation was aborted')) {\n // ignore aborted errors\n return;\n }\n this.logger.error(err, 'Error in EOU detection task:');\n });\n }\n\n private async createSttTask(stt: STTNode | undefined, signal: AbortSignal) {\n if (!stt) return;\n\n this.logger.debug('createSttTask: create stt stream from stt node');\n\n const sttStream = await stt(this.sttInputStream, {});\n\n if (signal.aborted || sttStream === null) return;\n\n if (sttStream instanceof ReadableStream) {\n const reader = sttStream.getReader();\n\n signal.addEventListener('abort', async () => {\n try {\n reader.releaseLock();\n await sttStream?.cancel();\n } catch (e) {\n this.logger.debug('createSttTask: error during abort handler:', e);\n }\n });\n\n try {\n while (true) {\n if (signal.aborted) break;\n\n const { done, value: ev } = await reader.read();\n if (done) break;\n\n if (typeof ev === 'string') {\n throw new Error('STT node must yield SpeechEvent');\n } else {\n await this.onSTTEvent(ev);\n }\n }\n } catch (e) {\n if (isStreamReaderReleaseError(e)) {\n return;\n }\n this.logger.error({ error: e }, 'createSttTask: error reading sttStream');\n } finally {\n reader.releaseLock();\n try {\n await sttStream.cancel();\n } catch (e) {\n this.logger.debug(\n 'createSttTask: error cancelling sttStream (may already be cancelled):',\n e,\n );\n }\n }\n }\n }\n\n private async createVadTask(vad: VAD | undefined, signal: AbortSignal) {\n if (!vad) return;\n\n const vadStream = vad.stream();\n vadStream.updateInputStream(this.vadInputStream);\n\n const abortHandler = () => {\n vadStream.detachInputStream();\n vadStream.close();\n signal.removeEventListener('abort', abortHandler);\n };\n signal.addEventListener('abort', abortHandler);\n\n try {\n for await (const ev of vadStream) {\n if (signal.aborted) break;\n\n switch (ev.type) {\n case VADEventType.START_OF_SPEECH:\n this.logger.debug('VAD task: START_OF_SPEECH');\n {\n const startTime = Date.now() - ev.speechDuration;\n const span = this.ensureUserTurnSpan(startTime);\n const ctx = this.userTurnContext(span);\n otelContext.with(ctx, () => this.hooks.onStartOfSpeech(ev));\n }\n this.speaking = true;\n\n // Capture sample rate from the first VAD event if not already set\n if (ev.frames.length > 0 && ev.frames[0]) {\n this.sampleRate = ev.frames[0].sampleRate;\n }\n\n this.bounceEOUTask?.cancel();\n break;\n case VADEventType.INFERENCE_DONE:\n this.hooks.onVADInferenceDone(ev);\n // for metrics, get the \"earliest\" signal of speech as possible\n if (ev.rawAccumulatedSpeech > 0.0) {\n this.lastSpeakingTime = Date.now();\n\n if (this.speechStartTime === undefined) {\n // Backdate speechStartTime to the actual start of accumulated speech.\n // ev.rawAccumulatedSpeech is in ms (VADEvent durations are all ms in TS).\n this.speechStartTime = Date.now() - ev.rawAccumulatedSpeech;\n }\n }\n break;\n case VADEventType.END_OF_SPEECH:\n this.logger.debug('VAD task: END_OF_SPEECH');\n {\n const span = this.ensureUserTurnSpan();\n const ctx = this.userTurnContext(span);\n otelContext.with(ctx, () => this.hooks.onEndOfSpeech(ev));\n }\n\n // when VAD fires END_OF_SPEECH, it already waited for the silence_duration\n this.speaking = false;\n\n if (\n this.vadBaseTurnDetection ||\n (this.turnDetectionMode === 'stt' && this.userTurnCommitted)\n ) {\n const chatCtx = this.hooks.retrieveChatCtx();\n this.runEOUDetection(chatCtx);\n }\n break;\n }\n }\n } catch (e) {\n this.logger.error(e, 'Error in VAD task');\n } finally {\n this.logger.debug('VAD task closed');\n }\n }\n\n private async createInterruptionTask(\n interruptionDetection: AdaptiveInterruptionDetector | undefined,\n signal: AbortSignal,\n ) {\n if (!interruptionDetection || !this.interruptionStreamChannel) return;\n\n let numRetries = 0;\n const maxRetries = apiConnectDefaults.maxRetries;\n\n while (!signal.aborted) {\n const stream = interruptionDetection.createStream();\n const eventReader = stream.stream().getReader();\n\n const cleanup = async () => {\n try {\n signal.removeEventListener('abort', cleanup);\n eventReader.releaseLock();\n await stream.close();\n } catch (e) {\n this.logger.debug('createInterruptionTask: error during cleanup:', e);\n }\n };\n\n signal.addEventListener('abort', cleanup, { once: true });\n\n let forwardTask: Promise<void> | undefined;\n\n try {\n // Unlike Python where _agent_speech_started lives on `self` and survives retries,\n // JS creates a fresh InterruptionStreamBase per retry with agentSpeechStarted = false.\n // Re-inject the sentinel so the new stream knows the agent is mid-speech.\n if (numRetries > 0 && this.isAgentSpeaking) {\n await stream.pushFrame(InterruptionStreamSentinel.agentSpeechStarted());\n }\n\n forwardTask = (async () => {\n const inputReader = this.interruptionStreamChannel!.stream().getReader();\n const abortPromise = waitForAbort(signal);\n\n try {\n while (!signal.aborted) {\n const res = await Promise.race([inputReader.read(), abortPromise]);\n if (!res) break;\n\n const { value, done } = res;\n if (done) break;\n\n if (value instanceof AudioFrame) {\n const frameDurationMs = (value.samplesPerChannel / value.sampleRate) * 1000;\n this._inputStartedAt ??= Date.now() - frameDurationMs;\n } else {\n this._inputStartedAt ??= Date.now();\n }\n\n await stream.pushFrame(value);\n }\n } finally {\n inputReader.releaseLock();\n }\n })();\n\n const abortPromise = waitForAbort(signal);\n\n while (!signal.aborted) {\n const res = await Promise.race([eventReader.read(), abortPromise]);\n if (!res) break;\n const { done, value: ev } = res;\n if (done) break;\n this.onOverlapSpeechEvent(ev);\n }\n break;\n } catch (e) {\n if (signal.aborted) break;\n\n if (isAPIError(e)) {\n if (maxRetries === 0 || !e.retryable) {\n interruptionDetection.emitError(\n new InterruptionDetectionError(\n e.message,\n Date.now(),\n interruptionDetection.label,\n false,\n ),\n );\n break;\n } else if (numRetries >= maxRetries) {\n interruptionDetection.emitError(\n new InterruptionDetectionError(\n `failed to detect interruption after ${numRetries} attempts`,\n Date.now(),\n interruptionDetection.label,\n false,\n ),\n );\n break;\n } else {\n const retryInterval = intervalForRetry(numRetries);\n interruptionDetection.emitError(\n new InterruptionDetectionError(\n e.message,\n Date.now(),\n interruptionDetection.label,\n true,\n ),\n );\n this.logger.warn(\n { model: interruptionDetection.label, attempt: numRetries },\n `failed to detect interruption, retrying in ${retryInterval}ms`,\n );\n numRetries++;\n await delay(retryInterval, { signal });\n }\n } else {\n const msg = e instanceof Error ? e.message : String(e);\n interruptionDetection.emitError(\n new InterruptionDetectionError(msg, Date.now(), interruptionDetection.label, false),\n );\n this.logger.error(e, 'Error in interruption task');\n break;\n }\n } finally {\n await cleanup();\n await forwardTask?.catch((e) => {\n this.logger.debug({ err: e }, 'interruption task exited with error');\n });\n }\n }\n this.logger.debug('Interruption task closed');\n }\n\n setInputAudioStream(audioStream: ReadableStream<AudioFrame>) {\n this.deferredInputStream.setSource(audioStream);\n }\n\n detachInputAudioStream() {\n this.deferredInputStream.detachSource();\n }\n\n clearUserTurn() {\n this.audioTranscript = '';\n this.audioInterimTranscript = '';\n this.audioPreflightTranscript = '';\n this.finalTranscriptConfidence = [];\n this.userTurnCommitted = false;\n\n this.sttTask?.cancelAndWait().finally(() => {\n this.sttTask = Task.from(({ signal }) => this.createSttTask(this.stt, signal));\n this.sttTask.result.catch((err) => {\n this.logger.error(`Error running STT task: ${err}`);\n });\n });\n }\n\n commitUserTurn(audioDetached: boolean) {\n const commitUserTurnTask =\n (delayDuration: number = 500) =>\n async (controller: AbortController) => {\n if (Date.now() - this.lastFinalTranscriptTime > delayDuration) {\n // flush the stt by pushing silence\n if (audioDetached && this.sampleRate !== undefined) {\n const numSamples = Math.floor(this.sampleRate * 0.5);\n const silence = new Int16Array(numSamples * 2);\n const silenceFrame = new AudioFrame(silence, this.sampleRate, 1, numSamples);\n this.silenceAudioWriter.write(silenceFrame);\n }\n\n // wait for the final transcript to be available\n await delay(delayDuration, { signal: controller.signal });\n }\n\n if (this.audioInterimTranscript) {\n // append interim transcript in case the final transcript is not ready\n this.audioTranscript = `${this.audioTranscript} ${this.audioInterimTranscript}`.trim();\n }\n this.audioInterimTranscript = '';\n\n const chatCtx = this.hooks.retrieveChatCtx();\n this.logger.debug('running EOU detection on commitUserTurn');\n this.runEOUDetection(chatCtx);\n this.userTurnCommitted = true;\n };\n\n // cancel any existing commit user turn task\n this.commitUserTurnTask?.cancel();\n this.commitUserTurnTask = Task.from(commitUserTurnTask());\n\n this.commitUserTurnTask.result\n .then(() => {\n this.logger.debug('User turn committed');\n })\n .catch((err: unknown) => {\n if (err instanceof Error && err.name === 'AbortError') {\n this.logger.debug('User turn commit task cancelled');\n return;\n }\n this.logger.error(err, 'Error in user turn commit task:');\n });\n }\n\n async close() {\n this.detachInputAudioStream();\n this.silenceAudioWriter.releaseLock();\n await this.commitUserTurnTask?.cancelAndWait();\n await this.sttTask?.cancelAndWait();\n await this.vadTask?.cancelAndWait();\n await this.bounceEOUTask?.cancelAndWait();\n await this.interruptionTask?.cancelAndWait();\n await this.interruptionStreamChannel?.close();\n }\n\n private _endUserTurnSpan({\n transcript,\n confidence,\n transcriptionDelay,\n endOfUtteranceDelay,\n }: {\n transcript: string;\n confidence: number;\n transcriptionDelay: number;\n endOfUtteranceDelay: number;\n }): void {\n if (this.userTurnSpan) {\n this.userTurnSpan.setAttributes({\n [traceTypes.ATTR_USER_TRANSCRIPT]: transcript,\n [traceTypes.ATTR_TRANSCRIPT_CONFIDENCE]: confidence,\n [traceTypes.ATTR_TRANSCRIPTION_DELAY]: transcriptionDelay,\n [traceTypes.ATTR_END_OF_TURN_DELAY]: endOfUtteranceDelay,\n });\n this.userTurnSpan.end();\n this.userTurnSpan = undefined;\n }\n }\n\n private get vadBaseTurnDetection() {\n if (typeof this.turnDetectionMode === 'object') {\n return false;\n }\n\n if (this.turnDetectionMode === undefined || this.turnDetectionMode === 'vad') {\n return true;\n }\n\n return false;\n }\n}\n"],"mappings":"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","names":["abortPromise"]}
@@ -1,12 +1,16 @@
1
1
  "use strict";
2
2
  var import_rtc_node = require("@livekit/rtc-node");
3
+ var import_api = require("@opentelemetry/api");
3
4
  var import_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
4
5
  var import_sdk_trace_node = require("@opentelemetry/sdk-trace-node");
6
+ var import_web = require("node:stream/web");
5
7
  var import_vitest = require("vitest");
8
+ var import_chat_context = require("../llm/chat_context.cjs");
6
9
  var import_log = require("../log.cjs");
7
10
  var import_stt = require("../stt/stt.cjs");
8
11
  var import_telemetry = require("../telemetry/index.cjs");
9
12
  var import_vad = require("../vad.cjs");
13
+ var import_agent_session = require("./agent_session.cjs");
10
14
  var import_audio_recognition = require("./audio_recognition.cjs");
11
15
  function setupInMemoryTracing() {
12
16
  const exporter = new import_sdk_trace_base.InMemorySpanExporter();
@@ -19,6 +23,20 @@ function setupInMemoryTracing() {
19
23
  function spanByName(spans, name) {
20
24
  return spans.find((s) => s.name === name);
21
25
  }
26
+ function createFakeSession(rootSpanContext = import_api.ROOT_CONTEXT) {
27
+ return {
28
+ _agentState: "listening",
29
+ _roomIO: {
30
+ linkedParticipant: { sid: "p3", identity: "charlie", kind: import_rtc_node.ParticipantKind.AGENT }
31
+ },
32
+ _setUserAwayTimer: import_vitest.vi.fn(),
33
+ _cancelUserAwayTimer: import_vitest.vi.fn(),
34
+ _userSpeakingSpan: void 0,
35
+ _userState: "listening",
36
+ emit: import_vitest.vi.fn(),
37
+ rootSpanContext
38
+ };
39
+ }
22
40
  class FakeVADStream extends Object {
23
41
  // We intentionally avoid extending the real VADStream (it is not exported as a value in JS output
24
42
  // in some bundling contexts). Instead we emulate the async iterator shape used by AudioRecognition.
@@ -57,6 +75,8 @@ class FakeVAD extends import_vad.VAD {
57
75
  }
58
76
  }
59
77
  const alwaysTrueTurnDetector = {
78
+ model: "test-turn-detector",
79
+ provider: "test-provider",
60
80
  supportsLanguage: async () => true,
61
81
  unlikelyThreshold: async () => void 0,
62
82
  predictEndOfTurn: async () => 1
@@ -66,22 +86,14 @@ const alwaysTrueTurnDetector = {
66
86
  (0, import_vitest.it)("creates user_turn and parents eou_detection under it (stt mode)", async () => {
67
87
  const { exporter } = setupInMemoryTracing();
68
88
  const hooks = {
89
+ onInterruption: import_vitest.vi.fn(),
69
90
  onStartOfSpeech: import_vitest.vi.fn(),
70
91
  onVADInferenceDone: import_vitest.vi.fn(),
71
92
  onEndOfSpeech: import_vitest.vi.fn(),
72
93
  onInterimTranscript: import_vitest.vi.fn(),
73
94
  onFinalTranscript: import_vitest.vi.fn(),
74
95
  onPreemptiveGeneration: import_vitest.vi.fn(),
75
- retrieveChatCtx: () => ({
76
- copy() {
77
- return this;
78
- },
79
- addMessage() {
80
- },
81
- toJSON() {
82
- return { items: [] };
83
- }
84
- }),
96
+ retrieveChatCtx: () => import_chat_context.ChatContext.empty(),
85
97
  onEndOfTurn: import_vitest.vi.fn(async () => true)
86
98
  };
87
99
  const sttEvents = [
@@ -100,7 +112,7 @@ const alwaysTrueTurnDetector = {
100
112
  },
101
113
  { type: import_stt.SpeechEventType.END_OF_SPEECH }
102
114
  ];
103
- const sttNode = async () => new ReadableStream({
115
+ const sttNode = async () => new import_web.ReadableStream({
104
116
  start(controller) {
105
117
  for (const ev of sttEvents) controller.enqueue(ev);
106
118
  controller.close();
@@ -126,6 +138,9 @@ const alwaysTrueTurnDetector = {
126
138
  const eou = spanByName(spans, "eou_detection");
127
139
  (0, import_vitest.expect)(userTurn, "user_turn span missing").toBeTruthy();
128
140
  (0, import_vitest.expect)(eou, "eou_detection span missing").toBeTruthy();
141
+ if (!userTurn || !eou) {
142
+ throw new Error("expected user_turn and eou_detection spans");
143
+ }
129
144
  (0, import_vitest.expect)(eou.parentSpanId).toBe(userTurn.spanContext().spanId);
130
145
  (0, import_vitest.expect)(userTurn.attributes["lk.participant_id"]).toBe("p1");
131
146
  (0, import_vitest.expect)(userTurn.attributes["lk.participant_identity"]).toBe("bob");
@@ -138,22 +153,14 @@ const alwaysTrueTurnDetector = {
138
153
  (0, import_vitest.it)("creates user_turn from VAD startTime (vad mode) and keeps same parenting", async () => {
139
154
  const { exporter } = setupInMemoryTracing();
140
155
  const hooks = {
156
+ onInterruption: import_vitest.vi.fn(),
141
157
  onStartOfSpeech: import_vitest.vi.fn(),
142
158
  onVADInferenceDone: import_vitest.vi.fn(),
143
159
  onEndOfSpeech: import_vitest.vi.fn(),
144
160
  onInterimTranscript: import_vitest.vi.fn(),
145
161
  onFinalTranscript: import_vitest.vi.fn(),
146
162
  onPreemptiveGeneration: import_vitest.vi.fn(),
147
- retrieveChatCtx: () => ({
148
- copy() {
149
- return this;
150
- },
151
- addMessage() {
152
- },
153
- toJSON() {
154
- return { items: [] };
155
- }
156
- }),
163
+ retrieveChatCtx: () => import_chat_context.ChatContext.empty(),
157
164
  onEndOfTurn: import_vitest.vi.fn(async () => true)
158
165
  };
159
166
  const now = Date.now();
@@ -199,7 +206,7 @@ const alwaysTrueTurnDetector = {
199
206
  ]
200
207
  }
201
208
  ];
202
- const sttNode = async () => new ReadableStream({
209
+ const sttNode = async () => new import_web.ReadableStream({
203
210
  start(controller) {
204
211
  for (const ev of sttEvents) controller.enqueue(ev);
205
212
  controller.close();
@@ -225,9 +232,64 @@ const alwaysTrueTurnDetector = {
225
232
  const eou = spanByName(spans, "eou_detection");
226
233
  (0, import_vitest.expect)(userTurn).toBeTruthy();
227
234
  (0, import_vitest.expect)(eou).toBeTruthy();
235
+ if (!userTurn || !eou) {
236
+ throw new Error("expected user_turn and eou_detection spans");
237
+ }
228
238
  (0, import_vitest.expect)(eou.parentSpanId).toBe(userTurn.spanContext().spanId);
229
239
  (0, import_vitest.expect)(hooks.onStartOfSpeech).toHaveBeenCalled();
230
240
  (0, import_vitest.expect)(hooks.onEndOfSpeech).toHaveBeenCalled();
231
241
  });
242
+ (0, import_vitest.it)("parents user_speaking under user_turn when an explicit speech context is provided", () => {
243
+ const { exporter } = setupInMemoryTracing();
244
+ const sessionSpan = import_telemetry.tracer.startSpan({ name: "agent_session", context: import_api.ROOT_CONTEXT });
245
+ const sessionContext = import_api.trace.setSpan(import_api.ROOT_CONTEXT, sessionSpan);
246
+ const fakeSession = createFakeSession(sessionContext);
247
+ const userTurn = import_telemetry.tracer.startSpan({ name: "user_turn", context: sessionContext });
248
+ const userTurnContext = import_api.trace.setSpan(sessionContext, userTurn);
249
+ const speakingStartedAt = Date.now() - 100;
250
+ const speakingEndedAt = Date.now();
251
+ import_api.context.with(userTurnContext, () => {
252
+ import_agent_session.AgentSession.prototype._updateUserState.call(fakeSession, "speaking", {
253
+ lastSpeakingTime: speakingStartedAt,
254
+ otelContext: import_api.context.active()
255
+ });
256
+ import_agent_session.AgentSession.prototype._updateUserState.call(fakeSession, "listening", {
257
+ lastSpeakingTime: speakingEndedAt,
258
+ otelContext: import_api.context.active()
259
+ });
260
+ });
261
+ userTurn.end();
262
+ sessionSpan.end();
263
+ const spans = exporter.getFinishedSpans();
264
+ const userSpeaking = spanByName(spans, "user_speaking");
265
+ const exportedUserTurn = spanByName(spans, "user_turn");
266
+ (0, import_vitest.expect)(userSpeaking).toBeTruthy();
267
+ (0, import_vitest.expect)(exportedUserTurn).toBeTruthy();
268
+ if (!userSpeaking || !exportedUserTurn) {
269
+ throw new Error("expected user_speaking and user_turn spans");
270
+ }
271
+ (0, import_vitest.expect)(userSpeaking.parentSpanId).toBe(exportedUserTurn.spanContext().spanId);
272
+ (0, import_vitest.expect)(userSpeaking.attributes["lk.participant_id"]).toBe("p3");
273
+ });
274
+ (0, import_vitest.it)("keeps user_speaking attached to the session root without an explicit speech context", () => {
275
+ const { exporter } = setupInMemoryTracing();
276
+ const sessionSpan = import_telemetry.tracer.startSpan({ name: "agent_session", context: import_api.ROOT_CONTEXT });
277
+ const sessionContext = import_api.trace.setSpan(import_api.ROOT_CONTEXT, sessionSpan);
278
+ const fakeSession = createFakeSession(sessionContext);
279
+ import_agent_session.AgentSession.prototype._updateUserState.call(fakeSession, "speaking", {
280
+ lastSpeakingTime: Date.now() - 100
281
+ });
282
+ import_agent_session.AgentSession.prototype._updateUserState.call(fakeSession, "listening", {
283
+ lastSpeakingTime: Date.now()
284
+ });
285
+ sessionSpan.end();
286
+ const spans = exporter.getFinishedSpans();
287
+ const userSpeaking = spanByName(spans, "user_speaking");
288
+ (0, import_vitest.expect)(userSpeaking).toBeTruthy();
289
+ if (!userSpeaking) {
290
+ throw new Error("expected user_speaking span");
291
+ }
292
+ (0, import_vitest.expect)(userSpeaking.parentSpanId).toBe(sessionSpan.spanContext().spanId);
293
+ });
232
294
  });
233
295
  //# sourceMappingURL=audio_recognition_span.test.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/voice/audio_recognition_span.test.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { ParticipantKind } from '@livekit/rtc-node';\nimport { InMemorySpanExporter, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base';\nimport { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';\nimport { describe, expect, it, vi } from 'vitest';\nimport { initializeLogger } from '../log.js';\nimport { type SpeechEvent, SpeechEventType } from '../stt/stt.js';\nimport { setTracerProvider } from '../telemetry/index.js';\nimport { VAD, type VADEvent, VADEventType, type VADStream } from '../vad.js';\nimport { AudioRecognition, type _TurnDetector } from './audio_recognition.js';\n\nfunction setupInMemoryTracing() {\n const exporter = new InMemorySpanExporter();\n const provider = new NodeTracerProvider();\n provider.addSpanProcessor(new SimpleSpanProcessor(exporter));\n provider.register();\n setTracerProvider(provider);\n return { exporter };\n}\n\nfunction spanByName(spans: any[], name: string) {\n return spans.find((s) => s.name === name);\n}\n\nclass FakeVADStream extends (Object as unknown as { new (): VADStream }) {\n // We intentionally avoid extending the real VADStream (it is not exported as a value in JS output\n // in some bundling contexts). Instead we emulate the async iterator shape used by AudioRecognition.\n private events: VADEvent[];\n private idx = 0;\n constructor(events: VADEvent[]) {\n super();\n this.events = events;\n }\n updateInputStream() {}\n detachInputStream() {}\n close() {}\n [Symbol.asyncIterator]() {\n return this;\n }\n async next(): Promise<IteratorResult<VADEvent>> {\n if (this.idx >= this.events.length) {\n return { done: true, value: undefined };\n }\n const value = this.events[this.idx++]!;\n return { done: false, value };\n }\n}\n\nclass FakeVAD extends VAD {\n label = 'fake-vad';\n private events: VADEvent[];\n constructor(events: VADEvent[]) {\n super({ updateInterval: 1 });\n this.events = events;\n }\n stream(): any {\n return new FakeVADStream(this.events);\n }\n}\n\nconst alwaysTrueTurnDetector: _TurnDetector = {\n supportsLanguage: async () => true,\n unlikelyThreshold: async () => undefined,\n predictEndOfTurn: async () => 1.0,\n};\n\ndescribe('AudioRecognition user_turn span parity', () => {\n initializeLogger({ pretty: false, level: 'silent' });\n\n it('creates user_turn and parents eou_detection under it (stt mode)', async () => {\n const { exporter } = setupInMemoryTracing();\n\n const hooks = {\n onStartOfSpeech: vi.fn(),\n onVADInferenceDone: vi.fn(),\n onEndOfSpeech: vi.fn(),\n onInterimTranscript: vi.fn(),\n onFinalTranscript: vi.fn(),\n onPreemptiveGeneration: vi.fn(),\n retrieveChatCtx: () =>\n ({\n copy() {\n return this;\n },\n addMessage() {},\n toJSON() {\n return { items: [] };\n },\n }) as any,\n onEndOfTurn: vi.fn(async () => true),\n };\n\n const sttEvents: SpeechEvent[] = [\n { type: SpeechEventType.START_OF_SPEECH },\n {\n type: SpeechEventType.FINAL_TRANSCRIPT,\n alternatives: [\n {\n language: 'en',\n text: 'hello',\n startTime: 0,\n endTime: 0,\n confidence: 0.9,\n },\n ],\n },\n { type: SpeechEventType.END_OF_SPEECH },\n ];\n\n const sttNode = async () =>\n new ReadableStream<SpeechEvent>({\n start(controller) {\n for (const ev of sttEvents) controller.enqueue(ev);\n controller.close();\n },\n });\n\n const ar = new AudioRecognition({\n recognitionHooks: hooks as any,\n stt: sttNode as any,\n vad: undefined,\n turnDetector: alwaysTrueTurnDetector,\n turnDetectionMode: 'stt',\n minEndpointingDelay: 0,\n maxEndpointingDelay: 0,\n sttModel: 'deepgram-nova2',\n sttProvider: 'deepgram',\n getLinkedParticipant: () => ({ sid: 'p1', identity: 'bob', kind: ParticipantKind.AGENT }),\n });\n\n await ar.start();\n // allow background task to drain\n await new Promise((r) => setTimeout(r, 20));\n await ar.close();\n\n const spans = exporter.getFinishedSpans();\n const userTurn = spanByName(spans, 'user_turn');\n const eou = spanByName(spans, 'eou_detection');\n expect(userTurn, 'user_turn span missing').toBeTruthy();\n expect(eou, 'eou_detection span missing').toBeTruthy();\n\n expect(eou.parentSpanId).toBe(userTurn.spanContext().spanId);\n\n // creation-time attributes\n expect(userTurn.attributes['lk.participant_id']).toBe('p1');\n expect(userTurn.attributes['lk.participant_identity']).toBe('bob');\n expect(userTurn.attributes['lk.participant_kind']).toBe('AGENT');\n expect(userTurn.attributes['gen_ai.request.model']).toBe('deepgram-nova2');\n expect(userTurn.attributes['gen_ai.provider.name']).toBe('deepgram');\n\n // end-of-turn attributes\n expect(userTurn.attributes['lk.user_transcript']).toContain('hello');\n expect(userTurn.attributes['lk.transcript_confidence']).toBeGreaterThan(0);\n });\n\n it('creates user_turn from VAD startTime (vad mode) and keeps same parenting', async () => {\n const { exporter } = setupInMemoryTracing();\n\n const hooks = {\n onStartOfSpeech: vi.fn(),\n onVADInferenceDone: vi.fn(),\n onEndOfSpeech: vi.fn(),\n onInterimTranscript: vi.fn(),\n onFinalTranscript: vi.fn(),\n onPreemptiveGeneration: vi.fn(),\n retrieveChatCtx: () =>\n ({\n copy() {\n return this;\n },\n addMessage() {},\n toJSON() {\n return { items: [] };\n },\n }) as any,\n onEndOfTurn: vi.fn(async () => true),\n };\n\n const now = Date.now();\n const vadEvents: VADEvent[] = [\n {\n type: VADEventType.START_OF_SPEECH,\n samplesIndex: 0,\n timestamp: now,\n speechDuration: 100,\n silenceDuration: 0,\n frames: [],\n probability: 0,\n inferenceDuration: 0,\n speaking: true,\n rawAccumulatedSilence: 0,\n rawAccumulatedSpeech: 0,\n },\n {\n type: VADEventType.END_OF_SPEECH,\n samplesIndex: 0,\n timestamp: now + 200,\n speechDuration: 100,\n silenceDuration: 100,\n frames: [],\n probability: 0,\n inferenceDuration: 0,\n speaking: false,\n rawAccumulatedSilence: 0,\n rawAccumulatedSpeech: 0,\n },\n ];\n\n const sttEvents: SpeechEvent[] = [\n {\n type: SpeechEventType.FINAL_TRANSCRIPT,\n alternatives: [\n {\n language: 'en',\n text: 'test',\n startTime: 0,\n endTime: 0,\n confidence: 0.8,\n },\n ],\n },\n ];\n\n const sttNode = async () =>\n new ReadableStream<SpeechEvent>({\n start(controller) {\n for (const ev of sttEvents) controller.enqueue(ev);\n controller.close();\n },\n });\n\n const ar = new AudioRecognition({\n recognitionHooks: hooks as any,\n stt: sttNode as any,\n vad: new FakeVAD(vadEvents) as any,\n turnDetector: alwaysTrueTurnDetector,\n turnDetectionMode: 'vad',\n minEndpointingDelay: 0,\n maxEndpointingDelay: 0,\n sttModel: 'stt-model',\n sttProvider: 'stt-provider',\n getLinkedParticipant: () => ({ sid: 'p2', identity: 'alice', kind: ParticipantKind.AGENT }),\n });\n\n await ar.start();\n await new Promise((r) => setTimeout(r, 20));\n await ar.close();\n\n const spans = exporter.getFinishedSpans();\n const userTurn = spanByName(spans, 'user_turn');\n const eou = spanByName(spans, 'eou_detection');\n expect(userTurn).toBeTruthy();\n expect(eou).toBeTruthy();\n expect(eou.parentSpanId).toBe(userTurn.spanContext().spanId);\n\n expect(hooks.onStartOfSpeech).toHaveBeenCalled();\n expect(hooks.onEndOfSpeech).toHaveBeenCalled();\n });\n});\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/voice/audio_recognition_span.test.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { ParticipantKind } from '@livekit/rtc-node';\nimport { ROOT_CONTEXT, context as otelContext, trace } from '@opentelemetry/api';\nimport {\n InMemorySpanExporter,\n type ReadableSpan,\n SimpleSpanProcessor,\n} from '@opentelemetry/sdk-trace-base';\nimport { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';\nimport { ReadableStream } from 'node:stream/web';\nimport { describe, expect, it, vi } from 'vitest';\nimport { ChatContext } from '../llm/chat_context.js';\nimport { initializeLogger } from '../log.js';\nimport { type SpeechEvent, SpeechEventType } from '../stt/stt.js';\nimport { setTracerProvider, tracer } from '../telemetry/index.js';\nimport { VAD, type VADEvent, VADEventType, type VADStream } from '../vad.js';\nimport { AgentSession } from './agent_session.js';\nimport {\n AudioRecognition,\n type RecognitionHooks,\n type _TurnDetector,\n} from './audio_recognition.js';\nimport type { STTNode } from './io.js';\n\nfunction setupInMemoryTracing() {\n const exporter = new InMemorySpanExporter();\n const provider = new NodeTracerProvider();\n provider.addSpanProcessor(new SimpleSpanProcessor(exporter));\n provider.register();\n setTracerProvider(provider);\n return { exporter };\n}\n\nfunction spanByName(spans: ReadableSpan[], name: string) {\n return spans.find((s) => s.name === name);\n}\n\nfunction createFakeSession(rootSpanContext = ROOT_CONTEXT): AgentSession {\n return {\n _agentState: 'listening',\n _roomIO: {\n linkedParticipant: { sid: 'p3', identity: 'charlie', kind: ParticipantKind.AGENT },\n },\n _setUserAwayTimer: vi.fn(),\n _cancelUserAwayTimer: vi.fn(),\n _userSpeakingSpan: undefined,\n _userState: 'listening',\n emit: vi.fn(),\n rootSpanContext,\n } as unknown as AgentSession;\n}\n\nclass FakeVADStream extends (Object as unknown as { new (): VADStream }) {\n // We intentionally avoid extending the real VADStream (it is not exported as a value in JS output\n // in some bundling contexts). Instead we emulate the async iterator shape used by AudioRecognition.\n private events: VADEvent[];\n private idx = 0;\n constructor(events: VADEvent[]) {\n super();\n this.events = events;\n }\n updateInputStream() {}\n detachInputStream() {}\n close() {}\n [Symbol.asyncIterator]() {\n return this;\n }\n async next(): Promise<IteratorResult<VADEvent>> {\n if (this.idx >= this.events.length) {\n return { done: true, value: undefined };\n }\n const value = this.events[this.idx++]!;\n return { done: false, value };\n }\n}\n\nclass FakeVAD extends VAD {\n label = 'fake-vad';\n private events: VADEvent[];\n constructor(events: VADEvent[]) {\n super({ updateInterval: 1 });\n this.events = events;\n }\n stream(): any {\n return new FakeVADStream(this.events);\n }\n}\n\nconst alwaysTrueTurnDetector: _TurnDetector = {\n model: 'test-turn-detector',\n provider: 'test-provider',\n supportsLanguage: async () => true,\n unlikelyThreshold: async () => undefined,\n predictEndOfTurn: async () => 1.0,\n};\n\ndescribe('AudioRecognition user_turn span parity', () => {\n initializeLogger({ pretty: false, level: 'silent' });\n\n it('creates user_turn and parents eou_detection under it (stt mode)', async () => {\n const { exporter } = setupInMemoryTracing();\n\n const hooks: RecognitionHooks = {\n onInterruption: vi.fn(),\n onStartOfSpeech: vi.fn(),\n onVADInferenceDone: vi.fn(),\n onEndOfSpeech: vi.fn(),\n onInterimTranscript: vi.fn(),\n onFinalTranscript: vi.fn(),\n onPreemptiveGeneration: vi.fn(),\n retrieveChatCtx: () => ChatContext.empty(),\n onEndOfTurn: vi.fn(async () => true),\n };\n\n const sttEvents: SpeechEvent[] = [\n { type: SpeechEventType.START_OF_SPEECH },\n {\n type: SpeechEventType.FINAL_TRANSCRIPT,\n alternatives: [\n {\n language: 'en',\n text: 'hello',\n startTime: 0,\n endTime: 0,\n confidence: 0.9,\n },\n ],\n },\n { type: SpeechEventType.END_OF_SPEECH },\n ];\n\n const sttNode: STTNode = async () =>\n new ReadableStream<SpeechEvent | string>({\n start(controller) {\n for (const ev of sttEvents) controller.enqueue(ev);\n controller.close();\n },\n });\n\n const ar = new AudioRecognition({\n recognitionHooks: hooks,\n stt: sttNode,\n vad: undefined,\n turnDetector: alwaysTrueTurnDetector,\n turnDetectionMode: 'stt',\n minEndpointingDelay: 0,\n maxEndpointingDelay: 0,\n sttModel: 'deepgram-nova2',\n sttProvider: 'deepgram',\n getLinkedParticipant: () => ({ sid: 'p1', identity: 'bob', kind: ParticipantKind.AGENT }),\n });\n\n await ar.start();\n // allow background task to drain\n await new Promise((r) => setTimeout(r, 20));\n await ar.close();\n\n const spans = exporter.getFinishedSpans();\n const userTurn = spanByName(spans, 'user_turn');\n const eou = spanByName(spans, 'eou_detection');\n expect(userTurn, 'user_turn span missing').toBeTruthy();\n expect(eou, 'eou_detection span missing').toBeTruthy();\n if (!userTurn || !eou) {\n throw new Error('expected user_turn and eou_detection spans');\n }\n\n expect(eou.parentSpanId).toBe(userTurn.spanContext().spanId);\n\n // creation-time attributes\n expect(userTurn.attributes['lk.participant_id']).toBe('p1');\n expect(userTurn.attributes['lk.participant_identity']).toBe('bob');\n expect(userTurn.attributes['lk.participant_kind']).toBe('AGENT');\n expect(userTurn.attributes['gen_ai.request.model']).toBe('deepgram-nova2');\n expect(userTurn.attributes['gen_ai.provider.name']).toBe('deepgram');\n\n // end-of-turn attributes\n expect(userTurn.attributes['lk.user_transcript']).toContain('hello');\n expect(userTurn.attributes['lk.transcript_confidence']).toBeGreaterThan(0);\n });\n\n it('creates user_turn from VAD startTime (vad mode) and keeps same parenting', async () => {\n const { exporter } = setupInMemoryTracing();\n\n const hooks: RecognitionHooks = {\n onInterruption: vi.fn(),\n onStartOfSpeech: vi.fn(),\n onVADInferenceDone: vi.fn(),\n onEndOfSpeech: vi.fn(),\n onInterimTranscript: vi.fn(),\n onFinalTranscript: vi.fn(),\n onPreemptiveGeneration: vi.fn(),\n retrieveChatCtx: () => ChatContext.empty(),\n onEndOfTurn: vi.fn(async () => true),\n };\n\n const now = Date.now();\n const vadEvents: VADEvent[] = [\n {\n type: VADEventType.START_OF_SPEECH,\n samplesIndex: 0,\n timestamp: now,\n speechDuration: 100,\n silenceDuration: 0,\n frames: [],\n probability: 0,\n inferenceDuration: 0,\n speaking: true,\n rawAccumulatedSilence: 0,\n rawAccumulatedSpeech: 0,\n },\n {\n type: VADEventType.END_OF_SPEECH,\n samplesIndex: 0,\n timestamp: now + 200,\n speechDuration: 100,\n silenceDuration: 100,\n frames: [],\n probability: 0,\n inferenceDuration: 0,\n speaking: false,\n rawAccumulatedSilence: 0,\n rawAccumulatedSpeech: 0,\n },\n ];\n\n const sttEvents: SpeechEvent[] = [\n {\n type: SpeechEventType.FINAL_TRANSCRIPT,\n alternatives: [\n {\n language: 'en',\n text: 'test',\n startTime: 0,\n endTime: 0,\n confidence: 0.8,\n },\n ],\n },\n ];\n\n const sttNode: STTNode = async () =>\n new ReadableStream<SpeechEvent | string>({\n start(controller) {\n for (const ev of sttEvents) controller.enqueue(ev);\n controller.close();\n },\n });\n\n const ar = new AudioRecognition({\n recognitionHooks: hooks,\n stt: sttNode,\n vad: new FakeVAD(vadEvents),\n turnDetector: alwaysTrueTurnDetector,\n turnDetectionMode: 'vad',\n minEndpointingDelay: 0,\n maxEndpointingDelay: 0,\n sttModel: 'stt-model',\n sttProvider: 'stt-provider',\n getLinkedParticipant: () => ({ sid: 'p2', identity: 'alice', kind: ParticipantKind.AGENT }),\n });\n\n await ar.start();\n await new Promise((r) => setTimeout(r, 20));\n await ar.close();\n\n const spans = exporter.getFinishedSpans();\n const userTurn = spanByName(spans, 'user_turn');\n const eou = spanByName(spans, 'eou_detection');\n expect(userTurn).toBeTruthy();\n expect(eou).toBeTruthy();\n if (!userTurn || !eou) {\n throw new Error('expected user_turn and eou_detection spans');\n }\n expect(eou.parentSpanId).toBe(userTurn.spanContext().spanId);\n\n expect(hooks.onStartOfSpeech).toHaveBeenCalled();\n expect(hooks.onEndOfSpeech).toHaveBeenCalled();\n });\n\n it('parents user_speaking under user_turn when an explicit speech context is provided', () => {\n const { exporter } = setupInMemoryTracing();\n const sessionSpan = tracer.startSpan({ name: 'agent_session', context: ROOT_CONTEXT });\n const sessionContext = trace.setSpan(ROOT_CONTEXT, sessionSpan);\n const fakeSession = createFakeSession(sessionContext);\n const userTurn = tracer.startSpan({ name: 'user_turn', context: sessionContext });\n const userTurnContext = trace.setSpan(sessionContext, userTurn);\n const speakingStartedAt = Date.now() - 100;\n const speakingEndedAt = Date.now();\n\n otelContext.with(userTurnContext, () => {\n AgentSession.prototype._updateUserState.call(fakeSession, 'speaking', {\n lastSpeakingTime: speakingStartedAt,\n otelContext: otelContext.active(),\n });\n AgentSession.prototype._updateUserState.call(fakeSession, 'listening', {\n lastSpeakingTime: speakingEndedAt,\n otelContext: otelContext.active(),\n });\n });\n\n userTurn.end();\n sessionSpan.end();\n\n const spans = exporter.getFinishedSpans();\n const userSpeaking = spanByName(spans, 'user_speaking');\n const exportedUserTurn = spanByName(spans, 'user_turn');\n expect(userSpeaking).toBeTruthy();\n expect(exportedUserTurn).toBeTruthy();\n if (!userSpeaking || !exportedUserTurn) {\n throw new Error('expected user_speaking and user_turn spans');\n }\n expect(userSpeaking.parentSpanId).toBe(exportedUserTurn.spanContext().spanId);\n expect(userSpeaking.attributes['lk.participant_id']).toBe('p3');\n });\n\n it('keeps user_speaking attached to the session root without an explicit speech context', () => {\n const { exporter } = setupInMemoryTracing();\n const sessionSpan = tracer.startSpan({ name: 'agent_session', context: ROOT_CONTEXT });\n const sessionContext = trace.setSpan(ROOT_CONTEXT, sessionSpan);\n const fakeSession = createFakeSession(sessionContext);\n\n AgentSession.prototype._updateUserState.call(fakeSession, 'speaking', {\n lastSpeakingTime: Date.now() - 100,\n });\n AgentSession.prototype._updateUserState.call(fakeSession, 'listening', {\n lastSpeakingTime: Date.now(),\n });\n\n sessionSpan.end();\n\n const spans = exporter.getFinishedSpans();\n const userSpeaking = spanByName(spans, 'user_speaking');\n expect(userSpeaking).toBeTruthy();\n if (!userSpeaking) {\n throw new Error('expected user_speaking span');\n }\n expect(userSpeaking.parentSpanId).toBe(sessionSpan.spanContext().spanId);\n });\n});\n"],"mappings":"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","names":["otelContext"]}