@livekit/agents 1.0.24 → 1.0.27

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 (184) hide show
  1. package/dist/inference/llm.cjs +1 -2
  2. package/dist/inference/llm.cjs.map +1 -1
  3. package/dist/inference/llm.d.ts.map +1 -1
  4. package/dist/inference/llm.js +1 -2
  5. package/dist/inference/llm.js.map +1 -1
  6. package/dist/inference/stt.cjs +1 -1
  7. package/dist/inference/stt.cjs.map +1 -1
  8. package/dist/inference/stt.d.ts.map +1 -1
  9. package/dist/inference/stt.js +1 -1
  10. package/dist/inference/stt.js.map +1 -1
  11. package/dist/inference/tts.cjs +4 -4
  12. package/dist/inference/tts.cjs.map +1 -1
  13. package/dist/inference/tts.d.cts +0 -1
  14. package/dist/inference/tts.d.ts +0 -1
  15. package/dist/inference/tts.d.ts.map +1 -1
  16. package/dist/inference/tts.js +4 -4
  17. package/dist/inference/tts.js.map +1 -1
  18. package/dist/ipc/job_proc_lazy_main.cjs +1 -1
  19. package/dist/ipc/job_proc_lazy_main.cjs.map +1 -1
  20. package/dist/ipc/job_proc_lazy_main.js +1 -1
  21. package/dist/ipc/job_proc_lazy_main.js.map +1 -1
  22. package/dist/job.cjs +29 -2
  23. package/dist/job.cjs.map +1 -1
  24. package/dist/job.d.cts +6 -0
  25. package/dist/job.d.ts +6 -0
  26. package/dist/job.d.ts.map +1 -1
  27. package/dist/job.js +19 -2
  28. package/dist/job.js.map +1 -1
  29. package/dist/llm/llm.cjs +2 -1
  30. package/dist/llm/llm.cjs.map +1 -1
  31. package/dist/llm/llm.d.cts +1 -1
  32. package/dist/llm/llm.d.ts +1 -1
  33. package/dist/llm/llm.d.ts.map +1 -1
  34. package/dist/llm/llm.js +2 -1
  35. package/dist/llm/llm.js.map +1 -1
  36. package/dist/stream/deferred_stream.cjs +12 -4
  37. package/dist/stream/deferred_stream.cjs.map +1 -1
  38. package/dist/stream/deferred_stream.d.cts +6 -1
  39. package/dist/stream/deferred_stream.d.ts +6 -1
  40. package/dist/stream/deferred_stream.d.ts.map +1 -1
  41. package/dist/stream/deferred_stream.js +12 -4
  42. package/dist/stream/deferred_stream.js.map +1 -1
  43. package/dist/stream/deferred_stream.test.cjs +2 -2
  44. package/dist/stream/deferred_stream.test.cjs.map +1 -1
  45. package/dist/stream/deferred_stream.test.js +2 -2
  46. package/dist/stream/deferred_stream.test.js.map +1 -1
  47. package/dist/stt/stream_adapter.cjs +15 -8
  48. package/dist/stt/stream_adapter.cjs.map +1 -1
  49. package/dist/stt/stream_adapter.d.cts +7 -3
  50. package/dist/stt/stream_adapter.d.ts +7 -3
  51. package/dist/stt/stream_adapter.d.ts.map +1 -1
  52. package/dist/stt/stream_adapter.js +15 -8
  53. package/dist/stt/stream_adapter.js.map +1 -1
  54. package/dist/stt/stt.cjs +8 -3
  55. package/dist/stt/stt.cjs.map +1 -1
  56. package/dist/stt/stt.d.cts +9 -3
  57. package/dist/stt/stt.d.ts +9 -3
  58. package/dist/stt/stt.d.ts.map +1 -1
  59. package/dist/stt/stt.js +9 -4
  60. package/dist/stt/stt.js.map +1 -1
  61. package/dist/telemetry/traces.cjs +23 -2
  62. package/dist/telemetry/traces.cjs.map +1 -1
  63. package/dist/telemetry/traces.d.ts.map +1 -1
  64. package/dist/telemetry/traces.js +23 -2
  65. package/dist/telemetry/traces.js.map +1 -1
  66. package/dist/tts/stream_adapter.cjs +10 -7
  67. package/dist/tts/stream_adapter.cjs.map +1 -1
  68. package/dist/tts/stream_adapter.d.cts +6 -3
  69. package/dist/tts/stream_adapter.d.ts +6 -3
  70. package/dist/tts/stream_adapter.d.ts.map +1 -1
  71. package/dist/tts/stream_adapter.js +10 -7
  72. package/dist/tts/stream_adapter.js.map +1 -1
  73. package/dist/tts/tts.cjs +27 -16
  74. package/dist/tts/tts.cjs.map +1 -1
  75. package/dist/tts/tts.d.cts +12 -5
  76. package/dist/tts/tts.d.ts +12 -5
  77. package/dist/tts/tts.d.ts.map +1 -1
  78. package/dist/tts/tts.js +28 -17
  79. package/dist/tts/tts.js.map +1 -1
  80. package/dist/types.cjs +21 -32
  81. package/dist/types.cjs.map +1 -1
  82. package/dist/types.d.cts +41 -10
  83. package/dist/types.d.ts +41 -10
  84. package/dist/types.d.ts.map +1 -1
  85. package/dist/types.js +18 -30
  86. package/dist/types.js.map +1 -1
  87. package/dist/voice/agent.cjs +54 -19
  88. package/dist/voice/agent.cjs.map +1 -1
  89. package/dist/voice/agent.d.ts.map +1 -1
  90. package/dist/voice/agent.js +54 -19
  91. package/dist/voice/agent.js.map +1 -1
  92. package/dist/voice/agent_activity.cjs +0 -3
  93. package/dist/voice/agent_activity.cjs.map +1 -1
  94. package/dist/voice/agent_activity.d.ts.map +1 -1
  95. package/dist/voice/agent_activity.js +0 -3
  96. package/dist/voice/agent_activity.js.map +1 -1
  97. package/dist/voice/agent_session.cjs +107 -27
  98. package/dist/voice/agent_session.cjs.map +1 -1
  99. package/dist/voice/agent_session.d.cts +16 -2
  100. package/dist/voice/agent_session.d.ts +16 -2
  101. package/dist/voice/agent_session.d.ts.map +1 -1
  102. package/dist/voice/agent_session.js +110 -27
  103. package/dist/voice/agent_session.js.map +1 -1
  104. package/dist/voice/events.cjs.map +1 -1
  105. package/dist/voice/events.d.cts +4 -4
  106. package/dist/voice/events.d.ts +4 -4
  107. package/dist/voice/events.d.ts.map +1 -1
  108. package/dist/voice/events.js.map +1 -1
  109. package/dist/voice/generation.cjs +6 -7
  110. package/dist/voice/generation.cjs.map +1 -1
  111. package/dist/voice/generation.d.ts.map +1 -1
  112. package/dist/voice/generation.js +7 -8
  113. package/dist/voice/generation.js.map +1 -1
  114. package/dist/voice/io.cjs +16 -0
  115. package/dist/voice/io.cjs.map +1 -1
  116. package/dist/voice/io.d.cts +8 -0
  117. package/dist/voice/io.d.ts +8 -0
  118. package/dist/voice/io.d.ts.map +1 -1
  119. package/dist/voice/io.js +16 -0
  120. package/dist/voice/io.js.map +1 -1
  121. package/dist/voice/recorder_io/index.cjs +23 -0
  122. package/dist/voice/recorder_io/index.cjs.map +1 -0
  123. package/dist/voice/recorder_io/index.d.cts +2 -0
  124. package/dist/voice/recorder_io/index.d.ts +2 -0
  125. package/dist/voice/recorder_io/index.d.ts.map +1 -0
  126. package/dist/voice/recorder_io/index.js +2 -0
  127. package/dist/voice/recorder_io/index.js.map +1 -0
  128. package/dist/voice/recorder_io/recorder_io.cjs +542 -0
  129. package/dist/voice/recorder_io/recorder_io.cjs.map +1 -0
  130. package/dist/voice/recorder_io/recorder_io.d.cts +100 -0
  131. package/dist/voice/recorder_io/recorder_io.d.ts +100 -0
  132. package/dist/voice/recorder_io/recorder_io.d.ts.map +1 -0
  133. package/dist/voice/recorder_io/recorder_io.js +508 -0
  134. package/dist/voice/recorder_io/recorder_io.js.map +1 -0
  135. package/dist/voice/report.cjs +7 -2
  136. package/dist/voice/report.cjs.map +1 -1
  137. package/dist/voice/report.d.cts +11 -1
  138. package/dist/voice/report.d.ts +11 -1
  139. package/dist/voice/report.d.ts.map +1 -1
  140. package/dist/voice/report.js +7 -2
  141. package/dist/voice/report.js.map +1 -1
  142. package/dist/voice/room_io/_input.cjs +2 -1
  143. package/dist/voice/room_io/_input.cjs.map +1 -1
  144. package/dist/voice/room_io/_input.d.ts.map +1 -1
  145. package/dist/voice/room_io/_input.js +2 -1
  146. package/dist/voice/room_io/_input.js.map +1 -1
  147. package/dist/voice/room_io/_output.cjs +8 -7
  148. package/dist/voice/room_io/_output.cjs.map +1 -1
  149. package/dist/voice/room_io/_output.d.cts +2 -1
  150. package/dist/voice/room_io/_output.d.ts +2 -1
  151. package/dist/voice/room_io/_output.d.ts.map +1 -1
  152. package/dist/voice/room_io/_output.js +8 -7
  153. package/dist/voice/room_io/_output.js.map +1 -1
  154. package/dist/worker.cjs +4 -3
  155. package/dist/worker.cjs.map +1 -1
  156. package/dist/worker.js +4 -3
  157. package/dist/worker.js.map +1 -1
  158. package/package.json +1 -1
  159. package/src/inference/llm.ts +0 -1
  160. package/src/inference/stt.ts +1 -2
  161. package/src/inference/tts.ts +5 -4
  162. package/src/ipc/job_proc_lazy_main.ts +1 -1
  163. package/src/job.ts +21 -2
  164. package/src/llm/llm.ts +2 -2
  165. package/src/stream/deferred_stream.test.ts +3 -3
  166. package/src/stream/deferred_stream.ts +22 -5
  167. package/src/stt/stream_adapter.ts +18 -8
  168. package/src/stt/stt.ts +19 -6
  169. package/src/telemetry/traces.ts +25 -3
  170. package/src/tts/stream_adapter.ts +15 -7
  171. package/src/tts/tts.ts +46 -21
  172. package/src/types.ts +57 -33
  173. package/src/voice/agent.ts +59 -19
  174. package/src/voice/agent_activity.ts +0 -3
  175. package/src/voice/agent_session.ts +142 -35
  176. package/src/voice/events.ts +6 -3
  177. package/src/voice/generation.ts +10 -8
  178. package/src/voice/io.ts +19 -0
  179. package/src/voice/recorder_io/index.ts +4 -0
  180. package/src/voice/recorder_io/recorder_io.ts +690 -0
  181. package/src/voice/report.ts +20 -3
  182. package/src/voice/room_io/_input.ts +2 -1
  183. package/src/voice/room_io/_output.ts +10 -7
  184. package/src/worker.ts +1 -1
@@ -13,6 +13,12 @@ export interface SessionReport {
13
13
  startedAt: number;
14
14
  /** Timestamp when the session report was created (milliseconds), typically at the end of the session */
15
15
  timestamp: number;
16
+ /** Path to the audio recording file (if recording was enabled) */
17
+ audioRecordingPath?: string;
18
+ /** Timestamp when the audio recording started (milliseconds) */
19
+ audioRecordingStartedAt?: number;
20
+ /** Duration of the session in milliseconds */
21
+ duration?: number;
16
22
  }
17
23
  export interface SessionReportOptions {
18
24
  jobId: string;
@@ -21,11 +27,15 @@ export interface SessionReportOptions {
21
27
  options: VoiceOptions;
22
28
  events: AgentEvent[];
23
29
  chatHistory: ChatContext;
24
- enableUserDataTraining?: boolean;
30
+ enableRecording?: boolean;
25
31
  /** Timestamp when the session started (milliseconds) */
26
32
  startedAt?: number;
27
33
  /** Timestamp when the session report was created (milliseconds) */
28
34
  timestamp?: number;
35
+ /** Path to the audio recording file (if recording was enabled) */
36
+ audioRecordingPath?: string;
37
+ /** Timestamp when the audio recording started (milliseconds) */
38
+ audioRecordingStartedAt?: number;
29
39
  }
30
40
  export declare function createSessionReport(opts: SessionReportOptions): SessionReport;
31
41
  export declare function sessionReportToJSON(report: SessionReport): Record<string, unknown>;
@@ -1 +1 @@
1
- {"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../src/voice/report.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,YAAY,CAAC;IACtB,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,WAAW,EAAE,WAAW,CAAC;IACzB,eAAe,EAAE,OAAO,CAAC;IACzB,wDAAwD;IACxD,SAAS,EAAE,MAAM,CAAC;IAClB,wGAAwG;IACxG,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,YAAY,CAAC;IACtB,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,WAAW,EAAE,WAAW,CAAC;IACzB,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,wDAAwD;IACxD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mEAAmE;IACnE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,oBAAoB,GAAG,aAAa,CAY7E;AAMD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA6BlF"}
1
+ {"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../src/voice/report.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,YAAY,CAAC;IACtB,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,WAAW,EAAE,WAAW,CAAC;IACzB,eAAe,EAAE,OAAO,CAAC;IACzB,wDAAwD;IACxD,SAAS,EAAE,MAAM,CAAC;IAClB,wGAAwG;IACxG,SAAS,EAAE,MAAM,CAAC;IAClB,kEAAkE;IAClE,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,gEAAgE;IAChE,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,YAAY,CAAC;IACtB,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,WAAW,EAAE,WAAW,CAAC;IACzB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,wDAAwD;IACxD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mEAAmE;IACnE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kEAAkE;IAClE,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,gEAAgE;IAChE,uBAAuB,CAAC,EAAE,MAAM,CAAC;CAClC;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,oBAAoB,GAAG,aAAa,CAmB7E;AAMD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA6BlF"}
@@ -1,4 +1,6 @@
1
1
  function createSessionReport(opts) {
2
+ const timestamp = opts.timestamp ?? Date.now();
3
+ const audioRecordingStartedAt = opts.audioRecordingStartedAt;
2
4
  return {
3
5
  jobId: opts.jobId,
4
6
  roomId: opts.roomId,
@@ -6,9 +8,12 @@ function createSessionReport(opts) {
6
8
  options: opts.options,
7
9
  events: opts.events,
8
10
  chatHistory: opts.chatHistory,
9
- enableRecording: opts.enableUserDataTraining ?? false,
11
+ enableRecording: opts.enableRecording ?? false,
10
12
  startedAt: opts.startedAt ?? Date.now(),
11
- timestamp: opts.timestamp ?? Date.now()
13
+ timestamp,
14
+ audioRecordingPath: opts.audioRecordingPath,
15
+ audioRecordingStartedAt,
16
+ duration: audioRecordingStartedAt !== void 0 ? timestamp - audioRecordingStartedAt : void 0
12
17
  };
13
18
  }
14
19
  function sessionReportToJSON(report) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/voice/report.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { ChatContext } from '../llm/chat_context.js';\nimport type { VoiceOptions } from './agent_session.js';\nimport type { AgentEvent } from './events.js';\n\nexport interface SessionReport {\n jobId: string;\n roomId: string;\n room: string;\n options: VoiceOptions;\n events: AgentEvent[];\n chatHistory: ChatContext;\n enableRecording: boolean;\n /** Timestamp when the session started (milliseconds) */\n startedAt: number;\n /** Timestamp when the session report was created (milliseconds), typically at the end of the session */\n timestamp: number;\n}\n\nexport interface SessionReportOptions {\n jobId: string;\n roomId: string;\n room: string;\n options: VoiceOptions;\n events: AgentEvent[];\n chatHistory: ChatContext;\n enableUserDataTraining?: boolean;\n /** Timestamp when the session started (milliseconds) */\n startedAt?: number;\n /** Timestamp when the session report was created (milliseconds) */\n timestamp?: number;\n}\n\nexport function createSessionReport(opts: SessionReportOptions): SessionReport {\n return {\n jobId: opts.jobId,\n roomId: opts.roomId,\n room: opts.room,\n options: opts.options,\n events: opts.events,\n chatHistory: opts.chatHistory,\n enableRecording: opts.enableUserDataTraining ?? false,\n startedAt: opts.startedAt ?? Date.now(),\n timestamp: opts.timestamp ?? Date.now(),\n };\n}\n\n// - header: protobuf MetricsRecordingHeader (room_id, duration, start_time)\n// - chat_history: JSON serialized chat history (use sessionReportToJSON)\n// - audio: audio recording file if available (ogg format)\n// - Uploads to LiveKit Cloud observability endpoint with JWT auth\nexport function sessionReportToJSON(report: SessionReport): Record<string, unknown> {\n const events: Record<string, unknown>[] = [];\n\n for (const event of report.events) {\n if (event.type === 'metrics_collected') {\n continue; // metrics are too noisy, Cloud is using the chat_history as the source of truth\n }\n\n events.push({ ...event });\n }\n\n return {\n job_id: report.jobId,\n room_id: report.roomId,\n room: report.room,\n events,\n options: {\n allow_interruptions: report.options.allowInterruptions,\n discard_audio_if_uninterruptible: report.options.discardAudioIfUninterruptible,\n min_interruption_duration: report.options.minInterruptionDuration,\n min_interruption_words: report.options.minInterruptionWords,\n min_endpointing_delay: report.options.minEndpointingDelay,\n max_endpointing_delay: report.options.maxEndpointingDelay,\n max_tool_steps: report.options.maxToolSteps,\n },\n chat_history: report.chatHistory.toJSON({ excludeTimestamp: false }),\n enable_user_data_training: report.enableRecording,\n timestamp: report.timestamp,\n };\n}\n"],"mappings":"AAmCO,SAAS,oBAAoB,MAA2C;AAC7E,SAAO;AAAA,IACL,OAAO,KAAK;AAAA,IACZ,QAAQ,KAAK;AAAA,IACb,MAAM,KAAK;AAAA,IACX,SAAS,KAAK;AAAA,IACd,QAAQ,KAAK;AAAA,IACb,aAAa,KAAK;AAAA,IAClB,iBAAiB,KAAK,0BAA0B;AAAA,IAChD,WAAW,KAAK,aAAa,KAAK,IAAI;AAAA,IACtC,WAAW,KAAK,aAAa,KAAK,IAAI;AAAA,EACxC;AACF;AAMO,SAAS,oBAAoB,QAAgD;AAClF,QAAM,SAAoC,CAAC;AAE3C,aAAW,SAAS,OAAO,QAAQ;AACjC,QAAI,MAAM,SAAS,qBAAqB;AACtC;AAAA,IACF;AAEA,WAAO,KAAK,EAAE,GAAG,MAAM,CAAC;AAAA,EAC1B;AAEA,SAAO;AAAA,IACL,QAAQ,OAAO;AAAA,IACf,SAAS,OAAO;AAAA,IAChB,MAAM,OAAO;AAAA,IACb;AAAA,IACA,SAAS;AAAA,MACP,qBAAqB,OAAO,QAAQ;AAAA,MACpC,kCAAkC,OAAO,QAAQ;AAAA,MACjD,2BAA2B,OAAO,QAAQ;AAAA,MAC1C,wBAAwB,OAAO,QAAQ;AAAA,MACvC,uBAAuB,OAAO,QAAQ;AAAA,MACtC,uBAAuB,OAAO,QAAQ;AAAA,MACtC,gBAAgB,OAAO,QAAQ;AAAA,IACjC;AAAA,IACA,cAAc,OAAO,YAAY,OAAO,EAAE,kBAAkB,MAAM,CAAC;AAAA,IACnE,2BAA2B,OAAO;AAAA,IAClC,WAAW,OAAO;AAAA,EACpB;AACF;","names":[]}
1
+ {"version":3,"sources":["../../src/voice/report.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { ChatContext } from '../llm/chat_context.js';\nimport type { VoiceOptions } from './agent_session.js';\nimport type { AgentEvent } from './events.js';\n\nexport interface SessionReport {\n jobId: string;\n roomId: string;\n room: string;\n options: VoiceOptions;\n events: AgentEvent[];\n chatHistory: ChatContext;\n enableRecording: boolean;\n /** Timestamp when the session started (milliseconds) */\n startedAt: number;\n /** Timestamp when the session report was created (milliseconds), typically at the end of the session */\n timestamp: number;\n /** Path to the audio recording file (if recording was enabled) */\n audioRecordingPath?: string;\n /** Timestamp when the audio recording started (milliseconds) */\n audioRecordingStartedAt?: number;\n /** Duration of the session in milliseconds */\n duration?: number;\n}\n\nexport interface SessionReportOptions {\n jobId: string;\n roomId: string;\n room: string;\n options: VoiceOptions;\n events: AgentEvent[];\n chatHistory: ChatContext;\n enableRecording?: boolean;\n /** Timestamp when the session started (milliseconds) */\n startedAt?: number;\n /** Timestamp when the session report was created (milliseconds) */\n timestamp?: number;\n /** Path to the audio recording file (if recording was enabled) */\n audioRecordingPath?: string;\n /** Timestamp when the audio recording started (milliseconds) */\n audioRecordingStartedAt?: number;\n}\n\nexport function createSessionReport(opts: SessionReportOptions): SessionReport {\n const timestamp = opts.timestamp ?? Date.now();\n const audioRecordingStartedAt = opts.audioRecordingStartedAt;\n\n return {\n jobId: opts.jobId,\n roomId: opts.roomId,\n room: opts.room,\n options: opts.options,\n events: opts.events,\n chatHistory: opts.chatHistory,\n enableRecording: opts.enableRecording ?? false,\n startedAt: opts.startedAt ?? Date.now(),\n timestamp,\n audioRecordingPath: opts.audioRecordingPath,\n audioRecordingStartedAt,\n duration:\n audioRecordingStartedAt !== undefined ? timestamp - audioRecordingStartedAt : undefined,\n };\n}\n\n// - header: protobuf MetricsRecordingHeader (room_id, duration, start_time)\n// - chat_history: JSON serialized chat history (use sessionReportToJSON)\n// - audio: audio recording file if available (ogg format)\n// - Uploads to LiveKit Cloud observability endpoint with JWT auth\nexport function sessionReportToJSON(report: SessionReport): Record<string, unknown> {\n const events: Record<string, unknown>[] = [];\n\n for (const event of report.events) {\n if (event.type === 'metrics_collected') {\n continue; // metrics are too noisy, Cloud is using the chat_history as the source of truth\n }\n\n events.push({ ...event });\n }\n\n return {\n job_id: report.jobId,\n room_id: report.roomId,\n room: report.room,\n events,\n options: {\n allow_interruptions: report.options.allowInterruptions,\n discard_audio_if_uninterruptible: report.options.discardAudioIfUninterruptible,\n min_interruption_duration: report.options.minInterruptionDuration,\n min_interruption_words: report.options.minInterruptionWords,\n min_endpointing_delay: report.options.minEndpointingDelay,\n max_endpointing_delay: report.options.maxEndpointingDelay,\n max_tool_steps: report.options.maxToolSteps,\n },\n chat_history: report.chatHistory.toJSON({ excludeTimestamp: false }),\n enable_user_data_training: report.enableRecording,\n timestamp: report.timestamp,\n };\n}\n"],"mappings":"AA6CO,SAAS,oBAAoB,MAA2C;AAC7E,QAAM,YAAY,KAAK,aAAa,KAAK,IAAI;AAC7C,QAAM,0BAA0B,KAAK;AAErC,SAAO;AAAA,IACL,OAAO,KAAK;AAAA,IACZ,QAAQ,KAAK;AAAA,IACb,MAAM,KAAK;AAAA,IACX,SAAS,KAAK;AAAA,IACd,QAAQ,KAAK;AAAA,IACb,aAAa,KAAK;AAAA,IAClB,iBAAiB,KAAK,mBAAmB;AAAA,IACzC,WAAW,KAAK,aAAa,KAAK,IAAI;AAAA,IACtC;AAAA,IACA,oBAAoB,KAAK;AAAA,IACzB;AAAA,IACA,UACE,4BAA4B,SAAY,YAAY,0BAA0B;AAAA,EAClF;AACF;AAMO,SAAS,oBAAoB,QAAgD;AAClF,QAAM,SAAoC,CAAC;AAE3C,aAAW,SAAS,OAAO,QAAQ;AACjC,QAAI,MAAM,SAAS,qBAAqB;AACtC;AAAA,IACF;AAEA,WAAO,KAAK,EAAE,GAAG,MAAM,CAAC;AAAA,EAC1B;AAEA,SAAO;AAAA,IACL,QAAQ,OAAO;AAAA,IACf,SAAS,OAAO;AAAA,IAChB,MAAM,OAAO;AAAA,IACb;AAAA,IACA,SAAS;AAAA,MACP,qBAAqB,OAAO,QAAQ;AAAA,MACpC,kCAAkC,OAAO,QAAQ;AAAA,MACjD,2BAA2B,OAAO,QAAQ;AAAA,MAC1C,wBAAwB,OAAO,QAAQ;AAAA,MACvC,uBAAuB,OAAO,QAAQ;AAAA,MACtC,uBAAuB,OAAO,QAAQ;AAAA,MACtC,gBAAgB,OAAO,QAAQ;AAAA,IACjC;AAAA,IACA,cAAc,OAAO,YAAY,OAAO,EAAE,kBAAkB,MAAM,CAAC;AAAA,IACnE,2BAA2B,OAAO;AAAA,IAClC,WAAW,OAAO;AAAA,EACpB;AACF;","names":[]}
@@ -122,7 +122,8 @@ class ParticipantAudioInputStream extends import_io.AudioInput {
122
122
  this.room.off(import_rtc_node.RoomEvent.TrackSubscribed, this.onTrackSubscribed);
123
123
  this.room.off(import_rtc_node.RoomEvent.TrackUnpublished, this.onTrackUnpublished);
124
124
  this.closeStream();
125
- this.deferredStream.stream.cancel();
125
+ await this.deferredStream.stream.cancel().catch(() => {
126
+ });
126
127
  }
127
128
  }
128
129
  // Annotate the CommonJS export names for ESM import in node:
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/voice/room_io/_input.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { AudioFrame } from '@livekit/rtc-node';\nimport {\n AudioStream,\n type NoiseCancellationOptions,\n RemoteParticipant,\n type RemoteTrack,\n type RemoteTrackPublication,\n type Room,\n RoomEvent,\n TrackSource,\n} from '@livekit/rtc-node';\nimport type { ReadableStream } from 'node:stream/web';\nimport { log } from '../../log.js';\nimport { resampleStream } from '../../utils.js';\nimport { AudioInput } from '../io.js';\n\nexport class ParticipantAudioInputStream extends AudioInput {\n private room: Room;\n private sampleRate: number;\n private numChannels: number;\n private noiseCancellation?: NoiseCancellationOptions;\n private publication: RemoteTrackPublication | null = null;\n private participantIdentity: string | null = null;\n private logger = log();\n constructor({\n room,\n sampleRate,\n numChannels,\n noiseCancellation,\n }: {\n room: Room;\n sampleRate: number;\n numChannels: number;\n noiseCancellation?: NoiseCancellationOptions;\n }) {\n super();\n this.room = room;\n this.sampleRate = sampleRate;\n this.numChannels = numChannels;\n this.noiseCancellation = noiseCancellation;\n\n this.room.on(RoomEvent.TrackSubscribed, this.onTrackSubscribed);\n this.room.on(RoomEvent.TrackUnpublished, this.onTrackUnpublished);\n }\n\n setParticipant(participant: RemoteParticipant | string | null) {\n this.logger.debug({ participant }, 'setting participant audio input');\n const participantIdentity =\n participant instanceof RemoteParticipant ? participant.identity : participant;\n\n if (this.participantIdentity === participantIdentity) {\n return;\n }\n this.participantIdentity = participantIdentity;\n this.closeStream();\n\n if (!participantIdentity) {\n return;\n }\n\n const participantValue =\n participant instanceof RemoteParticipant\n ? participant\n : this.room.remoteParticipants.get(participantIdentity);\n\n // Convert Map iterator to array for Pino serialization\n const trackPublicationsArray = Array.from(participantValue?.trackPublications.values() ?? []);\n\n this.logger.info(\n {\n participantValue: participantValue?.identity,\n trackPublications: trackPublicationsArray,\n lengthOfTrackPublications: trackPublicationsArray.length,\n },\n 'participantValue.trackPublications',\n );\n // We need to check if the participant has a microphone track and subscribe to it\n // in case we miss the tracksubscribed event\n if (participantValue) {\n for (const publication of participantValue.trackPublications.values()) {\n if (publication.track && publication.source === TrackSource.SOURCE_MICROPHONE) {\n this.onTrackSubscribed(publication.track, publication, participantValue);\n break;\n }\n }\n }\n }\n\n private onTrackUnpublished = (\n publication: RemoteTrackPublication,\n participant: RemoteParticipant,\n ) => {\n if (\n this.publication?.sid !== publication.sid ||\n participant.identity !== this.participantIdentity\n ) {\n return;\n }\n this.closeStream();\n\n // subscribe to the first available track\n for (const publication of participant.trackPublications.values()) {\n if (\n publication.track &&\n this.onTrackSubscribed(publication.track, publication, participant)\n ) {\n return;\n }\n }\n };\n\n private closeStream() {\n if (this.deferredStream.isSourceSet) {\n this.deferredStream.detachSource();\n }\n this.publication = null;\n }\n\n private onTrackSubscribed = (\n track: RemoteTrack,\n publication: RemoteTrackPublication,\n participant: RemoteParticipant,\n ): boolean => {\n this.logger.debug({ participant: participant.identity }, 'onTrackSubscribed in _input');\n if (\n this.participantIdentity !== participant.identity ||\n publication.source !== TrackSource.SOURCE_MICROPHONE ||\n (this.publication && this.publication.sid === publication.sid)\n ) {\n return false;\n }\n this.closeStream();\n this.publication = publication;\n this.deferredStream.setSource(\n resampleStream({\n stream: this.createStream(track),\n outputRate: this.sampleRate,\n }),\n );\n return true;\n };\n\n private createStream(track: RemoteTrack): ReadableStream<AudioFrame> {\n return new AudioStream(track, {\n sampleRate: this.sampleRate,\n numChannels: this.numChannels,\n noiseCancellation: this.noiseCancellation,\n // TODO(AJS-269): resolve compatibility issue with node-sdk to remove the forced type casting\n }) as unknown as ReadableStream<AudioFrame>;\n }\n\n async close() {\n this.room.off(RoomEvent.TrackSubscribed, this.onTrackSubscribed);\n this.room.off(RoomEvent.TrackUnpublished, this.onTrackUnpublished);\n this.closeStream();\n this.deferredStream.stream.cancel();\n }\n}\n"],"mappings":"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","names":["publication"]}
1
+ {"version":3,"sources":["../../../src/voice/room_io/_input.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { AudioFrame } from '@livekit/rtc-node';\nimport {\n AudioStream,\n type NoiseCancellationOptions,\n RemoteParticipant,\n type RemoteTrack,\n type RemoteTrackPublication,\n type Room,\n RoomEvent,\n TrackSource,\n} from '@livekit/rtc-node';\nimport type { ReadableStream } from 'node:stream/web';\nimport { log } from '../../log.js';\nimport { resampleStream } from '../../utils.js';\nimport { AudioInput } from '../io.js';\n\nexport class ParticipantAudioInputStream extends AudioInput {\n private room: Room;\n private sampleRate: number;\n private numChannels: number;\n private noiseCancellation?: NoiseCancellationOptions;\n private publication: RemoteTrackPublication | null = null;\n private participantIdentity: string | null = null;\n private logger = log();\n constructor({\n room,\n sampleRate,\n numChannels,\n noiseCancellation,\n }: {\n room: Room;\n sampleRate: number;\n numChannels: number;\n noiseCancellation?: NoiseCancellationOptions;\n }) {\n super();\n this.room = room;\n this.sampleRate = sampleRate;\n this.numChannels = numChannels;\n this.noiseCancellation = noiseCancellation;\n\n this.room.on(RoomEvent.TrackSubscribed, this.onTrackSubscribed);\n this.room.on(RoomEvent.TrackUnpublished, this.onTrackUnpublished);\n }\n\n setParticipant(participant: RemoteParticipant | string | null) {\n this.logger.debug({ participant }, 'setting participant audio input');\n const participantIdentity =\n participant instanceof RemoteParticipant ? participant.identity : participant;\n\n if (this.participantIdentity === participantIdentity) {\n return;\n }\n this.participantIdentity = participantIdentity;\n this.closeStream();\n\n if (!participantIdentity) {\n return;\n }\n\n const participantValue =\n participant instanceof RemoteParticipant\n ? participant\n : this.room.remoteParticipants.get(participantIdentity);\n\n // Convert Map iterator to array for Pino serialization\n const trackPublicationsArray = Array.from(participantValue?.trackPublications.values() ?? []);\n\n this.logger.info(\n {\n participantValue: participantValue?.identity,\n trackPublications: trackPublicationsArray,\n lengthOfTrackPublications: trackPublicationsArray.length,\n },\n 'participantValue.trackPublications',\n );\n // We need to check if the participant has a microphone track and subscribe to it\n // in case we miss the tracksubscribed event\n if (participantValue) {\n for (const publication of participantValue.trackPublications.values()) {\n if (publication.track && publication.source === TrackSource.SOURCE_MICROPHONE) {\n this.onTrackSubscribed(publication.track, publication, participantValue);\n break;\n }\n }\n }\n }\n\n private onTrackUnpublished = (\n publication: RemoteTrackPublication,\n participant: RemoteParticipant,\n ) => {\n if (\n this.publication?.sid !== publication.sid ||\n participant.identity !== this.participantIdentity\n ) {\n return;\n }\n this.closeStream();\n\n // subscribe to the first available track\n for (const publication of participant.trackPublications.values()) {\n if (\n publication.track &&\n this.onTrackSubscribed(publication.track, publication, participant)\n ) {\n return;\n }\n }\n };\n\n private closeStream() {\n if (this.deferredStream.isSourceSet) {\n this.deferredStream.detachSource();\n }\n this.publication = null;\n }\n\n private onTrackSubscribed = (\n track: RemoteTrack,\n publication: RemoteTrackPublication,\n participant: RemoteParticipant,\n ): boolean => {\n this.logger.debug({ participant: participant.identity }, 'onTrackSubscribed in _input');\n if (\n this.participantIdentity !== participant.identity ||\n publication.source !== TrackSource.SOURCE_MICROPHONE ||\n (this.publication && this.publication.sid === publication.sid)\n ) {\n return false;\n }\n this.closeStream();\n this.publication = publication;\n this.deferredStream.setSource(\n resampleStream({\n stream: this.createStream(track),\n outputRate: this.sampleRate,\n }),\n );\n return true;\n };\n\n private createStream(track: RemoteTrack): ReadableStream<AudioFrame> {\n return new AudioStream(track, {\n sampleRate: this.sampleRate,\n numChannels: this.numChannels,\n noiseCancellation: this.noiseCancellation,\n // TODO(AJS-269): resolve compatibility issue with node-sdk to remove the forced type casting\n }) as unknown as ReadableStream<AudioFrame>;\n }\n\n async close() {\n this.room.off(RoomEvent.TrackSubscribed, this.onTrackSubscribed);\n this.room.off(RoomEvent.TrackUnpublished, this.onTrackUnpublished);\n this.closeStream();\n // Ignore errors - stream may be locked by RecorderIO or already cancelled\n await this.deferredStream.stream.cancel().catch(() => {});\n }\n}\n"],"mappings":"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","names":["publication"]}
@@ -1 +1 @@
1
- {"version":3,"file":"_input.d.ts","sourceRoot":"","sources":["../../../src/voice/room_io/_input.ts"],"names":[],"mappings":"AAIA,OAAO,EAEL,KAAK,wBAAwB,EAC7B,iBAAiB,EAGjB,KAAK,IAAI,EAGV,MAAM,mBAAmB,CAAC;AAI3B,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAEtC,qBAAa,2BAA4B,SAAQ,UAAU;IACzD,OAAO,CAAC,IAAI,CAAO;IACnB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,iBAAiB,CAAC,CAA2B;IACrD,OAAO,CAAC,WAAW,CAAuC;IAC1D,OAAO,CAAC,mBAAmB,CAAuB;IAClD,OAAO,CAAC,MAAM,CAAS;gBACX,EACV,IAAI,EACJ,UAAU,EACV,WAAW,EACX,iBAAiB,GAClB,EAAE;QACD,IAAI,EAAE,IAAI,CAAC;QACX,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC;QACpB,iBAAiB,CAAC,EAAE,wBAAwB,CAAC;KAC9C;IAWD,cAAc,CAAC,WAAW,EAAE,iBAAiB,GAAG,MAAM,GAAG,IAAI;IA2C7D,OAAO,CAAC,kBAAkB,CAqBxB;IAEF,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,iBAAiB,CAsBvB;IAEF,OAAO,CAAC,YAAY;IASd,KAAK;CAMZ"}
1
+ {"version":3,"file":"_input.d.ts","sourceRoot":"","sources":["../../../src/voice/room_io/_input.ts"],"names":[],"mappings":"AAIA,OAAO,EAEL,KAAK,wBAAwB,EAC7B,iBAAiB,EAGjB,KAAK,IAAI,EAGV,MAAM,mBAAmB,CAAC;AAI3B,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAEtC,qBAAa,2BAA4B,SAAQ,UAAU;IACzD,OAAO,CAAC,IAAI,CAAO;IACnB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,iBAAiB,CAAC,CAA2B;IACrD,OAAO,CAAC,WAAW,CAAuC;IAC1D,OAAO,CAAC,mBAAmB,CAAuB;IAClD,OAAO,CAAC,MAAM,CAAS;gBACX,EACV,IAAI,EACJ,UAAU,EACV,WAAW,EACX,iBAAiB,GAClB,EAAE;QACD,IAAI,EAAE,IAAI,CAAC;QACX,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC;QACpB,iBAAiB,CAAC,EAAE,wBAAwB,CAAC;KAC9C;IAWD,cAAc,CAAC,WAAW,EAAE,iBAAiB,GAAG,MAAM,GAAG,IAAI;IA2C7D,OAAO,CAAC,kBAAkB,CAqBxB;IAEF,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,iBAAiB,CAsBvB;IAEF,OAAO,CAAC,YAAY;IASd,KAAK;CAOZ"}
@@ -104,7 +104,8 @@ class ParticipantAudioInputStream extends AudioInput {
104
104
  this.room.off(RoomEvent.TrackSubscribed, this.onTrackSubscribed);
105
105
  this.room.off(RoomEvent.TrackUnpublished, this.onTrackUnpublished);
106
106
  this.closeStream();
107
- this.deferredStream.stream.cancel();
107
+ await this.deferredStream.stream.cancel().catch(() => {
108
+ });
108
109
  }
109
110
  }
110
111
  export {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/voice/room_io/_input.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { AudioFrame } from '@livekit/rtc-node';\nimport {\n AudioStream,\n type NoiseCancellationOptions,\n RemoteParticipant,\n type RemoteTrack,\n type RemoteTrackPublication,\n type Room,\n RoomEvent,\n TrackSource,\n} from '@livekit/rtc-node';\nimport type { ReadableStream } from 'node:stream/web';\nimport { log } from '../../log.js';\nimport { resampleStream } from '../../utils.js';\nimport { AudioInput } from '../io.js';\n\nexport class ParticipantAudioInputStream extends AudioInput {\n private room: Room;\n private sampleRate: number;\n private numChannels: number;\n private noiseCancellation?: NoiseCancellationOptions;\n private publication: RemoteTrackPublication | null = null;\n private participantIdentity: string | null = null;\n private logger = log();\n constructor({\n room,\n sampleRate,\n numChannels,\n noiseCancellation,\n }: {\n room: Room;\n sampleRate: number;\n numChannels: number;\n noiseCancellation?: NoiseCancellationOptions;\n }) {\n super();\n this.room = room;\n this.sampleRate = sampleRate;\n this.numChannels = numChannels;\n this.noiseCancellation = noiseCancellation;\n\n this.room.on(RoomEvent.TrackSubscribed, this.onTrackSubscribed);\n this.room.on(RoomEvent.TrackUnpublished, this.onTrackUnpublished);\n }\n\n setParticipant(participant: RemoteParticipant | string | null) {\n this.logger.debug({ participant }, 'setting participant audio input');\n const participantIdentity =\n participant instanceof RemoteParticipant ? participant.identity : participant;\n\n if (this.participantIdentity === participantIdentity) {\n return;\n }\n this.participantIdentity = participantIdentity;\n this.closeStream();\n\n if (!participantIdentity) {\n return;\n }\n\n const participantValue =\n participant instanceof RemoteParticipant\n ? participant\n : this.room.remoteParticipants.get(participantIdentity);\n\n // Convert Map iterator to array for Pino serialization\n const trackPublicationsArray = Array.from(participantValue?.trackPublications.values() ?? []);\n\n this.logger.info(\n {\n participantValue: participantValue?.identity,\n trackPublications: trackPublicationsArray,\n lengthOfTrackPublications: trackPublicationsArray.length,\n },\n 'participantValue.trackPublications',\n );\n // We need to check if the participant has a microphone track and subscribe to it\n // in case we miss the tracksubscribed event\n if (participantValue) {\n for (const publication of participantValue.trackPublications.values()) {\n if (publication.track && publication.source === TrackSource.SOURCE_MICROPHONE) {\n this.onTrackSubscribed(publication.track, publication, participantValue);\n break;\n }\n }\n }\n }\n\n private onTrackUnpublished = (\n publication: RemoteTrackPublication,\n participant: RemoteParticipant,\n ) => {\n if (\n this.publication?.sid !== publication.sid ||\n participant.identity !== this.participantIdentity\n ) {\n return;\n }\n this.closeStream();\n\n // subscribe to the first available track\n for (const publication of participant.trackPublications.values()) {\n if (\n publication.track &&\n this.onTrackSubscribed(publication.track, publication, participant)\n ) {\n return;\n }\n }\n };\n\n private closeStream() {\n if (this.deferredStream.isSourceSet) {\n this.deferredStream.detachSource();\n }\n this.publication = null;\n }\n\n private onTrackSubscribed = (\n track: RemoteTrack,\n publication: RemoteTrackPublication,\n participant: RemoteParticipant,\n ): boolean => {\n this.logger.debug({ participant: participant.identity }, 'onTrackSubscribed in _input');\n if (\n this.participantIdentity !== participant.identity ||\n publication.source !== TrackSource.SOURCE_MICROPHONE ||\n (this.publication && this.publication.sid === publication.sid)\n ) {\n return false;\n }\n this.closeStream();\n this.publication = publication;\n this.deferredStream.setSource(\n resampleStream({\n stream: this.createStream(track),\n outputRate: this.sampleRate,\n }),\n );\n return true;\n };\n\n private createStream(track: RemoteTrack): ReadableStream<AudioFrame> {\n return new AudioStream(track, {\n sampleRate: this.sampleRate,\n numChannels: this.numChannels,\n noiseCancellation: this.noiseCancellation,\n // TODO(AJS-269): resolve compatibility issue with node-sdk to remove the forced type casting\n }) as unknown as ReadableStream<AudioFrame>;\n }\n\n async close() {\n this.room.off(RoomEvent.TrackSubscribed, this.onTrackSubscribed);\n this.room.off(RoomEvent.TrackUnpublished, this.onTrackUnpublished);\n this.closeStream();\n this.deferredStream.stream.cancel();\n }\n}\n"],"mappings":"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","names":["publication"]}
1
+ {"version":3,"sources":["../../../src/voice/room_io/_input.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { AudioFrame } from '@livekit/rtc-node';\nimport {\n AudioStream,\n type NoiseCancellationOptions,\n RemoteParticipant,\n type RemoteTrack,\n type RemoteTrackPublication,\n type Room,\n RoomEvent,\n TrackSource,\n} from '@livekit/rtc-node';\nimport type { ReadableStream } from 'node:stream/web';\nimport { log } from '../../log.js';\nimport { resampleStream } from '../../utils.js';\nimport { AudioInput } from '../io.js';\n\nexport class ParticipantAudioInputStream extends AudioInput {\n private room: Room;\n private sampleRate: number;\n private numChannels: number;\n private noiseCancellation?: NoiseCancellationOptions;\n private publication: RemoteTrackPublication | null = null;\n private participantIdentity: string | null = null;\n private logger = log();\n constructor({\n room,\n sampleRate,\n numChannels,\n noiseCancellation,\n }: {\n room: Room;\n sampleRate: number;\n numChannels: number;\n noiseCancellation?: NoiseCancellationOptions;\n }) {\n super();\n this.room = room;\n this.sampleRate = sampleRate;\n this.numChannels = numChannels;\n this.noiseCancellation = noiseCancellation;\n\n this.room.on(RoomEvent.TrackSubscribed, this.onTrackSubscribed);\n this.room.on(RoomEvent.TrackUnpublished, this.onTrackUnpublished);\n }\n\n setParticipant(participant: RemoteParticipant | string | null) {\n this.logger.debug({ participant }, 'setting participant audio input');\n const participantIdentity =\n participant instanceof RemoteParticipant ? participant.identity : participant;\n\n if (this.participantIdentity === participantIdentity) {\n return;\n }\n this.participantIdentity = participantIdentity;\n this.closeStream();\n\n if (!participantIdentity) {\n return;\n }\n\n const participantValue =\n participant instanceof RemoteParticipant\n ? participant\n : this.room.remoteParticipants.get(participantIdentity);\n\n // Convert Map iterator to array for Pino serialization\n const trackPublicationsArray = Array.from(participantValue?.trackPublications.values() ?? []);\n\n this.logger.info(\n {\n participantValue: participantValue?.identity,\n trackPublications: trackPublicationsArray,\n lengthOfTrackPublications: trackPublicationsArray.length,\n },\n 'participantValue.trackPublications',\n );\n // We need to check if the participant has a microphone track and subscribe to it\n // in case we miss the tracksubscribed event\n if (participantValue) {\n for (const publication of participantValue.trackPublications.values()) {\n if (publication.track && publication.source === TrackSource.SOURCE_MICROPHONE) {\n this.onTrackSubscribed(publication.track, publication, participantValue);\n break;\n }\n }\n }\n }\n\n private onTrackUnpublished = (\n publication: RemoteTrackPublication,\n participant: RemoteParticipant,\n ) => {\n if (\n this.publication?.sid !== publication.sid ||\n participant.identity !== this.participantIdentity\n ) {\n return;\n }\n this.closeStream();\n\n // subscribe to the first available track\n for (const publication of participant.trackPublications.values()) {\n if (\n publication.track &&\n this.onTrackSubscribed(publication.track, publication, participant)\n ) {\n return;\n }\n }\n };\n\n private closeStream() {\n if (this.deferredStream.isSourceSet) {\n this.deferredStream.detachSource();\n }\n this.publication = null;\n }\n\n private onTrackSubscribed = (\n track: RemoteTrack,\n publication: RemoteTrackPublication,\n participant: RemoteParticipant,\n ): boolean => {\n this.logger.debug({ participant: participant.identity }, 'onTrackSubscribed in _input');\n if (\n this.participantIdentity !== participant.identity ||\n publication.source !== TrackSource.SOURCE_MICROPHONE ||\n (this.publication && this.publication.sid === publication.sid)\n ) {\n return false;\n }\n this.closeStream();\n this.publication = publication;\n this.deferredStream.setSource(\n resampleStream({\n stream: this.createStream(track),\n outputRate: this.sampleRate,\n }),\n );\n return true;\n };\n\n private createStream(track: RemoteTrack): ReadableStream<AudioFrame> {\n return new AudioStream(track, {\n sampleRate: this.sampleRate,\n numChannels: this.numChannels,\n noiseCancellation: this.noiseCancellation,\n // TODO(AJS-269): resolve compatibility issue with node-sdk to remove the forced type casting\n }) as unknown as ReadableStream<AudioFrame>;\n }\n\n async close() {\n this.room.off(RoomEvent.TrackSubscribed, this.onTrackSubscribed);\n this.room.off(RoomEvent.TrackUnpublished, this.onTrackUnpublished);\n this.closeStream();\n // Ignore errors - stream may be locked by RecorderIO or already cancelled\n await this.deferredStream.stream.cancel().catch(() => {});\n }\n}\n"],"mappings":"AAIA;AAAA,EACE;AAAA,EAEA;AAAA,EAIA;AAAA,EACA;AAAA,OACK;AAEP,SAAS,WAAW;AACpB,SAAS,sBAAsB;AAC/B,SAAS,kBAAkB;AAEpB,MAAM,oCAAoC,WAAW;AAAA,EAClD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,cAA6C;AAAA,EAC7C,sBAAqC;AAAA,EACrC,SAAS,IAAI;AAAA,EACrB,YAAY;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,GAKG;AACD,UAAM;AACN,SAAK,OAAO;AACZ,SAAK,aAAa;AAClB,SAAK,cAAc;AACnB,SAAK,oBAAoB;AAEzB,SAAK,KAAK,GAAG,UAAU,iBAAiB,KAAK,iBAAiB;AAC9D,SAAK,KAAK,GAAG,UAAU,kBAAkB,KAAK,kBAAkB;AAAA,EAClE;AAAA,EAEA,eAAe,aAAgD;AAC7D,SAAK,OAAO,MAAM,EAAE,YAAY,GAAG,iCAAiC;AACpE,UAAM,sBACJ,uBAAuB,oBAAoB,YAAY,WAAW;AAEpE,QAAI,KAAK,wBAAwB,qBAAqB;AACpD;AAAA,IACF;AACA,SAAK,sBAAsB;AAC3B,SAAK,YAAY;AAEjB,QAAI,CAAC,qBAAqB;AACxB;AAAA,IACF;AAEA,UAAM,mBACJ,uBAAuB,oBACnB,cACA,KAAK,KAAK,mBAAmB,IAAI,mBAAmB;AAG1D,UAAM,yBAAyB,MAAM,MAAK,qDAAkB,kBAAkB,aAAY,CAAC,CAAC;AAE5F,SAAK,OAAO;AAAA,MACV;AAAA,QACE,kBAAkB,qDAAkB;AAAA,QACpC,mBAAmB;AAAA,QACnB,2BAA2B,uBAAuB;AAAA,MACpD;AAAA,MACA;AAAA,IACF;AAGA,QAAI,kBAAkB;AACpB,iBAAW,eAAe,iBAAiB,kBAAkB,OAAO,GAAG;AACrE,YAAI,YAAY,SAAS,YAAY,WAAW,YAAY,mBAAmB;AAC7E,eAAK,kBAAkB,YAAY,OAAO,aAAa,gBAAgB;AACvE;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,qBAAqB,CAC3B,aACA,gBACG;AA9FP;AA+FI,UACE,UAAK,gBAAL,mBAAkB,SAAQ,YAAY,OACtC,YAAY,aAAa,KAAK,qBAC9B;AACA;AAAA,IACF;AACA,SAAK,YAAY;AAGjB,eAAWA,gBAAe,YAAY,kBAAkB,OAAO,GAAG;AAChE,UACEA,aAAY,SACZ,KAAK,kBAAkBA,aAAY,OAAOA,cAAa,WAAW,GAClE;AACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,cAAc;AACpB,QAAI,KAAK,eAAe,aAAa;AACnC,WAAK,eAAe,aAAa;AAAA,IACnC;AACA,SAAK,cAAc;AAAA,EACrB;AAAA,EAEQ,oBAAoB,CAC1B,OACA,aACA,gBACY;AACZ,SAAK,OAAO,MAAM,EAAE,aAAa,YAAY,SAAS,GAAG,6BAA6B;AACtF,QACE,KAAK,wBAAwB,YAAY,YACzC,YAAY,WAAW,YAAY,qBAClC,KAAK,eAAe,KAAK,YAAY,QAAQ,YAAY,KAC1D;AACA,aAAO;AAAA,IACT;AACA,SAAK,YAAY;AACjB,SAAK,cAAc;AACnB,SAAK,eAAe;AAAA,MAClB,eAAe;AAAA,QACb,QAAQ,KAAK,aAAa,KAAK;AAAA,QAC/B,YAAY,KAAK;AAAA,MACnB,CAAC;AAAA,IACH;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,aAAa,OAAgD;AACnE,WAAO,IAAI,YAAY,OAAO;AAAA,MAC5B,YAAY,KAAK;AAAA,MACjB,aAAa,KAAK;AAAA,MAClB,mBAAmB,KAAK;AAAA;AAAA,IAE1B,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,QAAQ;AACZ,SAAK,KAAK,IAAI,UAAU,iBAAiB,KAAK,iBAAiB;AAC/D,SAAK,KAAK,IAAI,UAAU,kBAAkB,KAAK,kBAAkB;AACjE,SAAK,YAAY;AAEjB,UAAM,KAAK,eAAe,OAAO,OAAO,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EAC1D;AACF;","names":["publication"]}
@@ -262,7 +262,8 @@ class ParticipantAudioOutput extends import_io.AudioOutput {
262
262
  audioSource;
263
263
  publication;
264
264
  flushTask;
265
- pushedDurationMs = 0;
265
+ /** Duration of audio pushed to the source, in seconds */
266
+ pushedDuration = 0;
266
267
  startedFuture = new import_utils.Future();
267
268
  interruptedFuture = new import_utils.Future();
268
269
  constructor(room, options) {
@@ -280,7 +281,7 @@ class ParticipantAudioOutput extends import_io.AudioOutput {
280
281
  async captureFrame(frame) {
281
282
  await this.startedFuture.await;
282
283
  super.captureFrame(frame);
283
- this.pushedDurationMs += frame.samplesPerChannel / frame.sampleRate;
284
+ this.pushedDuration += frame.samplesPerChannel / frame.sampleRate;
284
285
  await this.audioSource.captureFrame(frame);
285
286
  }
286
287
  async waitForPlayoutTask(abortController) {
@@ -297,12 +298,12 @@ class ParticipantAudioOutput extends import_io.AudioOutput {
297
298
  abortFuture.await,
298
299
  this.interruptedFuture.await.then(() => true)
299
300
  ]);
300
- let pushedDuration = this.pushedDurationMs;
301
+ let pushedDuration = this.pushedDuration;
301
302
  if (interrupted) {
302
- pushedDuration = Math.max(this.pushedDurationMs - this.audioSource.queuedDuration, 0);
303
+ pushedDuration = Math.max(this.pushedDuration - this.audioSource.queuedDuration / 1e3, 0);
303
304
  this.audioSource.clearQueue();
304
305
  }
305
- this.pushedDurationMs = 0;
306
+ this.pushedDuration = 0;
306
307
  this.interruptedFuture = new import_utils.Future();
307
308
  this.onPlaybackFinished({
308
309
  playbackPosition: pushedDuration,
@@ -314,7 +315,7 @@ class ParticipantAudioOutput extends import_io.AudioOutput {
314
315
  */
315
316
  flush() {
316
317
  super.flush();
317
- if (!this.pushedDurationMs) {
318
+ if (!this.pushedDuration) {
318
319
  return;
319
320
  }
320
321
  if (this.flushTask && !this.flushTask.done) {
@@ -324,7 +325,7 @@ class ParticipantAudioOutput extends import_io.AudioOutput {
324
325
  this.flushTask = import_utils.Task.from((controller) => this.waitForPlayoutTask(controller));
325
326
  }
326
327
  clearBuffer() {
327
- if (!this.pushedDurationMs) {
328
+ if (!this.pushedDuration) {
328
329
  return;
329
330
  }
330
331
  this.interruptedFuture.resolve();
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/voice/room_io/_output.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { RemoteParticipant } from '@livekit/rtc-node';\nimport {\n type AudioFrame,\n AudioSource,\n LocalAudioTrack,\n type LocalTrackPublication,\n type Participant,\n type RemoteTrackPublication,\n type Room,\n RoomEvent,\n type TextStreamWriter,\n TrackPublishOptions,\n TrackSource,\n} from '@livekit/rtc-node';\nimport {\n ATTRIBUTE_TRANSCRIPTION_FINAL,\n ATTRIBUTE_TRANSCRIPTION_SEGMENT_ID,\n ATTRIBUTE_TRANSCRIPTION_TRACK_ID,\n TOPIC_TRANSCRIPTION,\n} from '../../constants.js';\nimport { log } from '../../log.js';\nimport { Future, Task, shortuuid } from '../../utils.js';\nimport { AudioOutput, TextOutput } from '../io.js';\nimport { findMicrophoneTrackId } from '../transcription/index.js';\n\nabstract class BaseParticipantTranscriptionOutput extends TextOutput {\n protected room: Room;\n protected isDeltaStream: boolean;\n protected participantIdentity: string | null = null;\n protected trackId?: string;\n protected capturing: boolean = false;\n protected latestText: string = '';\n protected currentId: string = this.generateCurrentId();\n protected logger = log();\n\n constructor(room: Room, isDeltaStream: boolean, participant: Participant | string | null) {\n super();\n this.room = room;\n this.isDeltaStream = isDeltaStream;\n\n this.room.on(RoomEvent.TrackPublished, this.onTrackPublished);\n this.room.on(RoomEvent.LocalTrackPublished, this.onLocalTrackPublished);\n\n this.setParticipant(participant);\n }\n\n setParticipant(participant: Participant | string | null) {\n if (typeof participant === 'string' || participant === null) {\n this.participantIdentity = participant;\n } else {\n this.participantIdentity = participant.identity;\n }\n\n if (!this.participantIdentity) {\n return;\n }\n\n try {\n this.trackId = findMicrophoneTrackId(this.room, this.participantIdentity);\n } catch (error) {\n // track id is optional for TextStream when audio is not published\n }\n\n this.flush();\n this.resetState();\n }\n\n protected onTrackPublished = (track: RemoteTrackPublication, participant: RemoteParticipant) => {\n if (\n !this.participantIdentity ||\n participant.identity !== this.participantIdentity ||\n track.source !== TrackSource.SOURCE_MICROPHONE\n ) {\n return;\n }\n\n this.trackId = track.sid;\n };\n\n protected onLocalTrackPublished = (track: LocalTrackPublication) => {\n if (\n !this.participantIdentity ||\n this.participantIdentity !== this.room.localParticipant?.identity ||\n track.source !== TrackSource.SOURCE_MICROPHONE\n ) {\n return;\n }\n\n this.trackId = track.sid;\n };\n\n protected generateCurrentId(): string {\n return shortuuid('SG_');\n }\n\n protected resetState() {\n this.currentId = this.generateCurrentId();\n this.capturing = false;\n this.latestText = '';\n }\n\n async captureText(text: string) {\n if (!this.participantIdentity) {\n return;\n }\n\n this.latestText = text;\n await this.handleCaptureText(text);\n }\n\n flush() {\n if (!this.participantIdentity || !this.capturing) {\n return;\n }\n\n this.capturing = false;\n this.handleFlush();\n }\n\n protected abstract handleCaptureText(text: string): Promise<void>;\n protected abstract handleFlush(): void;\n}\n\nexport class ParticipantTranscriptionOutput extends BaseParticipantTranscriptionOutput {\n private writer: TextStreamWriter | null = null;\n private flushTask: Task<void> | null = null;\n\n protected async handleCaptureText(text: string): Promise<void> {\n if (this.flushTask && !this.flushTask.done) {\n await this.flushTask.result;\n }\n\n if (!this.capturing) {\n this.resetState();\n this.capturing = true;\n }\n\n try {\n if (this.room.isConnected) {\n if (this.isDeltaStream) {\n // reuse the existing writer\n if (this.writer === null) {\n this.writer = await this.createTextWriter();\n }\n await this.writer.write(text);\n } else {\n const tmpWriter = await this.createTextWriter();\n await tmpWriter.write(text);\n await tmpWriter.close();\n }\n }\n } catch (error) {\n this.logger.error(error, 'failed to publish transcription');\n }\n }\n\n protected handleFlush() {\n const currWriter = this.writer;\n this.writer = null;\n this.flushTask = Task.from((controller) => this.flushTaskImpl(currWriter, controller.signal));\n }\n\n private async createTextWriter(attributes?: Record<string, string>): Promise<TextStreamWriter> {\n if (!this.participantIdentity) {\n throw new Error('participantIdentity not found');\n }\n\n if (!this.room.localParticipant) {\n throw new Error('localParticipant not found');\n }\n\n if (!attributes) {\n attributes = {\n [ATTRIBUTE_TRANSCRIPTION_FINAL]: 'false',\n };\n if (this.trackId) {\n attributes[ATTRIBUTE_TRANSCRIPTION_TRACK_ID] = this.trackId;\n }\n }\n attributes[ATTRIBUTE_TRANSCRIPTION_SEGMENT_ID] = this.currentId;\n\n return await this.room.localParticipant.streamText({\n topic: TOPIC_TRANSCRIPTION,\n senderIdentity: this.participantIdentity,\n attributes,\n });\n }\n\n private async flushTaskImpl(writer: TextStreamWriter | null, signal: AbortSignal): Promise<void> {\n const attributes: Record<string, string> = {\n [ATTRIBUTE_TRANSCRIPTION_FINAL]: 'true',\n };\n if (this.trackId) {\n attributes[ATTRIBUTE_TRANSCRIPTION_TRACK_ID] = this.trackId;\n }\n\n const abortPromise = new Promise<void>((resolve) => {\n signal.addEventListener('abort', () => resolve());\n });\n\n try {\n if (this.room.isConnected) {\n if (this.isDeltaStream) {\n if (writer) {\n await Promise.race([writer.close(), abortPromise]);\n }\n } else {\n const tmpWriter = await Promise.race([this.createTextWriter(attributes), abortPromise]);\n if (signal.aborted || !tmpWriter) {\n return;\n }\n await Promise.race([tmpWriter.write(this.latestText), abortPromise]);\n if (signal.aborted) {\n return;\n }\n await Promise.race([tmpWriter.close(), abortPromise]);\n }\n }\n } catch (error) {\n this.logger.error(error, 'failed to publish transcription');\n }\n }\n}\n\nexport class ParticipantLegacyTranscriptionOutput extends BaseParticipantTranscriptionOutput {\n private pushedText: string = '';\n private flushTask: Promise<void> | null = null;\n\n protected async handleCaptureText(text: string): Promise<void> {\n if (!this.trackId) {\n return;\n }\n\n if (this.flushTask) {\n await this.flushTask;\n }\n\n if (!this.capturing) {\n this.resetState();\n this.capturing = true;\n }\n\n if (this.isDeltaStream) {\n this.pushedText += text;\n } else {\n this.pushedText = text;\n }\n\n await this.publishTranscription(this.currentId, this.pushedText, false);\n }\n\n protected handleFlush() {\n if (!this.trackId) {\n return;\n }\n\n this.flushTask = this.publishTranscription(this.currentId, this.pushedText, true);\n this.resetState();\n }\n\n async publishTranscription(id: string, text: string, final: boolean, signal?: AbortSignal) {\n if (!this.participantIdentity || !this.trackId) {\n return;\n }\n\n try {\n if (this.room.isConnected) {\n if (signal?.aborted) {\n return;\n }\n\n await this.room.localParticipant?.publishTranscription({\n participantIdentity: this.participantIdentity,\n trackSid: this.trackId,\n segments: [{ id, text, final, startTime: BigInt(0), endTime: BigInt(0), language: '' }],\n });\n }\n } catch (error) {\n this.logger.error(error, 'failed to publish transcription');\n }\n }\n\n protected resetState() {\n super.resetState();\n this.pushedText = '';\n }\n}\n\nexport class ParalellTextOutput extends TextOutput {\n /** @internal */\n _sinks: TextOutput[];\n\n constructor(sinks: TextOutput[], nextInChain?: TextOutput) {\n super(nextInChain);\n this._sinks = sinks;\n }\n\n async captureText(text: string) {\n await Promise.all(this._sinks.map((sink) => sink.captureText(text)));\n }\n\n flush() {\n for (const sink of this._sinks) {\n sink.flush();\n }\n }\n}\n\nexport interface AudioOutputOptions {\n sampleRate: number;\n numChannels: number;\n trackPublishOptions: TrackPublishOptions;\n queueSizeMs?: number;\n}\nexport class ParticipantAudioOutput extends AudioOutput {\n private room: Room;\n private options: AudioOutputOptions;\n private audioSource: AudioSource;\n private publication?: LocalTrackPublication;\n private flushTask?: Task<void>;\n private pushedDurationMs: number = 0;\n private startedFuture: Future<void> = new Future();\n private interruptedFuture: Future<void> = new Future();\n\n constructor(room: Room, options: AudioOutputOptions) {\n super(options.sampleRate);\n this.room = room;\n this.options = options;\n this.audioSource = new AudioSource(options.sampleRate, options.numChannels);\n }\n\n get subscribed(): boolean {\n return this.startedFuture.done;\n }\n\n async start(signal: AbortSignal): Promise<void> {\n await this.publishTrack(signal);\n }\n\n async captureFrame(frame: AudioFrame): Promise<void> {\n await this.startedFuture.await;\n\n super.captureFrame(frame);\n\n // TODO(AJS-102): use frame.durationMs once available in rtc-node\n this.pushedDurationMs += frame.samplesPerChannel / frame.sampleRate;\n await this.audioSource.captureFrame(frame);\n }\n\n private async waitForPlayoutTask(abortController: AbortController): Promise<void> {\n const abortFuture = new Future<boolean>();\n\n const resolveAbort = () => {\n if (!abortFuture.done) abortFuture.resolve(true);\n };\n\n abortController.signal.addEventListener('abort', resolveAbort);\n\n this.audioSource.waitForPlayout().finally(() => {\n abortController.signal.removeEventListener('abort', resolveAbort);\n if (!abortFuture.done) abortFuture.resolve(false);\n });\n\n const interrupted = await Promise.race([\n abortFuture.await,\n this.interruptedFuture.await.then(() => true),\n ]);\n\n let pushedDuration = this.pushedDurationMs;\n\n if (interrupted) {\n // Calculate actual played duration accounting for queued audio\n pushedDuration = Math.max(this.pushedDurationMs - this.audioSource.queuedDuration, 0);\n this.audioSource.clearQueue();\n }\n\n this.pushedDurationMs = 0;\n this.interruptedFuture = new Future();\n this.onPlaybackFinished({\n playbackPosition: pushedDuration,\n interrupted,\n });\n }\n\n /**\n * Flush any buffered audio, marking the current playback/segment as complete\n */\n flush(): void {\n super.flush();\n\n if (!this.pushedDurationMs) {\n return;\n }\n\n if (this.flushTask && !this.flushTask.done) {\n this.logger.error('flush called while playback is in progress');\n this.flushTask.cancel();\n }\n\n this.flushTask = Task.from((controller) => this.waitForPlayoutTask(controller));\n }\n\n clearBuffer(): void {\n if (!this.pushedDurationMs) {\n return;\n }\n\n this.interruptedFuture.resolve();\n }\n\n private async publishTrack(signal: AbortSignal) {\n const track = LocalAudioTrack.createAudioTrack('roomio_audio', this.audioSource);\n this.publication = await this.room.localParticipant?.publishTrack(\n track,\n new TrackPublishOptions({ source: TrackSource.SOURCE_MICROPHONE }),\n );\n\n if (signal.aborted) {\n return;\n }\n\n await this.publication?.waitForSubscription();\n\n if (!this.startedFuture.done) {\n this.startedFuture.resolve();\n }\n }\n\n async close() {\n // TODO(AJS-106): add republish track\n await this.audioSource.close();\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/voice/room_io/_output.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { RemoteParticipant } from '@livekit/rtc-node';\nimport {\n type AudioFrame,\n AudioSource,\n LocalAudioTrack,\n type LocalTrackPublication,\n type Participant,\n type RemoteTrackPublication,\n type Room,\n RoomEvent,\n type TextStreamWriter,\n TrackPublishOptions,\n TrackSource,\n} from '@livekit/rtc-node';\nimport {\n ATTRIBUTE_TRANSCRIPTION_FINAL,\n ATTRIBUTE_TRANSCRIPTION_SEGMENT_ID,\n ATTRIBUTE_TRANSCRIPTION_TRACK_ID,\n TOPIC_TRANSCRIPTION,\n} from '../../constants.js';\nimport { log } from '../../log.js';\nimport { Future, Task, shortuuid } from '../../utils.js';\nimport { AudioOutput, TextOutput } from '../io.js';\nimport { findMicrophoneTrackId } from '../transcription/index.js';\n\nabstract class BaseParticipantTranscriptionOutput extends TextOutput {\n protected room: Room;\n protected isDeltaStream: boolean;\n protected participantIdentity: string | null = null;\n protected trackId?: string;\n protected capturing: boolean = false;\n protected latestText: string = '';\n protected currentId: string = this.generateCurrentId();\n protected logger = log();\n\n constructor(room: Room, isDeltaStream: boolean, participant: Participant | string | null) {\n super();\n this.room = room;\n this.isDeltaStream = isDeltaStream;\n\n this.room.on(RoomEvent.TrackPublished, this.onTrackPublished);\n this.room.on(RoomEvent.LocalTrackPublished, this.onLocalTrackPublished);\n\n this.setParticipant(participant);\n }\n\n setParticipant(participant: Participant | string | null) {\n if (typeof participant === 'string' || participant === null) {\n this.participantIdentity = participant;\n } else {\n this.participantIdentity = participant.identity;\n }\n\n if (!this.participantIdentity) {\n return;\n }\n\n try {\n this.trackId = findMicrophoneTrackId(this.room, this.participantIdentity);\n } catch (error) {\n // track id is optional for TextStream when audio is not published\n }\n\n this.flush();\n this.resetState();\n }\n\n protected onTrackPublished = (track: RemoteTrackPublication, participant: RemoteParticipant) => {\n if (\n !this.participantIdentity ||\n participant.identity !== this.participantIdentity ||\n track.source !== TrackSource.SOURCE_MICROPHONE\n ) {\n return;\n }\n\n this.trackId = track.sid;\n };\n\n protected onLocalTrackPublished = (track: LocalTrackPublication) => {\n if (\n !this.participantIdentity ||\n this.participantIdentity !== this.room.localParticipant?.identity ||\n track.source !== TrackSource.SOURCE_MICROPHONE\n ) {\n return;\n }\n\n this.trackId = track.sid;\n };\n\n protected generateCurrentId(): string {\n return shortuuid('SG_');\n }\n\n protected resetState() {\n this.currentId = this.generateCurrentId();\n this.capturing = false;\n this.latestText = '';\n }\n\n async captureText(text: string) {\n if (!this.participantIdentity) {\n return;\n }\n\n this.latestText = text;\n await this.handleCaptureText(text);\n }\n\n flush() {\n if (!this.participantIdentity || !this.capturing) {\n return;\n }\n\n this.capturing = false;\n this.handleFlush();\n }\n\n protected abstract handleCaptureText(text: string): Promise<void>;\n protected abstract handleFlush(): void;\n}\n\nexport class ParticipantTranscriptionOutput extends BaseParticipantTranscriptionOutput {\n private writer: TextStreamWriter | null = null;\n private flushTask: Task<void> | null = null;\n\n protected async handleCaptureText(text: string): Promise<void> {\n if (this.flushTask && !this.flushTask.done) {\n await this.flushTask.result;\n }\n\n if (!this.capturing) {\n this.resetState();\n this.capturing = true;\n }\n\n try {\n if (this.room.isConnected) {\n if (this.isDeltaStream) {\n // reuse the existing writer\n if (this.writer === null) {\n this.writer = await this.createTextWriter();\n }\n await this.writer.write(text);\n } else {\n const tmpWriter = await this.createTextWriter();\n await tmpWriter.write(text);\n await tmpWriter.close();\n }\n }\n } catch (error) {\n this.logger.error(error, 'failed to publish transcription');\n }\n }\n\n protected handleFlush() {\n const currWriter = this.writer;\n this.writer = null;\n this.flushTask = Task.from((controller) => this.flushTaskImpl(currWriter, controller.signal));\n }\n\n private async createTextWriter(attributes?: Record<string, string>): Promise<TextStreamWriter> {\n if (!this.participantIdentity) {\n throw new Error('participantIdentity not found');\n }\n\n if (!this.room.localParticipant) {\n throw new Error('localParticipant not found');\n }\n\n if (!attributes) {\n attributes = {\n [ATTRIBUTE_TRANSCRIPTION_FINAL]: 'false',\n };\n if (this.trackId) {\n attributes[ATTRIBUTE_TRANSCRIPTION_TRACK_ID] = this.trackId;\n }\n }\n attributes[ATTRIBUTE_TRANSCRIPTION_SEGMENT_ID] = this.currentId;\n\n return await this.room.localParticipant.streamText({\n topic: TOPIC_TRANSCRIPTION,\n senderIdentity: this.participantIdentity,\n attributes,\n });\n }\n\n private async flushTaskImpl(writer: TextStreamWriter | null, signal: AbortSignal): Promise<void> {\n const attributes: Record<string, string> = {\n [ATTRIBUTE_TRANSCRIPTION_FINAL]: 'true',\n };\n if (this.trackId) {\n attributes[ATTRIBUTE_TRANSCRIPTION_TRACK_ID] = this.trackId;\n }\n\n const abortPromise = new Promise<void>((resolve) => {\n signal.addEventListener('abort', () => resolve());\n });\n\n try {\n if (this.room.isConnected) {\n if (this.isDeltaStream) {\n if (writer) {\n await Promise.race([writer.close(), abortPromise]);\n }\n } else {\n const tmpWriter = await Promise.race([this.createTextWriter(attributes), abortPromise]);\n if (signal.aborted || !tmpWriter) {\n return;\n }\n await Promise.race([tmpWriter.write(this.latestText), abortPromise]);\n if (signal.aborted) {\n return;\n }\n await Promise.race([tmpWriter.close(), abortPromise]);\n }\n }\n } catch (error) {\n this.logger.error(error, 'failed to publish transcription');\n }\n }\n}\n\nexport class ParticipantLegacyTranscriptionOutput extends BaseParticipantTranscriptionOutput {\n private pushedText: string = '';\n private flushTask: Promise<void> | null = null;\n\n protected async handleCaptureText(text: string): Promise<void> {\n if (!this.trackId) {\n return;\n }\n\n if (this.flushTask) {\n await this.flushTask;\n }\n\n if (!this.capturing) {\n this.resetState();\n this.capturing = true;\n }\n\n if (this.isDeltaStream) {\n this.pushedText += text;\n } else {\n this.pushedText = text;\n }\n\n await this.publishTranscription(this.currentId, this.pushedText, false);\n }\n\n protected handleFlush() {\n if (!this.trackId) {\n return;\n }\n\n this.flushTask = this.publishTranscription(this.currentId, this.pushedText, true);\n this.resetState();\n }\n\n async publishTranscription(id: string, text: string, final: boolean, signal?: AbortSignal) {\n if (!this.participantIdentity || !this.trackId) {\n return;\n }\n\n try {\n if (this.room.isConnected) {\n if (signal?.aborted) {\n return;\n }\n\n await this.room.localParticipant?.publishTranscription({\n participantIdentity: this.participantIdentity,\n trackSid: this.trackId,\n segments: [{ id, text, final, startTime: BigInt(0), endTime: BigInt(0), language: '' }],\n });\n }\n } catch (error) {\n this.logger.error(error, 'failed to publish transcription');\n }\n }\n\n protected resetState() {\n super.resetState();\n this.pushedText = '';\n }\n}\n\nexport class ParalellTextOutput extends TextOutput {\n /** @internal */\n _sinks: TextOutput[];\n\n constructor(sinks: TextOutput[], nextInChain?: TextOutput) {\n super(nextInChain);\n this._sinks = sinks;\n }\n\n async captureText(text: string) {\n await Promise.all(this._sinks.map((sink) => sink.captureText(text)));\n }\n\n flush() {\n for (const sink of this._sinks) {\n sink.flush();\n }\n }\n}\n\nexport interface AudioOutputOptions {\n sampleRate: number;\n numChannels: number;\n trackPublishOptions: TrackPublishOptions;\n queueSizeMs?: number;\n}\nexport class ParticipantAudioOutput extends AudioOutput {\n private room: Room;\n private options: AudioOutputOptions;\n private audioSource: AudioSource;\n private publication?: LocalTrackPublication;\n private flushTask?: Task<void>;\n\n /** Duration of audio pushed to the source, in seconds */\n private pushedDuration: number = 0;\n private startedFuture: Future<void> = new Future();\n private interruptedFuture: Future<void> = new Future();\n\n constructor(room: Room, options: AudioOutputOptions) {\n super(options.sampleRate);\n this.room = room;\n this.options = options;\n this.audioSource = new AudioSource(options.sampleRate, options.numChannels);\n }\n\n get subscribed(): boolean {\n return this.startedFuture.done;\n }\n\n async start(signal: AbortSignal): Promise<void> {\n await this.publishTrack(signal);\n }\n\n async captureFrame(frame: AudioFrame): Promise<void> {\n await this.startedFuture.await;\n\n super.captureFrame(frame);\n\n // TODO(AJS-102): use frame.durationMs once available in rtc-node\n this.pushedDuration += frame.samplesPerChannel / frame.sampleRate;\n await this.audioSource.captureFrame(frame);\n }\n\n private async waitForPlayoutTask(abortController: AbortController): Promise<void> {\n const abortFuture = new Future<boolean>();\n\n const resolveAbort = () => {\n if (!abortFuture.done) abortFuture.resolve(true);\n };\n\n abortController.signal.addEventListener('abort', resolveAbort);\n\n this.audioSource.waitForPlayout().finally(() => {\n abortController.signal.removeEventListener('abort', resolveAbort);\n if (!abortFuture.done) abortFuture.resolve(false);\n });\n\n const interrupted = await Promise.race([\n abortFuture.await,\n this.interruptedFuture.await.then(() => true),\n ]);\n\n let pushedDuration = this.pushedDuration;\n\n if (interrupted) {\n // Calculate actual played duration accounting for queued audio\n // Note: queuedDuration is in milliseconds, pushedDuration is in seconds\n pushedDuration = Math.max(this.pushedDuration - this.audioSource.queuedDuration / 1000, 0);\n this.audioSource.clearQueue();\n }\n\n this.pushedDuration = 0;\n this.interruptedFuture = new Future();\n this.onPlaybackFinished({\n playbackPosition: pushedDuration,\n interrupted,\n });\n }\n\n /**\n * Flush any buffered audio, marking the current playback/segment as complete\n */\n flush(): void {\n super.flush();\n\n if (!this.pushedDuration) {\n return;\n }\n\n if (this.flushTask && !this.flushTask.done) {\n this.logger.error('flush called while playback is in progress');\n this.flushTask.cancel();\n }\n\n this.flushTask = Task.from((controller) => this.waitForPlayoutTask(controller));\n }\n\n clearBuffer(): void {\n if (!this.pushedDuration) {\n return;\n }\n\n this.interruptedFuture.resolve();\n }\n\n private async publishTrack(signal: AbortSignal) {\n const track = LocalAudioTrack.createAudioTrack('roomio_audio', this.audioSource);\n this.publication = await this.room.localParticipant?.publishTrack(\n track,\n new TrackPublishOptions({ source: TrackSource.SOURCE_MICROPHONE }),\n );\n\n if (signal.aborted) {\n return;\n }\n\n await this.publication?.waitForSubscription();\n\n if (!this.startedFuture.done) {\n this.startedFuture.resolve();\n }\n }\n\n async close() {\n // TODO(AJS-106): add republish track\n await this.audioSource.close();\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAIA,sBAYO;AACP,uBAKO;AACP,iBAAoB;AACpB,mBAAwC;AACxC,gBAAwC;AACxC,2BAAsC;AAEtC,MAAe,2CAA2C,qBAAW;AAAA,EACzD;AAAA,EACA;AAAA,EACA,sBAAqC;AAAA,EACrC;AAAA,EACA,YAAqB;AAAA,EACrB,aAAqB;AAAA,EACrB,YAAoB,KAAK,kBAAkB;AAAA,EAC3C,aAAS,gBAAI;AAAA,EAEvB,YAAY,MAAY,eAAwB,aAA0C;AACxF,UAAM;AACN,SAAK,OAAO;AACZ,SAAK,gBAAgB;AAErB,SAAK,KAAK,GAAG,0BAAU,gBAAgB,KAAK,gBAAgB;AAC5D,SAAK,KAAK,GAAG,0BAAU,qBAAqB,KAAK,qBAAqB;AAEtE,SAAK,eAAe,WAAW;AAAA,EACjC;AAAA,EAEA,eAAe,aAA0C;AACvD,QAAI,OAAO,gBAAgB,YAAY,gBAAgB,MAAM;AAC3D,WAAK,sBAAsB;AAAA,IAC7B,OAAO;AACL,WAAK,sBAAsB,YAAY;AAAA,IACzC;AAEA,QAAI,CAAC,KAAK,qBAAqB;AAC7B;AAAA,IACF;AAEA,QAAI;AACF,WAAK,cAAU,4CAAsB,KAAK,MAAM,KAAK,mBAAmB;AAAA,IAC1E,SAAS,OAAO;AAAA,IAEhB;AAEA,SAAK,MAAM;AACX,SAAK,WAAW;AAAA,EAClB;AAAA,EAEU,mBAAmB,CAAC,OAA+B,gBAAmC;AAC9F,QACE,CAAC,KAAK,uBACN,YAAY,aAAa,KAAK,uBAC9B,MAAM,WAAW,4BAAY,mBAC7B;AACA;AAAA,IACF;AAEA,SAAK,UAAU,MAAM;AAAA,EACvB;AAAA,EAEU,wBAAwB,CAAC,UAAiC;AAlFtE;AAmFI,QACE,CAAC,KAAK,uBACN,KAAK,0BAAwB,UAAK,KAAK,qBAAV,mBAA4B,aACzD,MAAM,WAAW,4BAAY,mBAC7B;AACA;AAAA,IACF;AAEA,SAAK,UAAU,MAAM;AAAA,EACvB;AAAA,EAEU,oBAA4B;AACpC,eAAO,wBAAU,KAAK;AAAA,EACxB;AAAA,EAEU,aAAa;AACrB,SAAK,YAAY,KAAK,kBAAkB;AACxC,SAAK,YAAY;AACjB,SAAK,aAAa;AAAA,EACpB;AAAA,EAEA,MAAM,YAAY,MAAc;AAC9B,QAAI,CAAC,KAAK,qBAAqB;AAC7B;AAAA,IACF;AAEA,SAAK,aAAa;AAClB,UAAM,KAAK,kBAAkB,IAAI;AAAA,EACnC;AAAA,EAEA,QAAQ;AACN,QAAI,CAAC,KAAK,uBAAuB,CAAC,KAAK,WAAW;AAChD;AAAA,IACF;AAEA,SAAK,YAAY;AACjB,SAAK,YAAY;AAAA,EACnB;AAIF;AAEO,MAAM,uCAAuC,mCAAmC;AAAA,EAC7E,SAAkC;AAAA,EAClC,YAA+B;AAAA,EAEvC,MAAgB,kBAAkB,MAA6B;AAC7D,QAAI,KAAK,aAAa,CAAC,KAAK,UAAU,MAAM;AAC1C,YAAM,KAAK,UAAU;AAAA,IACvB;AAEA,QAAI,CAAC,KAAK,WAAW;AACnB,WAAK,WAAW;AAChB,WAAK,YAAY;AAAA,IACnB;AAEA,QAAI;AACF,UAAI,KAAK,KAAK,aAAa;AACzB,YAAI,KAAK,eAAe;AAEtB,cAAI,KAAK,WAAW,MAAM;AACxB,iBAAK,SAAS,MAAM,KAAK,iBAAiB;AAAA,UAC5C;AACA,gBAAM,KAAK,OAAO,MAAM,IAAI;AAAA,QAC9B,OAAO;AACL,gBAAM,YAAY,MAAM,KAAK,iBAAiB;AAC9C,gBAAM,UAAU,MAAM,IAAI;AAC1B,gBAAM,UAAU,MAAM;AAAA,QACxB;AAAA,MACF;AAAA,IACF,SAAS,OAAO;AACd,WAAK,OAAO,MAAM,OAAO,iCAAiC;AAAA,IAC5D;AAAA,EACF;AAAA,EAEU,cAAc;AACtB,UAAM,aAAa,KAAK;AACxB,SAAK,SAAS;AACd,SAAK,YAAY,kBAAK,KAAK,CAAC,eAAe,KAAK,cAAc,YAAY,WAAW,MAAM,CAAC;AAAA,EAC9F;AAAA,EAEA,MAAc,iBAAiB,YAAgE;AAC7F,QAAI,CAAC,KAAK,qBAAqB;AAC7B,YAAM,IAAI,MAAM,+BAA+B;AAAA,IACjD;AAEA,QAAI,CAAC,KAAK,KAAK,kBAAkB;AAC/B,YAAM,IAAI,MAAM,4BAA4B;AAAA,IAC9C;AAEA,QAAI,CAAC,YAAY;AACf,mBAAa;AAAA,QACX,CAAC,8CAA6B,GAAG;AAAA,MACnC;AACA,UAAI,KAAK,SAAS;AAChB,mBAAW,iDAAgC,IAAI,KAAK;AAAA,MACtD;AAAA,IACF;AACA,eAAW,mDAAkC,IAAI,KAAK;AAEtD,WAAO,MAAM,KAAK,KAAK,iBAAiB,WAAW;AAAA,MACjD,OAAO;AAAA,MACP,gBAAgB,KAAK;AAAA,MACrB;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,cAAc,QAAiC,QAAoC;AAC/F,UAAM,aAAqC;AAAA,MACzC,CAAC,8CAA6B,GAAG;AAAA,IACnC;AACA,QAAI,KAAK,SAAS;AAChB,iBAAW,iDAAgC,IAAI,KAAK;AAAA,IACtD;AAEA,UAAM,eAAe,IAAI,QAAc,CAAC,YAAY;AAClD,aAAO,iBAAiB,SAAS,MAAM,QAAQ,CAAC;AAAA,IAClD,CAAC;AAED,QAAI;AACF,UAAI,KAAK,KAAK,aAAa;AACzB,YAAI,KAAK,eAAe;AACtB,cAAI,QAAQ;AACV,kBAAM,QAAQ,KAAK,CAAC,OAAO,MAAM,GAAG,YAAY,CAAC;AAAA,UACnD;AAAA,QACF,OAAO;AACL,gBAAM,YAAY,MAAM,QAAQ,KAAK,CAAC,KAAK,iBAAiB,UAAU,GAAG,YAAY,CAAC;AACtF,cAAI,OAAO,WAAW,CAAC,WAAW;AAChC;AAAA,UACF;AACA,gBAAM,QAAQ,KAAK,CAAC,UAAU,MAAM,KAAK,UAAU,GAAG,YAAY,CAAC;AACnE,cAAI,OAAO,SAAS;AAClB;AAAA,UACF;AACA,gBAAM,QAAQ,KAAK,CAAC,UAAU,MAAM,GAAG,YAAY,CAAC;AAAA,QACtD;AAAA,MACF;AAAA,IACF,SAAS,OAAO;AACd,WAAK,OAAO,MAAM,OAAO,iCAAiC;AAAA,IAC5D;AAAA,EACF;AACF;AAEO,MAAM,6CAA6C,mCAAmC;AAAA,EACnF,aAAqB;AAAA,EACrB,YAAkC;AAAA,EAE1C,MAAgB,kBAAkB,MAA6B;AAC7D,QAAI,CAAC,KAAK,SAAS;AACjB;AAAA,IACF;AAEA,QAAI,KAAK,WAAW;AAClB,YAAM,KAAK;AAAA,IACb;AAEA,QAAI,CAAC,KAAK,WAAW;AACnB,WAAK,WAAW;AAChB,WAAK,YAAY;AAAA,IACnB;AAEA,QAAI,KAAK,eAAe;AACtB,WAAK,cAAc;AAAA,IACrB,OAAO;AACL,WAAK,aAAa;AAAA,IACpB;AAEA,UAAM,KAAK,qBAAqB,KAAK,WAAW,KAAK,YAAY,KAAK;AAAA,EACxE;AAAA,EAEU,cAAc;AACtB,QAAI,CAAC,KAAK,SAAS;AACjB;AAAA,IACF;AAEA,SAAK,YAAY,KAAK,qBAAqB,KAAK,WAAW,KAAK,YAAY,IAAI;AAChF,SAAK,WAAW;AAAA,EAClB;AAAA,EAEA,MAAM,qBAAqB,IAAY,MAAc,OAAgB,QAAsB;AAvQ7F;AAwQI,QAAI,CAAC,KAAK,uBAAuB,CAAC,KAAK,SAAS;AAC9C;AAAA,IACF;AAEA,QAAI;AACF,UAAI,KAAK,KAAK,aAAa;AACzB,YAAI,iCAAQ,SAAS;AACnB;AAAA,QACF;AAEA,gBAAM,UAAK,KAAK,qBAAV,mBAA4B,qBAAqB;AAAA,UACrD,qBAAqB,KAAK;AAAA,UAC1B,UAAU,KAAK;AAAA,UACf,UAAU,CAAC,EAAE,IAAI,MAAM,OAAO,WAAW,OAAO,CAAC,GAAG,SAAS,OAAO,CAAC,GAAG,UAAU,GAAG,CAAC;AAAA,QACxF;AAAA,MACF;AAAA,IACF,SAAS,OAAO;AACd,WAAK,OAAO,MAAM,OAAO,iCAAiC;AAAA,IAC5D;AAAA,EACF;AAAA,EAEU,aAAa;AACrB,UAAM,WAAW;AACjB,SAAK,aAAa;AAAA,EACpB;AACF;AAEO,MAAM,2BAA2B,qBAAW;AAAA;AAAA,EAEjD;AAAA,EAEA,YAAY,OAAqB,aAA0B;AACzD,UAAM,WAAW;AACjB,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,MAAM,YAAY,MAAc;AAC9B,UAAM,QAAQ,IAAI,KAAK,OAAO,IAAI,CAAC,SAAS,KAAK,YAAY,IAAI,CAAC,CAAC;AAAA,EACrE;AAAA,EAEA,QAAQ;AACN,eAAW,QAAQ,KAAK,QAAQ;AAC9B,WAAK,MAAM;AAAA,IACb;AAAA,EACF;AACF;AAQO,MAAM,+BAA+B,sBAAY;AAAA,EAC9C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAGA,iBAAyB;AAAA,EACzB,gBAA8B,IAAI,oBAAO;AAAA,EACzC,oBAAkC,IAAI,oBAAO;AAAA,EAErD,YAAY,MAAY,SAA6B;AACnD,UAAM,QAAQ,UAAU;AACxB,SAAK,OAAO;AACZ,SAAK,UAAU;AACf,SAAK,cAAc,IAAI,4BAAY,QAAQ,YAAY,QAAQ,WAAW;AAAA,EAC5E;AAAA,EAEA,IAAI,aAAsB;AACxB,WAAO,KAAK,cAAc;AAAA,EAC5B;AAAA,EAEA,MAAM,MAAM,QAAoC;AAC9C,UAAM,KAAK,aAAa,MAAM;AAAA,EAChC;AAAA,EAEA,MAAM,aAAa,OAAkC;AACnD,UAAM,KAAK,cAAc;AAEzB,UAAM,aAAa,KAAK;AAGxB,SAAK,kBAAkB,MAAM,oBAAoB,MAAM;AACvD,UAAM,KAAK,YAAY,aAAa,KAAK;AAAA,EAC3C;AAAA,EAEA,MAAc,mBAAmB,iBAAiD;AAChF,UAAM,cAAc,IAAI,oBAAgB;AAExC,UAAM,eAAe,MAAM;AACzB,UAAI,CAAC,YAAY,KAAM,aAAY,QAAQ,IAAI;AAAA,IACjD;AAEA,oBAAgB,OAAO,iBAAiB,SAAS,YAAY;AAE7D,SAAK,YAAY,eAAe,EAAE,QAAQ,MAAM;AAC9C,sBAAgB,OAAO,oBAAoB,SAAS,YAAY;AAChE,UAAI,CAAC,YAAY,KAAM,aAAY,QAAQ,KAAK;AAAA,IAClD,CAAC;AAED,UAAM,cAAc,MAAM,QAAQ,KAAK;AAAA,MACrC,YAAY;AAAA,MACZ,KAAK,kBAAkB,MAAM,KAAK,MAAM,IAAI;AAAA,IAC9C,CAAC;AAED,QAAI,iBAAiB,KAAK;AAE1B,QAAI,aAAa;AAGf,uBAAiB,KAAK,IAAI,KAAK,iBAAiB,KAAK,YAAY,iBAAiB,KAAM,CAAC;AACzF,WAAK,YAAY,WAAW;AAAA,IAC9B;AAEA,SAAK,iBAAiB;AACtB,SAAK,oBAAoB,IAAI,oBAAO;AACpC,SAAK,mBAAmB;AAAA,MACtB,kBAAkB;AAAA,MAClB;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACZ,UAAM,MAAM;AAEZ,QAAI,CAAC,KAAK,gBAAgB;AACxB;AAAA,IACF;AAEA,QAAI,KAAK,aAAa,CAAC,KAAK,UAAU,MAAM;AAC1C,WAAK,OAAO,MAAM,4CAA4C;AAC9D,WAAK,UAAU,OAAO;AAAA,IACxB;AAEA,SAAK,YAAY,kBAAK,KAAK,CAAC,eAAe,KAAK,mBAAmB,UAAU,CAAC;AAAA,EAChF;AAAA,EAEA,cAAoB;AAClB,QAAI,CAAC,KAAK,gBAAgB;AACxB;AAAA,IACF;AAEA,SAAK,kBAAkB,QAAQ;AAAA,EACjC;AAAA,EAEA,MAAc,aAAa,QAAqB;AAhalD;AAiaI,UAAM,QAAQ,gCAAgB,iBAAiB,gBAAgB,KAAK,WAAW;AAC/E,SAAK,cAAc,QAAM,UAAK,KAAK,qBAAV,mBAA4B;AAAA,MACnD;AAAA,MACA,IAAI,oCAAoB,EAAE,QAAQ,4BAAY,kBAAkB,CAAC;AAAA;AAGnE,QAAI,OAAO,SAAS;AAClB;AAAA,IACF;AAEA,YAAM,UAAK,gBAAL,mBAAkB;AAExB,QAAI,CAAC,KAAK,cAAc,MAAM;AAC5B,WAAK,cAAc,QAAQ;AAAA,IAC7B;AAAA,EACF;AAAA,EAEA,MAAM,QAAQ;AAEZ,UAAM,KAAK,YAAY,MAAM;AAAA,EAC/B;AACF;","names":[]}
@@ -56,7 +56,8 @@ export declare class ParticipantAudioOutput extends AudioOutput {
56
56
  private audioSource;
57
57
  private publication?;
58
58
  private flushTask?;
59
- private pushedDurationMs;
59
+ /** Duration of audio pushed to the source, in seconds */
60
+ private pushedDuration;
60
61
  private startedFuture;
61
62
  private interruptedFuture;
62
63
  constructor(room: Room, options: AudioOutputOptions);
@@ -56,7 +56,8 @@ export declare class ParticipantAudioOutput extends AudioOutput {
56
56
  private audioSource;
57
57
  private publication?;
58
58
  private flushTask?;
59
- private pushedDurationMs;
59
+ /** Duration of audio pushed to the source, in seconds */
60
+ private pushedDuration;
60
61
  private startedFuture;
61
62
  private interruptedFuture;
62
63
  constructor(room: Room, options: AudioOutputOptions);
@@ -1 +1 @@
1
- {"version":3,"file":"_output.d.ts","sourceRoot":"","sources":["../../../src/voice/room_io/_output.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"_output.d.ts","sourceRoot":"","sources":["../../../src/voice/room_io/_output.ts"],"names":[],"mappings":"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"}
@@ -247,7 +247,8 @@ class ParticipantAudioOutput extends AudioOutput {
247
247
  audioSource;
248
248
  publication;
249
249
  flushTask;
250
- pushedDurationMs = 0;
250
+ /** Duration of audio pushed to the source, in seconds */
251
+ pushedDuration = 0;
251
252
  startedFuture = new Future();
252
253
  interruptedFuture = new Future();
253
254
  constructor(room, options) {
@@ -265,7 +266,7 @@ class ParticipantAudioOutput extends AudioOutput {
265
266
  async captureFrame(frame) {
266
267
  await this.startedFuture.await;
267
268
  super.captureFrame(frame);
268
- this.pushedDurationMs += frame.samplesPerChannel / frame.sampleRate;
269
+ this.pushedDuration += frame.samplesPerChannel / frame.sampleRate;
269
270
  await this.audioSource.captureFrame(frame);
270
271
  }
271
272
  async waitForPlayoutTask(abortController) {
@@ -282,12 +283,12 @@ class ParticipantAudioOutput extends AudioOutput {
282
283
  abortFuture.await,
283
284
  this.interruptedFuture.await.then(() => true)
284
285
  ]);
285
- let pushedDuration = this.pushedDurationMs;
286
+ let pushedDuration = this.pushedDuration;
286
287
  if (interrupted) {
287
- pushedDuration = Math.max(this.pushedDurationMs - this.audioSource.queuedDuration, 0);
288
+ pushedDuration = Math.max(this.pushedDuration - this.audioSource.queuedDuration / 1e3, 0);
288
289
  this.audioSource.clearQueue();
289
290
  }
290
- this.pushedDurationMs = 0;
291
+ this.pushedDuration = 0;
291
292
  this.interruptedFuture = new Future();
292
293
  this.onPlaybackFinished({
293
294
  playbackPosition: pushedDuration,
@@ -299,7 +300,7 @@ class ParticipantAudioOutput extends AudioOutput {
299
300
  */
300
301
  flush() {
301
302
  super.flush();
302
- if (!this.pushedDurationMs) {
303
+ if (!this.pushedDuration) {
303
304
  return;
304
305
  }
305
306
  if (this.flushTask && !this.flushTask.done) {
@@ -309,7 +310,7 @@ class ParticipantAudioOutput extends AudioOutput {
309
310
  this.flushTask = Task.from((controller) => this.waitForPlayoutTask(controller));
310
311
  }
311
312
  clearBuffer() {
312
- if (!this.pushedDurationMs) {
313
+ if (!this.pushedDuration) {
313
314
  return;
314
315
  }
315
316
  this.interruptedFuture.resolve();
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/voice/room_io/_output.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { RemoteParticipant } from '@livekit/rtc-node';\nimport {\n type AudioFrame,\n AudioSource,\n LocalAudioTrack,\n type LocalTrackPublication,\n type Participant,\n type RemoteTrackPublication,\n type Room,\n RoomEvent,\n type TextStreamWriter,\n TrackPublishOptions,\n TrackSource,\n} from '@livekit/rtc-node';\nimport {\n ATTRIBUTE_TRANSCRIPTION_FINAL,\n ATTRIBUTE_TRANSCRIPTION_SEGMENT_ID,\n ATTRIBUTE_TRANSCRIPTION_TRACK_ID,\n TOPIC_TRANSCRIPTION,\n} from '../../constants.js';\nimport { log } from '../../log.js';\nimport { Future, Task, shortuuid } from '../../utils.js';\nimport { AudioOutput, TextOutput } from '../io.js';\nimport { findMicrophoneTrackId } from '../transcription/index.js';\n\nabstract class BaseParticipantTranscriptionOutput extends TextOutput {\n protected room: Room;\n protected isDeltaStream: boolean;\n protected participantIdentity: string | null = null;\n protected trackId?: string;\n protected capturing: boolean = false;\n protected latestText: string = '';\n protected currentId: string = this.generateCurrentId();\n protected logger = log();\n\n constructor(room: Room, isDeltaStream: boolean, participant: Participant | string | null) {\n super();\n this.room = room;\n this.isDeltaStream = isDeltaStream;\n\n this.room.on(RoomEvent.TrackPublished, this.onTrackPublished);\n this.room.on(RoomEvent.LocalTrackPublished, this.onLocalTrackPublished);\n\n this.setParticipant(participant);\n }\n\n setParticipant(participant: Participant | string | null) {\n if (typeof participant === 'string' || participant === null) {\n this.participantIdentity = participant;\n } else {\n this.participantIdentity = participant.identity;\n }\n\n if (!this.participantIdentity) {\n return;\n }\n\n try {\n this.trackId = findMicrophoneTrackId(this.room, this.participantIdentity);\n } catch (error) {\n // track id is optional for TextStream when audio is not published\n }\n\n this.flush();\n this.resetState();\n }\n\n protected onTrackPublished = (track: RemoteTrackPublication, participant: RemoteParticipant) => {\n if (\n !this.participantIdentity ||\n participant.identity !== this.participantIdentity ||\n track.source !== TrackSource.SOURCE_MICROPHONE\n ) {\n return;\n }\n\n this.trackId = track.sid;\n };\n\n protected onLocalTrackPublished = (track: LocalTrackPublication) => {\n if (\n !this.participantIdentity ||\n this.participantIdentity !== this.room.localParticipant?.identity ||\n track.source !== TrackSource.SOURCE_MICROPHONE\n ) {\n return;\n }\n\n this.trackId = track.sid;\n };\n\n protected generateCurrentId(): string {\n return shortuuid('SG_');\n }\n\n protected resetState() {\n this.currentId = this.generateCurrentId();\n this.capturing = false;\n this.latestText = '';\n }\n\n async captureText(text: string) {\n if (!this.participantIdentity) {\n return;\n }\n\n this.latestText = text;\n await this.handleCaptureText(text);\n }\n\n flush() {\n if (!this.participantIdentity || !this.capturing) {\n return;\n }\n\n this.capturing = false;\n this.handleFlush();\n }\n\n protected abstract handleCaptureText(text: string): Promise<void>;\n protected abstract handleFlush(): void;\n}\n\nexport class ParticipantTranscriptionOutput extends BaseParticipantTranscriptionOutput {\n private writer: TextStreamWriter | null = null;\n private flushTask: Task<void> | null = null;\n\n protected async handleCaptureText(text: string): Promise<void> {\n if (this.flushTask && !this.flushTask.done) {\n await this.flushTask.result;\n }\n\n if (!this.capturing) {\n this.resetState();\n this.capturing = true;\n }\n\n try {\n if (this.room.isConnected) {\n if (this.isDeltaStream) {\n // reuse the existing writer\n if (this.writer === null) {\n this.writer = await this.createTextWriter();\n }\n await this.writer.write(text);\n } else {\n const tmpWriter = await this.createTextWriter();\n await tmpWriter.write(text);\n await tmpWriter.close();\n }\n }\n } catch (error) {\n this.logger.error(error, 'failed to publish transcription');\n }\n }\n\n protected handleFlush() {\n const currWriter = this.writer;\n this.writer = null;\n this.flushTask = Task.from((controller) => this.flushTaskImpl(currWriter, controller.signal));\n }\n\n private async createTextWriter(attributes?: Record<string, string>): Promise<TextStreamWriter> {\n if (!this.participantIdentity) {\n throw new Error('participantIdentity not found');\n }\n\n if (!this.room.localParticipant) {\n throw new Error('localParticipant not found');\n }\n\n if (!attributes) {\n attributes = {\n [ATTRIBUTE_TRANSCRIPTION_FINAL]: 'false',\n };\n if (this.trackId) {\n attributes[ATTRIBUTE_TRANSCRIPTION_TRACK_ID] = this.trackId;\n }\n }\n attributes[ATTRIBUTE_TRANSCRIPTION_SEGMENT_ID] = this.currentId;\n\n return await this.room.localParticipant.streamText({\n topic: TOPIC_TRANSCRIPTION,\n senderIdentity: this.participantIdentity,\n attributes,\n });\n }\n\n private async flushTaskImpl(writer: TextStreamWriter | null, signal: AbortSignal): Promise<void> {\n const attributes: Record<string, string> = {\n [ATTRIBUTE_TRANSCRIPTION_FINAL]: 'true',\n };\n if (this.trackId) {\n attributes[ATTRIBUTE_TRANSCRIPTION_TRACK_ID] = this.trackId;\n }\n\n const abortPromise = new Promise<void>((resolve) => {\n signal.addEventListener('abort', () => resolve());\n });\n\n try {\n if (this.room.isConnected) {\n if (this.isDeltaStream) {\n if (writer) {\n await Promise.race([writer.close(), abortPromise]);\n }\n } else {\n const tmpWriter = await Promise.race([this.createTextWriter(attributes), abortPromise]);\n if (signal.aborted || !tmpWriter) {\n return;\n }\n await Promise.race([tmpWriter.write(this.latestText), abortPromise]);\n if (signal.aborted) {\n return;\n }\n await Promise.race([tmpWriter.close(), abortPromise]);\n }\n }\n } catch (error) {\n this.logger.error(error, 'failed to publish transcription');\n }\n }\n}\n\nexport class ParticipantLegacyTranscriptionOutput extends BaseParticipantTranscriptionOutput {\n private pushedText: string = '';\n private flushTask: Promise<void> | null = null;\n\n protected async handleCaptureText(text: string): Promise<void> {\n if (!this.trackId) {\n return;\n }\n\n if (this.flushTask) {\n await this.flushTask;\n }\n\n if (!this.capturing) {\n this.resetState();\n this.capturing = true;\n }\n\n if (this.isDeltaStream) {\n this.pushedText += text;\n } else {\n this.pushedText = text;\n }\n\n await this.publishTranscription(this.currentId, this.pushedText, false);\n }\n\n protected handleFlush() {\n if (!this.trackId) {\n return;\n }\n\n this.flushTask = this.publishTranscription(this.currentId, this.pushedText, true);\n this.resetState();\n }\n\n async publishTranscription(id: string, text: string, final: boolean, signal?: AbortSignal) {\n if (!this.participantIdentity || !this.trackId) {\n return;\n }\n\n try {\n if (this.room.isConnected) {\n if (signal?.aborted) {\n return;\n }\n\n await this.room.localParticipant?.publishTranscription({\n participantIdentity: this.participantIdentity,\n trackSid: this.trackId,\n segments: [{ id, text, final, startTime: BigInt(0), endTime: BigInt(0), language: '' }],\n });\n }\n } catch (error) {\n this.logger.error(error, 'failed to publish transcription');\n }\n }\n\n protected resetState() {\n super.resetState();\n this.pushedText = '';\n }\n}\n\nexport class ParalellTextOutput extends TextOutput {\n /** @internal */\n _sinks: TextOutput[];\n\n constructor(sinks: TextOutput[], nextInChain?: TextOutput) {\n super(nextInChain);\n this._sinks = sinks;\n }\n\n async captureText(text: string) {\n await Promise.all(this._sinks.map((sink) => sink.captureText(text)));\n }\n\n flush() {\n for (const sink of this._sinks) {\n sink.flush();\n }\n }\n}\n\nexport interface AudioOutputOptions {\n sampleRate: number;\n numChannels: number;\n trackPublishOptions: TrackPublishOptions;\n queueSizeMs?: number;\n}\nexport class ParticipantAudioOutput extends AudioOutput {\n private room: Room;\n private options: AudioOutputOptions;\n private audioSource: AudioSource;\n private publication?: LocalTrackPublication;\n private flushTask?: Task<void>;\n private pushedDurationMs: number = 0;\n private startedFuture: Future<void> = new Future();\n private interruptedFuture: Future<void> = new Future();\n\n constructor(room: Room, options: AudioOutputOptions) {\n super(options.sampleRate);\n this.room = room;\n this.options = options;\n this.audioSource = new AudioSource(options.sampleRate, options.numChannels);\n }\n\n get subscribed(): boolean {\n return this.startedFuture.done;\n }\n\n async start(signal: AbortSignal): Promise<void> {\n await this.publishTrack(signal);\n }\n\n async captureFrame(frame: AudioFrame): Promise<void> {\n await this.startedFuture.await;\n\n super.captureFrame(frame);\n\n // TODO(AJS-102): use frame.durationMs once available in rtc-node\n this.pushedDurationMs += frame.samplesPerChannel / frame.sampleRate;\n await this.audioSource.captureFrame(frame);\n }\n\n private async waitForPlayoutTask(abortController: AbortController): Promise<void> {\n const abortFuture = new Future<boolean>();\n\n const resolveAbort = () => {\n if (!abortFuture.done) abortFuture.resolve(true);\n };\n\n abortController.signal.addEventListener('abort', resolveAbort);\n\n this.audioSource.waitForPlayout().finally(() => {\n abortController.signal.removeEventListener('abort', resolveAbort);\n if (!abortFuture.done) abortFuture.resolve(false);\n });\n\n const interrupted = await Promise.race([\n abortFuture.await,\n this.interruptedFuture.await.then(() => true),\n ]);\n\n let pushedDuration = this.pushedDurationMs;\n\n if (interrupted) {\n // Calculate actual played duration accounting for queued audio\n pushedDuration = Math.max(this.pushedDurationMs - this.audioSource.queuedDuration, 0);\n this.audioSource.clearQueue();\n }\n\n this.pushedDurationMs = 0;\n this.interruptedFuture = new Future();\n this.onPlaybackFinished({\n playbackPosition: pushedDuration,\n interrupted,\n });\n }\n\n /**\n * Flush any buffered audio, marking the current playback/segment as complete\n */\n flush(): void {\n super.flush();\n\n if (!this.pushedDurationMs) {\n return;\n }\n\n if (this.flushTask && !this.flushTask.done) {\n this.logger.error('flush called while playback is in progress');\n this.flushTask.cancel();\n }\n\n this.flushTask = Task.from((controller) => this.waitForPlayoutTask(controller));\n }\n\n clearBuffer(): void {\n if (!this.pushedDurationMs) {\n return;\n }\n\n this.interruptedFuture.resolve();\n }\n\n private async publishTrack(signal: AbortSignal) {\n const track = LocalAudioTrack.createAudioTrack('roomio_audio', this.audioSource);\n this.publication = await this.room.localParticipant?.publishTrack(\n track,\n new TrackPublishOptions({ source: TrackSource.SOURCE_MICROPHONE }),\n );\n\n if (signal.aborted) {\n return;\n }\n\n await this.publication?.waitForSubscription();\n\n if (!this.startedFuture.done) {\n this.startedFuture.resolve();\n }\n }\n\n async close() {\n // TODO(AJS-106): add republish track\n await this.audioSource.close();\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/voice/room_io/_output.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { RemoteParticipant } from '@livekit/rtc-node';\nimport {\n type AudioFrame,\n AudioSource,\n LocalAudioTrack,\n type LocalTrackPublication,\n type Participant,\n type RemoteTrackPublication,\n type Room,\n RoomEvent,\n type TextStreamWriter,\n TrackPublishOptions,\n TrackSource,\n} from '@livekit/rtc-node';\nimport {\n ATTRIBUTE_TRANSCRIPTION_FINAL,\n ATTRIBUTE_TRANSCRIPTION_SEGMENT_ID,\n ATTRIBUTE_TRANSCRIPTION_TRACK_ID,\n TOPIC_TRANSCRIPTION,\n} from '../../constants.js';\nimport { log } from '../../log.js';\nimport { Future, Task, shortuuid } from '../../utils.js';\nimport { AudioOutput, TextOutput } from '../io.js';\nimport { findMicrophoneTrackId } from '../transcription/index.js';\n\nabstract class BaseParticipantTranscriptionOutput extends TextOutput {\n protected room: Room;\n protected isDeltaStream: boolean;\n protected participantIdentity: string | null = null;\n protected trackId?: string;\n protected capturing: boolean = false;\n protected latestText: string = '';\n protected currentId: string = this.generateCurrentId();\n protected logger = log();\n\n constructor(room: Room, isDeltaStream: boolean, participant: Participant | string | null) {\n super();\n this.room = room;\n this.isDeltaStream = isDeltaStream;\n\n this.room.on(RoomEvent.TrackPublished, this.onTrackPublished);\n this.room.on(RoomEvent.LocalTrackPublished, this.onLocalTrackPublished);\n\n this.setParticipant(participant);\n }\n\n setParticipant(participant: Participant | string | null) {\n if (typeof participant === 'string' || participant === null) {\n this.participantIdentity = participant;\n } else {\n this.participantIdentity = participant.identity;\n }\n\n if (!this.participantIdentity) {\n return;\n }\n\n try {\n this.trackId = findMicrophoneTrackId(this.room, this.participantIdentity);\n } catch (error) {\n // track id is optional for TextStream when audio is not published\n }\n\n this.flush();\n this.resetState();\n }\n\n protected onTrackPublished = (track: RemoteTrackPublication, participant: RemoteParticipant) => {\n if (\n !this.participantIdentity ||\n participant.identity !== this.participantIdentity ||\n track.source !== TrackSource.SOURCE_MICROPHONE\n ) {\n return;\n }\n\n this.trackId = track.sid;\n };\n\n protected onLocalTrackPublished = (track: LocalTrackPublication) => {\n if (\n !this.participantIdentity ||\n this.participantIdentity !== this.room.localParticipant?.identity ||\n track.source !== TrackSource.SOURCE_MICROPHONE\n ) {\n return;\n }\n\n this.trackId = track.sid;\n };\n\n protected generateCurrentId(): string {\n return shortuuid('SG_');\n }\n\n protected resetState() {\n this.currentId = this.generateCurrentId();\n this.capturing = false;\n this.latestText = '';\n }\n\n async captureText(text: string) {\n if (!this.participantIdentity) {\n return;\n }\n\n this.latestText = text;\n await this.handleCaptureText(text);\n }\n\n flush() {\n if (!this.participantIdentity || !this.capturing) {\n return;\n }\n\n this.capturing = false;\n this.handleFlush();\n }\n\n protected abstract handleCaptureText(text: string): Promise<void>;\n protected abstract handleFlush(): void;\n}\n\nexport class ParticipantTranscriptionOutput extends BaseParticipantTranscriptionOutput {\n private writer: TextStreamWriter | null = null;\n private flushTask: Task<void> | null = null;\n\n protected async handleCaptureText(text: string): Promise<void> {\n if (this.flushTask && !this.flushTask.done) {\n await this.flushTask.result;\n }\n\n if (!this.capturing) {\n this.resetState();\n this.capturing = true;\n }\n\n try {\n if (this.room.isConnected) {\n if (this.isDeltaStream) {\n // reuse the existing writer\n if (this.writer === null) {\n this.writer = await this.createTextWriter();\n }\n await this.writer.write(text);\n } else {\n const tmpWriter = await this.createTextWriter();\n await tmpWriter.write(text);\n await tmpWriter.close();\n }\n }\n } catch (error) {\n this.logger.error(error, 'failed to publish transcription');\n }\n }\n\n protected handleFlush() {\n const currWriter = this.writer;\n this.writer = null;\n this.flushTask = Task.from((controller) => this.flushTaskImpl(currWriter, controller.signal));\n }\n\n private async createTextWriter(attributes?: Record<string, string>): Promise<TextStreamWriter> {\n if (!this.participantIdentity) {\n throw new Error('participantIdentity not found');\n }\n\n if (!this.room.localParticipant) {\n throw new Error('localParticipant not found');\n }\n\n if (!attributes) {\n attributes = {\n [ATTRIBUTE_TRANSCRIPTION_FINAL]: 'false',\n };\n if (this.trackId) {\n attributes[ATTRIBUTE_TRANSCRIPTION_TRACK_ID] = this.trackId;\n }\n }\n attributes[ATTRIBUTE_TRANSCRIPTION_SEGMENT_ID] = this.currentId;\n\n return await this.room.localParticipant.streamText({\n topic: TOPIC_TRANSCRIPTION,\n senderIdentity: this.participantIdentity,\n attributes,\n });\n }\n\n private async flushTaskImpl(writer: TextStreamWriter | null, signal: AbortSignal): Promise<void> {\n const attributes: Record<string, string> = {\n [ATTRIBUTE_TRANSCRIPTION_FINAL]: 'true',\n };\n if (this.trackId) {\n attributes[ATTRIBUTE_TRANSCRIPTION_TRACK_ID] = this.trackId;\n }\n\n const abortPromise = new Promise<void>((resolve) => {\n signal.addEventListener('abort', () => resolve());\n });\n\n try {\n if (this.room.isConnected) {\n if (this.isDeltaStream) {\n if (writer) {\n await Promise.race([writer.close(), abortPromise]);\n }\n } else {\n const tmpWriter = await Promise.race([this.createTextWriter(attributes), abortPromise]);\n if (signal.aborted || !tmpWriter) {\n return;\n }\n await Promise.race([tmpWriter.write(this.latestText), abortPromise]);\n if (signal.aborted) {\n return;\n }\n await Promise.race([tmpWriter.close(), abortPromise]);\n }\n }\n } catch (error) {\n this.logger.error(error, 'failed to publish transcription');\n }\n }\n}\n\nexport class ParticipantLegacyTranscriptionOutput extends BaseParticipantTranscriptionOutput {\n private pushedText: string = '';\n private flushTask: Promise<void> | null = null;\n\n protected async handleCaptureText(text: string): Promise<void> {\n if (!this.trackId) {\n return;\n }\n\n if (this.flushTask) {\n await this.flushTask;\n }\n\n if (!this.capturing) {\n this.resetState();\n this.capturing = true;\n }\n\n if (this.isDeltaStream) {\n this.pushedText += text;\n } else {\n this.pushedText = text;\n }\n\n await this.publishTranscription(this.currentId, this.pushedText, false);\n }\n\n protected handleFlush() {\n if (!this.trackId) {\n return;\n }\n\n this.flushTask = this.publishTranscription(this.currentId, this.pushedText, true);\n this.resetState();\n }\n\n async publishTranscription(id: string, text: string, final: boolean, signal?: AbortSignal) {\n if (!this.participantIdentity || !this.trackId) {\n return;\n }\n\n try {\n if (this.room.isConnected) {\n if (signal?.aborted) {\n return;\n }\n\n await this.room.localParticipant?.publishTranscription({\n participantIdentity: this.participantIdentity,\n trackSid: this.trackId,\n segments: [{ id, text, final, startTime: BigInt(0), endTime: BigInt(0), language: '' }],\n });\n }\n } catch (error) {\n this.logger.error(error, 'failed to publish transcription');\n }\n }\n\n protected resetState() {\n super.resetState();\n this.pushedText = '';\n }\n}\n\nexport class ParalellTextOutput extends TextOutput {\n /** @internal */\n _sinks: TextOutput[];\n\n constructor(sinks: TextOutput[], nextInChain?: TextOutput) {\n super(nextInChain);\n this._sinks = sinks;\n }\n\n async captureText(text: string) {\n await Promise.all(this._sinks.map((sink) => sink.captureText(text)));\n }\n\n flush() {\n for (const sink of this._sinks) {\n sink.flush();\n }\n }\n}\n\nexport interface AudioOutputOptions {\n sampleRate: number;\n numChannels: number;\n trackPublishOptions: TrackPublishOptions;\n queueSizeMs?: number;\n}\nexport class ParticipantAudioOutput extends AudioOutput {\n private room: Room;\n private options: AudioOutputOptions;\n private audioSource: AudioSource;\n private publication?: LocalTrackPublication;\n private flushTask?: Task<void>;\n\n /** Duration of audio pushed to the source, in seconds */\n private pushedDuration: number = 0;\n private startedFuture: Future<void> = new Future();\n private interruptedFuture: Future<void> = new Future();\n\n constructor(room: Room, options: AudioOutputOptions) {\n super(options.sampleRate);\n this.room = room;\n this.options = options;\n this.audioSource = new AudioSource(options.sampleRate, options.numChannels);\n }\n\n get subscribed(): boolean {\n return this.startedFuture.done;\n }\n\n async start(signal: AbortSignal): Promise<void> {\n await this.publishTrack(signal);\n }\n\n async captureFrame(frame: AudioFrame): Promise<void> {\n await this.startedFuture.await;\n\n super.captureFrame(frame);\n\n // TODO(AJS-102): use frame.durationMs once available in rtc-node\n this.pushedDuration += frame.samplesPerChannel / frame.sampleRate;\n await this.audioSource.captureFrame(frame);\n }\n\n private async waitForPlayoutTask(abortController: AbortController): Promise<void> {\n const abortFuture = new Future<boolean>();\n\n const resolveAbort = () => {\n if (!abortFuture.done) abortFuture.resolve(true);\n };\n\n abortController.signal.addEventListener('abort', resolveAbort);\n\n this.audioSource.waitForPlayout().finally(() => {\n abortController.signal.removeEventListener('abort', resolveAbort);\n if (!abortFuture.done) abortFuture.resolve(false);\n });\n\n const interrupted = await Promise.race([\n abortFuture.await,\n this.interruptedFuture.await.then(() => true),\n ]);\n\n let pushedDuration = this.pushedDuration;\n\n if (interrupted) {\n // Calculate actual played duration accounting for queued audio\n // Note: queuedDuration is in milliseconds, pushedDuration is in seconds\n pushedDuration = Math.max(this.pushedDuration - this.audioSource.queuedDuration / 1000, 0);\n this.audioSource.clearQueue();\n }\n\n this.pushedDuration = 0;\n this.interruptedFuture = new Future();\n this.onPlaybackFinished({\n playbackPosition: pushedDuration,\n interrupted,\n });\n }\n\n /**\n * Flush any buffered audio, marking the current playback/segment as complete\n */\n flush(): void {\n super.flush();\n\n if (!this.pushedDuration) {\n return;\n }\n\n if (this.flushTask && !this.flushTask.done) {\n this.logger.error('flush called while playback is in progress');\n this.flushTask.cancel();\n }\n\n this.flushTask = Task.from((controller) => this.waitForPlayoutTask(controller));\n }\n\n clearBuffer(): void {\n if (!this.pushedDuration) {\n return;\n }\n\n this.interruptedFuture.resolve();\n }\n\n private async publishTrack(signal: AbortSignal) {\n const track = LocalAudioTrack.createAudioTrack('roomio_audio', this.audioSource);\n this.publication = await this.room.localParticipant?.publishTrack(\n track,\n new TrackPublishOptions({ source: TrackSource.SOURCE_MICROPHONE }),\n );\n\n if (signal.aborted) {\n return;\n }\n\n await this.publication?.waitForSubscription();\n\n if (!this.startedFuture.done) {\n this.startedFuture.resolve();\n }\n }\n\n async close() {\n // TODO(AJS-106): add republish track\n await this.audioSource.close();\n }\n}\n"],"mappings":"AAIA;AAAA,EAEE;AAAA,EACA;AAAA,EAKA;AAAA,EAEA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,WAAW;AACpB,SAAS,QAAQ,MAAM,iBAAiB;AACxC,SAAS,aAAa,kBAAkB;AACxC,SAAS,6BAA6B;AAEtC,MAAe,2CAA2C,WAAW;AAAA,EACzD;AAAA,EACA;AAAA,EACA,sBAAqC;AAAA,EACrC;AAAA,EACA,YAAqB;AAAA,EACrB,aAAqB;AAAA,EACrB,YAAoB,KAAK,kBAAkB;AAAA,EAC3C,SAAS,IAAI;AAAA,EAEvB,YAAY,MAAY,eAAwB,aAA0C;AACxF,UAAM;AACN,SAAK,OAAO;AACZ,SAAK,gBAAgB;AAErB,SAAK,KAAK,GAAG,UAAU,gBAAgB,KAAK,gBAAgB;AAC5D,SAAK,KAAK,GAAG,UAAU,qBAAqB,KAAK,qBAAqB;AAEtE,SAAK,eAAe,WAAW;AAAA,EACjC;AAAA,EAEA,eAAe,aAA0C;AACvD,QAAI,OAAO,gBAAgB,YAAY,gBAAgB,MAAM;AAC3D,WAAK,sBAAsB;AAAA,IAC7B,OAAO;AACL,WAAK,sBAAsB,YAAY;AAAA,IACzC;AAEA,QAAI,CAAC,KAAK,qBAAqB;AAC7B;AAAA,IACF;AAEA,QAAI;AACF,WAAK,UAAU,sBAAsB,KAAK,MAAM,KAAK,mBAAmB;AAAA,IAC1E,SAAS,OAAO;AAAA,IAEhB;AAEA,SAAK,MAAM;AACX,SAAK,WAAW;AAAA,EAClB;AAAA,EAEU,mBAAmB,CAAC,OAA+B,gBAAmC;AAC9F,QACE,CAAC,KAAK,uBACN,YAAY,aAAa,KAAK,uBAC9B,MAAM,WAAW,YAAY,mBAC7B;AACA;AAAA,IACF;AAEA,SAAK,UAAU,MAAM;AAAA,EACvB;AAAA,EAEU,wBAAwB,CAAC,UAAiC;AAlFtE;AAmFI,QACE,CAAC,KAAK,uBACN,KAAK,0BAAwB,UAAK,KAAK,qBAAV,mBAA4B,aACzD,MAAM,WAAW,YAAY,mBAC7B;AACA;AAAA,IACF;AAEA,SAAK,UAAU,MAAM;AAAA,EACvB;AAAA,EAEU,oBAA4B;AACpC,WAAO,UAAU,KAAK;AAAA,EACxB;AAAA,EAEU,aAAa;AACrB,SAAK,YAAY,KAAK,kBAAkB;AACxC,SAAK,YAAY;AACjB,SAAK,aAAa;AAAA,EACpB;AAAA,EAEA,MAAM,YAAY,MAAc;AAC9B,QAAI,CAAC,KAAK,qBAAqB;AAC7B;AAAA,IACF;AAEA,SAAK,aAAa;AAClB,UAAM,KAAK,kBAAkB,IAAI;AAAA,EACnC;AAAA,EAEA,QAAQ;AACN,QAAI,CAAC,KAAK,uBAAuB,CAAC,KAAK,WAAW;AAChD;AAAA,IACF;AAEA,SAAK,YAAY;AACjB,SAAK,YAAY;AAAA,EACnB;AAIF;AAEO,MAAM,uCAAuC,mCAAmC;AAAA,EAC7E,SAAkC;AAAA,EAClC,YAA+B;AAAA,EAEvC,MAAgB,kBAAkB,MAA6B;AAC7D,QAAI,KAAK,aAAa,CAAC,KAAK,UAAU,MAAM;AAC1C,YAAM,KAAK,UAAU;AAAA,IACvB;AAEA,QAAI,CAAC,KAAK,WAAW;AACnB,WAAK,WAAW;AAChB,WAAK,YAAY;AAAA,IACnB;AAEA,QAAI;AACF,UAAI,KAAK,KAAK,aAAa;AACzB,YAAI,KAAK,eAAe;AAEtB,cAAI,KAAK,WAAW,MAAM;AACxB,iBAAK,SAAS,MAAM,KAAK,iBAAiB;AAAA,UAC5C;AACA,gBAAM,KAAK,OAAO,MAAM,IAAI;AAAA,QAC9B,OAAO;AACL,gBAAM,YAAY,MAAM,KAAK,iBAAiB;AAC9C,gBAAM,UAAU,MAAM,IAAI;AAC1B,gBAAM,UAAU,MAAM;AAAA,QACxB;AAAA,MACF;AAAA,IACF,SAAS,OAAO;AACd,WAAK,OAAO,MAAM,OAAO,iCAAiC;AAAA,IAC5D;AAAA,EACF;AAAA,EAEU,cAAc;AACtB,UAAM,aAAa,KAAK;AACxB,SAAK,SAAS;AACd,SAAK,YAAY,KAAK,KAAK,CAAC,eAAe,KAAK,cAAc,YAAY,WAAW,MAAM,CAAC;AAAA,EAC9F;AAAA,EAEA,MAAc,iBAAiB,YAAgE;AAC7F,QAAI,CAAC,KAAK,qBAAqB;AAC7B,YAAM,IAAI,MAAM,+BAA+B;AAAA,IACjD;AAEA,QAAI,CAAC,KAAK,KAAK,kBAAkB;AAC/B,YAAM,IAAI,MAAM,4BAA4B;AAAA,IAC9C;AAEA,QAAI,CAAC,YAAY;AACf,mBAAa;AAAA,QACX,CAAC,6BAA6B,GAAG;AAAA,MACnC;AACA,UAAI,KAAK,SAAS;AAChB,mBAAW,gCAAgC,IAAI,KAAK;AAAA,MACtD;AAAA,IACF;AACA,eAAW,kCAAkC,IAAI,KAAK;AAEtD,WAAO,MAAM,KAAK,KAAK,iBAAiB,WAAW;AAAA,MACjD,OAAO;AAAA,MACP,gBAAgB,KAAK;AAAA,MACrB;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,cAAc,QAAiC,QAAoC;AAC/F,UAAM,aAAqC;AAAA,MACzC,CAAC,6BAA6B,GAAG;AAAA,IACnC;AACA,QAAI,KAAK,SAAS;AAChB,iBAAW,gCAAgC,IAAI,KAAK;AAAA,IACtD;AAEA,UAAM,eAAe,IAAI,QAAc,CAAC,YAAY;AAClD,aAAO,iBAAiB,SAAS,MAAM,QAAQ,CAAC;AAAA,IAClD,CAAC;AAED,QAAI;AACF,UAAI,KAAK,KAAK,aAAa;AACzB,YAAI,KAAK,eAAe;AACtB,cAAI,QAAQ;AACV,kBAAM,QAAQ,KAAK,CAAC,OAAO,MAAM,GAAG,YAAY,CAAC;AAAA,UACnD;AAAA,QACF,OAAO;AACL,gBAAM,YAAY,MAAM,QAAQ,KAAK,CAAC,KAAK,iBAAiB,UAAU,GAAG,YAAY,CAAC;AACtF,cAAI,OAAO,WAAW,CAAC,WAAW;AAChC;AAAA,UACF;AACA,gBAAM,QAAQ,KAAK,CAAC,UAAU,MAAM,KAAK,UAAU,GAAG,YAAY,CAAC;AACnE,cAAI,OAAO,SAAS;AAClB;AAAA,UACF;AACA,gBAAM,QAAQ,KAAK,CAAC,UAAU,MAAM,GAAG,YAAY,CAAC;AAAA,QACtD;AAAA,MACF;AAAA,IACF,SAAS,OAAO;AACd,WAAK,OAAO,MAAM,OAAO,iCAAiC;AAAA,IAC5D;AAAA,EACF;AACF;AAEO,MAAM,6CAA6C,mCAAmC;AAAA,EACnF,aAAqB;AAAA,EACrB,YAAkC;AAAA,EAE1C,MAAgB,kBAAkB,MAA6B;AAC7D,QAAI,CAAC,KAAK,SAAS;AACjB;AAAA,IACF;AAEA,QAAI,KAAK,WAAW;AAClB,YAAM,KAAK;AAAA,IACb;AAEA,QAAI,CAAC,KAAK,WAAW;AACnB,WAAK,WAAW;AAChB,WAAK,YAAY;AAAA,IACnB;AAEA,QAAI,KAAK,eAAe;AACtB,WAAK,cAAc;AAAA,IACrB,OAAO;AACL,WAAK,aAAa;AAAA,IACpB;AAEA,UAAM,KAAK,qBAAqB,KAAK,WAAW,KAAK,YAAY,KAAK;AAAA,EACxE;AAAA,EAEU,cAAc;AACtB,QAAI,CAAC,KAAK,SAAS;AACjB;AAAA,IACF;AAEA,SAAK,YAAY,KAAK,qBAAqB,KAAK,WAAW,KAAK,YAAY,IAAI;AAChF,SAAK,WAAW;AAAA,EAClB;AAAA,EAEA,MAAM,qBAAqB,IAAY,MAAc,OAAgB,QAAsB;AAvQ7F;AAwQI,QAAI,CAAC,KAAK,uBAAuB,CAAC,KAAK,SAAS;AAC9C;AAAA,IACF;AAEA,QAAI;AACF,UAAI,KAAK,KAAK,aAAa;AACzB,YAAI,iCAAQ,SAAS;AACnB;AAAA,QACF;AAEA,gBAAM,UAAK,KAAK,qBAAV,mBAA4B,qBAAqB;AAAA,UACrD,qBAAqB,KAAK;AAAA,UAC1B,UAAU,KAAK;AAAA,UACf,UAAU,CAAC,EAAE,IAAI,MAAM,OAAO,WAAW,OAAO,CAAC,GAAG,SAAS,OAAO,CAAC,GAAG,UAAU,GAAG,CAAC;AAAA,QACxF;AAAA,MACF;AAAA,IACF,SAAS,OAAO;AACd,WAAK,OAAO,MAAM,OAAO,iCAAiC;AAAA,IAC5D;AAAA,EACF;AAAA,EAEU,aAAa;AACrB,UAAM,WAAW;AACjB,SAAK,aAAa;AAAA,EACpB;AACF;AAEO,MAAM,2BAA2B,WAAW;AAAA;AAAA,EAEjD;AAAA,EAEA,YAAY,OAAqB,aAA0B;AACzD,UAAM,WAAW;AACjB,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,MAAM,YAAY,MAAc;AAC9B,UAAM,QAAQ,IAAI,KAAK,OAAO,IAAI,CAAC,SAAS,KAAK,YAAY,IAAI,CAAC,CAAC;AAAA,EACrE;AAAA,EAEA,QAAQ;AACN,eAAW,QAAQ,KAAK,QAAQ;AAC9B,WAAK,MAAM;AAAA,IACb;AAAA,EACF;AACF;AAQO,MAAM,+BAA+B,YAAY;AAAA,EAC9C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAGA,iBAAyB;AAAA,EACzB,gBAA8B,IAAI,OAAO;AAAA,EACzC,oBAAkC,IAAI,OAAO;AAAA,EAErD,YAAY,MAAY,SAA6B;AACnD,UAAM,QAAQ,UAAU;AACxB,SAAK,OAAO;AACZ,SAAK,UAAU;AACf,SAAK,cAAc,IAAI,YAAY,QAAQ,YAAY,QAAQ,WAAW;AAAA,EAC5E;AAAA,EAEA,IAAI,aAAsB;AACxB,WAAO,KAAK,cAAc;AAAA,EAC5B;AAAA,EAEA,MAAM,MAAM,QAAoC;AAC9C,UAAM,KAAK,aAAa,MAAM;AAAA,EAChC;AAAA,EAEA,MAAM,aAAa,OAAkC;AACnD,UAAM,KAAK,cAAc;AAEzB,UAAM,aAAa,KAAK;AAGxB,SAAK,kBAAkB,MAAM,oBAAoB,MAAM;AACvD,UAAM,KAAK,YAAY,aAAa,KAAK;AAAA,EAC3C;AAAA,EAEA,MAAc,mBAAmB,iBAAiD;AAChF,UAAM,cAAc,IAAI,OAAgB;AAExC,UAAM,eAAe,MAAM;AACzB,UAAI,CAAC,YAAY,KAAM,aAAY,QAAQ,IAAI;AAAA,IACjD;AAEA,oBAAgB,OAAO,iBAAiB,SAAS,YAAY;AAE7D,SAAK,YAAY,eAAe,EAAE,QAAQ,MAAM;AAC9C,sBAAgB,OAAO,oBAAoB,SAAS,YAAY;AAChE,UAAI,CAAC,YAAY,KAAM,aAAY,QAAQ,KAAK;AAAA,IAClD,CAAC;AAED,UAAM,cAAc,MAAM,QAAQ,KAAK;AAAA,MACrC,YAAY;AAAA,MACZ,KAAK,kBAAkB,MAAM,KAAK,MAAM,IAAI;AAAA,IAC9C,CAAC;AAED,QAAI,iBAAiB,KAAK;AAE1B,QAAI,aAAa;AAGf,uBAAiB,KAAK,IAAI,KAAK,iBAAiB,KAAK,YAAY,iBAAiB,KAAM,CAAC;AACzF,WAAK,YAAY,WAAW;AAAA,IAC9B;AAEA,SAAK,iBAAiB;AACtB,SAAK,oBAAoB,IAAI,OAAO;AACpC,SAAK,mBAAmB;AAAA,MACtB,kBAAkB;AAAA,MAClB;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACZ,UAAM,MAAM;AAEZ,QAAI,CAAC,KAAK,gBAAgB;AACxB;AAAA,IACF;AAEA,QAAI,KAAK,aAAa,CAAC,KAAK,UAAU,MAAM;AAC1C,WAAK,OAAO,MAAM,4CAA4C;AAC9D,WAAK,UAAU,OAAO;AAAA,IACxB;AAEA,SAAK,YAAY,KAAK,KAAK,CAAC,eAAe,KAAK,mBAAmB,UAAU,CAAC;AAAA,EAChF;AAAA,EAEA,cAAoB;AAClB,QAAI,CAAC,KAAK,gBAAgB;AACxB;AAAA,IACF;AAEA,SAAK,kBAAkB,QAAQ;AAAA,EACjC;AAAA,EAEA,MAAc,aAAa,QAAqB;AAhalD;AAiaI,UAAM,QAAQ,gBAAgB,iBAAiB,gBAAgB,KAAK,WAAW;AAC/E,SAAK,cAAc,QAAM,UAAK,KAAK,qBAAV,mBAA4B;AAAA,MACnD;AAAA,MACA,IAAI,oBAAoB,EAAE,QAAQ,YAAY,kBAAkB,CAAC;AAAA;AAGnE,QAAI,OAAO,SAAS;AAClB;AAAA,IACF;AAEA,YAAM,UAAK,gBAAL,mBAAkB;AAExB,QAAI,CAAC,KAAK,cAAc,MAAM;AAC5B,WAAK,cAAc,QAAQ;AAAA,IAC7B;AAAA,EACF;AAAA,EAEA,MAAM,QAAQ;AAEZ,UAAM,KAAK,YAAY,MAAM;AAAA,EAC/B;AACF;","names":[]}
package/dist/worker.cjs CHANGED
@@ -562,6 +562,7 @@ class AgentServer {
562
562
  ws.removeAllListeners();
563
563
  }
564
564
  async #availability(msg) {
565
+ var _a;
565
566
  let answered = false;
566
567
  const onReject = async () => {
567
568
  answered = true;
@@ -579,7 +580,7 @@ class AgentServer {
579
580
  );
580
581
  };
581
582
  const onAccept = async (args) => {
582
- var _a;
583
+ var _a2;
583
584
  answered = true;
584
585
  this.event.emit(
585
586
  "worker_msg",
@@ -602,7 +603,7 @@ class AgentServer {
602
603
  this.#logger.child({ req }).warn(`assignment for job ${req.id} timed out`);
603
604
  return;
604
605
  }, ASSIGNMENT_TIMEOUT);
605
- const asgn = await ((_a = this.#pending[req.id]) == null ? void 0 : _a.promise.then(async (asgn2) => {
606
+ const asgn = await ((_a2 = this.#pending[req.id]) == null ? void 0 : _a2.promise.then(async (asgn2) => {
606
607
  clearTimeout(timer);
607
608
  return asgn2;
608
609
  }));
@@ -619,7 +620,7 @@ class AgentServer {
619
620
  }
620
621
  };
621
622
  const req = new import_job.JobRequest(msg.job, onReject, onAccept);
622
- this.#logger.child({ job: msg.job, resuming: msg.resuming, agentName: this.#opts.agentName }).info("received job request");
623
+ this.#logger.child({ jobId: (_a = msg.job) == null ? void 0 : _a.id, resuming: msg.resuming, agentName: this.#opts.agentName }).info("received job request");
623
624
  const jobRequestTask = async () => {
624
625
  try {
625
626
  await this.#opts.requestFunc(req);