@livekit/agents 1.0.45 → 1.0.47

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 (225) hide show
  1. package/dist/cli.cjs +14 -20
  2. package/dist/cli.cjs.map +1 -1
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +14 -20
  5. package/dist/cli.js.map +1 -1
  6. package/dist/ipc/job_proc_lazy_main.cjs +14 -5
  7. package/dist/ipc/job_proc_lazy_main.cjs.map +1 -1
  8. package/dist/ipc/job_proc_lazy_main.js +14 -5
  9. package/dist/ipc/job_proc_lazy_main.js.map +1 -1
  10. package/dist/llm/chat_context.cjs +19 -0
  11. package/dist/llm/chat_context.cjs.map +1 -1
  12. package/dist/llm/chat_context.d.cts +4 -0
  13. package/dist/llm/chat_context.d.ts +4 -0
  14. package/dist/llm/chat_context.d.ts.map +1 -1
  15. package/dist/llm/chat_context.js +19 -0
  16. package/dist/llm/chat_context.js.map +1 -1
  17. package/dist/llm/provider_format/index.cjs +2 -0
  18. package/dist/llm/provider_format/index.cjs.map +1 -1
  19. package/dist/llm/provider_format/index.d.cts +1 -1
  20. package/dist/llm/provider_format/index.d.ts +1 -1
  21. package/dist/llm/provider_format/index.d.ts.map +1 -1
  22. package/dist/llm/provider_format/index.js +6 -1
  23. package/dist/llm/provider_format/index.js.map +1 -1
  24. package/dist/llm/provider_format/openai.cjs +82 -2
  25. package/dist/llm/provider_format/openai.cjs.map +1 -1
  26. package/dist/llm/provider_format/openai.d.cts +1 -0
  27. package/dist/llm/provider_format/openai.d.ts +1 -0
  28. package/dist/llm/provider_format/openai.d.ts.map +1 -1
  29. package/dist/llm/provider_format/openai.js +80 -1
  30. package/dist/llm/provider_format/openai.js.map +1 -1
  31. package/dist/llm/provider_format/openai.test.cjs +326 -0
  32. package/dist/llm/provider_format/openai.test.cjs.map +1 -1
  33. package/dist/llm/provider_format/openai.test.js +327 -1
  34. package/dist/llm/provider_format/openai.test.js.map +1 -1
  35. package/dist/llm/provider_format/utils.cjs +4 -3
  36. package/dist/llm/provider_format/utils.cjs.map +1 -1
  37. package/dist/llm/provider_format/utils.d.ts.map +1 -1
  38. package/dist/llm/provider_format/utils.js +4 -3
  39. package/dist/llm/provider_format/utils.js.map +1 -1
  40. package/dist/llm/realtime.cjs.map +1 -1
  41. package/dist/llm/realtime.d.cts +1 -0
  42. package/dist/llm/realtime.d.ts +1 -0
  43. package/dist/llm/realtime.d.ts.map +1 -1
  44. package/dist/llm/realtime.js.map +1 -1
  45. package/dist/log.cjs +5 -2
  46. package/dist/log.cjs.map +1 -1
  47. package/dist/log.d.ts.map +1 -1
  48. package/dist/log.js +5 -2
  49. package/dist/log.js.map +1 -1
  50. package/dist/stream/deferred_stream.cjs +15 -6
  51. package/dist/stream/deferred_stream.cjs.map +1 -1
  52. package/dist/stream/deferred_stream.d.ts.map +1 -1
  53. package/dist/stream/deferred_stream.js +15 -6
  54. package/dist/stream/deferred_stream.js.map +1 -1
  55. package/dist/stream/index.cjs +3 -0
  56. package/dist/stream/index.cjs.map +1 -1
  57. package/dist/stream/index.d.cts +1 -0
  58. package/dist/stream/index.d.ts +1 -0
  59. package/dist/stream/index.d.ts.map +1 -1
  60. package/dist/stream/index.js +2 -0
  61. package/dist/stream/index.js.map +1 -1
  62. package/dist/stream/multi_input_stream.cjs +139 -0
  63. package/dist/stream/multi_input_stream.cjs.map +1 -0
  64. package/dist/stream/multi_input_stream.d.cts +55 -0
  65. package/dist/stream/multi_input_stream.d.ts +55 -0
  66. package/dist/stream/multi_input_stream.d.ts.map +1 -0
  67. package/dist/stream/multi_input_stream.js +115 -0
  68. package/dist/stream/multi_input_stream.js.map +1 -0
  69. package/dist/stream/multi_input_stream.test.cjs +340 -0
  70. package/dist/stream/multi_input_stream.test.cjs.map +1 -0
  71. package/dist/stream/multi_input_stream.test.js +339 -0
  72. package/dist/stream/multi_input_stream.test.js.map +1 -0
  73. package/dist/telemetry/trace_types.cjs +42 -0
  74. package/dist/telemetry/trace_types.cjs.map +1 -1
  75. package/dist/telemetry/trace_types.d.cts +14 -0
  76. package/dist/telemetry/trace_types.d.ts +14 -0
  77. package/dist/telemetry/trace_types.d.ts.map +1 -1
  78. package/dist/telemetry/trace_types.js +28 -0
  79. package/dist/telemetry/trace_types.js.map +1 -1
  80. package/dist/utils.cjs +44 -2
  81. package/dist/utils.cjs.map +1 -1
  82. package/dist/utils.d.cts +8 -0
  83. package/dist/utils.d.ts +8 -0
  84. package/dist/utils.d.ts.map +1 -1
  85. package/dist/utils.js +44 -2
  86. package/dist/utils.js.map +1 -1
  87. package/dist/utils.test.cjs +71 -0
  88. package/dist/utils.test.cjs.map +1 -1
  89. package/dist/utils.test.js +71 -0
  90. package/dist/utils.test.js.map +1 -1
  91. package/dist/version.cjs +1 -1
  92. package/dist/version.cjs.map +1 -1
  93. package/dist/version.d.cts +1 -1
  94. package/dist/version.d.ts +1 -1
  95. package/dist/version.d.ts.map +1 -1
  96. package/dist/version.js +1 -1
  97. package/dist/version.js.map +1 -1
  98. package/dist/voice/agent.cjs +144 -12
  99. package/dist/voice/agent.cjs.map +1 -1
  100. package/dist/voice/agent.d.cts +29 -4
  101. package/dist/voice/agent.d.ts +29 -4
  102. package/dist/voice/agent.d.ts.map +1 -1
  103. package/dist/voice/agent.js +140 -11
  104. package/dist/voice/agent.js.map +1 -1
  105. package/dist/voice/agent.test.cjs +120 -0
  106. package/dist/voice/agent.test.cjs.map +1 -1
  107. package/dist/voice/agent.test.js +122 -2
  108. package/dist/voice/agent.test.js.map +1 -1
  109. package/dist/voice/agent_activity.cjs +402 -292
  110. package/dist/voice/agent_activity.cjs.map +1 -1
  111. package/dist/voice/agent_activity.d.cts +35 -7
  112. package/dist/voice/agent_activity.d.ts +35 -7
  113. package/dist/voice/agent_activity.d.ts.map +1 -1
  114. package/dist/voice/agent_activity.js +402 -287
  115. package/dist/voice/agent_activity.js.map +1 -1
  116. package/dist/voice/agent_session.cjs +156 -44
  117. package/dist/voice/agent_session.cjs.map +1 -1
  118. package/dist/voice/agent_session.d.cts +22 -9
  119. package/dist/voice/agent_session.d.ts +22 -9
  120. package/dist/voice/agent_session.d.ts.map +1 -1
  121. package/dist/voice/agent_session.js +156 -44
  122. package/dist/voice/agent_session.js.map +1 -1
  123. package/dist/voice/audio_recognition.cjs +89 -36
  124. package/dist/voice/audio_recognition.cjs.map +1 -1
  125. package/dist/voice/audio_recognition.d.cts +22 -1
  126. package/dist/voice/audio_recognition.d.ts +22 -1
  127. package/dist/voice/audio_recognition.d.ts.map +1 -1
  128. package/dist/voice/audio_recognition.js +93 -36
  129. package/dist/voice/audio_recognition.js.map +1 -1
  130. package/dist/voice/audio_recognition_span.test.cjs +233 -0
  131. package/dist/voice/audio_recognition_span.test.cjs.map +1 -0
  132. package/dist/voice/audio_recognition_span.test.js +232 -0
  133. package/dist/voice/audio_recognition_span.test.js.map +1 -0
  134. package/dist/voice/generation.cjs +39 -19
  135. package/dist/voice/generation.cjs.map +1 -1
  136. package/dist/voice/generation.d.ts.map +1 -1
  137. package/dist/voice/generation.js +44 -20
  138. package/dist/voice/generation.js.map +1 -1
  139. package/dist/voice/index.cjs +2 -0
  140. package/dist/voice/index.cjs.map +1 -1
  141. package/dist/voice/index.d.cts +1 -1
  142. package/dist/voice/index.d.ts +1 -1
  143. package/dist/voice/index.d.ts.map +1 -1
  144. package/dist/voice/index.js +2 -1
  145. package/dist/voice/index.js.map +1 -1
  146. package/dist/voice/io.cjs +6 -3
  147. package/dist/voice/io.cjs.map +1 -1
  148. package/dist/voice/io.d.cts +3 -2
  149. package/dist/voice/io.d.ts +3 -2
  150. package/dist/voice/io.d.ts.map +1 -1
  151. package/dist/voice/io.js +6 -3
  152. package/dist/voice/io.js.map +1 -1
  153. package/dist/voice/recorder_io/recorder_io.cjs +3 -1
  154. package/dist/voice/recorder_io/recorder_io.cjs.map +1 -1
  155. package/dist/voice/recorder_io/recorder_io.d.ts.map +1 -1
  156. package/dist/voice/recorder_io/recorder_io.js +3 -1
  157. package/dist/voice/recorder_io/recorder_io.js.map +1 -1
  158. package/dist/voice/room_io/_input.cjs +17 -17
  159. package/dist/voice/room_io/_input.cjs.map +1 -1
  160. package/dist/voice/room_io/_input.d.cts +2 -2
  161. package/dist/voice/room_io/_input.d.ts +2 -2
  162. package/dist/voice/room_io/_input.d.ts.map +1 -1
  163. package/dist/voice/room_io/_input.js +7 -6
  164. package/dist/voice/room_io/_input.js.map +1 -1
  165. package/dist/voice/room_io/room_io.cjs +9 -0
  166. package/dist/voice/room_io/room_io.cjs.map +1 -1
  167. package/dist/voice/room_io/room_io.d.cts +3 -1
  168. package/dist/voice/room_io/room_io.d.ts +3 -1
  169. package/dist/voice/room_io/room_io.d.ts.map +1 -1
  170. package/dist/voice/room_io/room_io.js +9 -0
  171. package/dist/voice/room_io/room_io.js.map +1 -1
  172. package/dist/voice/speech_handle.cjs +7 -1
  173. package/dist/voice/speech_handle.cjs.map +1 -1
  174. package/dist/voice/speech_handle.d.cts +2 -0
  175. package/dist/voice/speech_handle.d.ts +2 -0
  176. package/dist/voice/speech_handle.d.ts.map +1 -1
  177. package/dist/voice/speech_handle.js +8 -2
  178. package/dist/voice/speech_handle.js.map +1 -1
  179. package/dist/voice/testing/run_result.cjs +66 -15
  180. package/dist/voice/testing/run_result.cjs.map +1 -1
  181. package/dist/voice/testing/run_result.d.cts +14 -3
  182. package/dist/voice/testing/run_result.d.ts +14 -3
  183. package/dist/voice/testing/run_result.d.ts.map +1 -1
  184. package/dist/voice/testing/run_result.js +66 -15
  185. package/dist/voice/testing/run_result.js.map +1 -1
  186. package/dist/voice/utils.cjs +47 -0
  187. package/dist/voice/utils.cjs.map +1 -0
  188. package/dist/voice/utils.d.cts +4 -0
  189. package/dist/voice/utils.d.ts +4 -0
  190. package/dist/voice/utils.d.ts.map +1 -0
  191. package/dist/voice/utils.js +23 -0
  192. package/dist/voice/utils.js.map +1 -0
  193. package/package.json +1 -1
  194. package/src/cli.ts +20 -33
  195. package/src/ipc/job_proc_lazy_main.ts +16 -5
  196. package/src/llm/chat_context.ts +35 -0
  197. package/src/llm/provider_format/index.ts +7 -2
  198. package/src/llm/provider_format/openai.test.ts +385 -1
  199. package/src/llm/provider_format/openai.ts +103 -0
  200. package/src/llm/provider_format/utils.ts +6 -4
  201. package/src/llm/realtime.ts +1 -0
  202. package/src/log.ts +5 -2
  203. package/src/stream/deferred_stream.ts +17 -6
  204. package/src/stream/index.ts +1 -0
  205. package/src/stream/multi_input_stream.test.ts +540 -0
  206. package/src/stream/multi_input_stream.ts +172 -0
  207. package/src/telemetry/trace_types.ts +18 -0
  208. package/src/utils.test.ts +87 -0
  209. package/src/utils.ts +52 -2
  210. package/src/version.ts +1 -1
  211. package/src/voice/agent.test.ts +140 -2
  212. package/src/voice/agent.ts +189 -10
  213. package/src/voice/agent_activity.ts +449 -286
  214. package/src/voice/agent_session.ts +195 -51
  215. package/src/voice/audio_recognition.ts +118 -38
  216. package/src/voice/audio_recognition_span.test.ts +261 -0
  217. package/src/voice/generation.ts +52 -23
  218. package/src/voice/index.ts +1 -1
  219. package/src/voice/io.ts +7 -4
  220. package/src/voice/recorder_io/recorder_io.ts +2 -1
  221. package/src/voice/room_io/_input.ts +11 -7
  222. package/src/voice/room_io/room_io.ts +12 -0
  223. package/src/voice/speech_handle.ts +9 -2
  224. package/src/voice/testing/run_result.ts +81 -23
  225. package/src/voice/utils.ts +29 -0
@@ -1,4 +1,4 @@
1
- import { type AudioFrame, type FrameProcessor, type NoiseCancellationOptions, ParticipantKind, type RemoteParticipant, type Room, type TextStreamInfo, TrackPublishOptions } from '@livekit/rtc-node';
1
+ import { type AudioFrame, type FrameProcessor, type NoiseCancellationOptions, type Participant, ParticipantKind, type RemoteParticipant, type Room, type TextStreamInfo, TrackPublishOptions } from '@livekit/rtc-node';
2
2
  import { type AgentSession } from '../agent_session.js';
3
3
  import type { AudioOutput, TextOutput } from '../io.js';
4
4
  export interface TextInputEvent {
@@ -85,6 +85,8 @@ export declare class RoomIO {
85
85
  get audioOutput(): AudioOutput | undefined;
86
86
  get transcriptionOutput(): TextOutput | undefined;
87
87
  get isParticipantAvailable(): boolean;
88
+ get linkedParticipant(): RemoteParticipant | undefined;
89
+ get localParticipant(): Participant | undefined;
88
90
  /** Switch to a different participant */
89
91
  setParticipant(participantIdentity: string | null): void;
90
92
  unsetParticipant(): void;
@@ -1,4 +1,4 @@
1
- import { type AudioFrame, type FrameProcessor, type NoiseCancellationOptions, ParticipantKind, type RemoteParticipant, type Room, type TextStreamInfo, TrackPublishOptions } from '@livekit/rtc-node';
1
+ import { type AudioFrame, type FrameProcessor, type NoiseCancellationOptions, type Participant, ParticipantKind, type RemoteParticipant, type Room, type TextStreamInfo, TrackPublishOptions } from '@livekit/rtc-node';
2
2
  import { type AgentSession } from '../agent_session.js';
3
3
  import type { AudioOutput, TextOutput } from '../io.js';
4
4
  export interface TextInputEvent {
@@ -85,6 +85,8 @@ export declare class RoomIO {
85
85
  get audioOutput(): AudioOutput | undefined;
86
86
  get transcriptionOutput(): TextOutput | undefined;
87
87
  get isParticipantAvailable(): boolean;
88
+ get linkedParticipant(): RemoteParticipant | undefined;
89
+ get localParticipant(): Participant | undefined;
88
90
  /** Switch to a different participant */
89
91
  setParticipant(participantIdentity: string | null): void;
90
92
  unsetParticipant(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"room_io.d.ts","sourceRoot":"","sources":["../../../src/voice/room_io/room_io.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"room_io.d.ts","sourceRoot":"","sources":["../../../src/voice/room_io/room_io.ts"],"names":[],"mappings":"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"}
@@ -241,6 +241,15 @@ class RoomIO {
241
241
  get isParticipantAvailable() {
242
242
  return this.participantAvailableFuture.done;
243
243
  }
244
+ get linkedParticipant() {
245
+ if (!this.isParticipantAvailable) {
246
+ return void 0;
247
+ }
248
+ return this.participantAvailableFuture.result;
249
+ }
250
+ get localParticipant() {
251
+ return this.room.localParticipant ?? void 0;
252
+ }
244
253
  /** Switch to a different participant */
245
254
  setParticipant(participantIdentity) {
246
255
  var _a;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/voice/room_io/room_io.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport {\n type AudioFrame,\n ConnectionState,\n DisconnectReason,\n type FrameProcessor,\n type NoiseCancellationOptions,\n type Participant,\n ParticipantKind,\n type RemoteParticipant,\n type Room,\n RoomEvent,\n type TextStreamInfo,\n type TextStreamReader,\n TrackPublishOptions,\n TrackSource,\n} from '@livekit/rtc-node';\nimport type { WritableStreamDefaultWriter } from 'node:stream/web';\nimport { ATTRIBUTE_PUBLISH_ON_BEHALF, TOPIC_CHAT } from '../../constants.js';\nimport { log } from '../../log.js';\nimport { IdentityTransform } from '../../stream/identity_transform.js';\nimport { Future, Task } from '../../utils.js';\nimport { type AgentSession } from '../agent_session.js';\nimport {\n AgentSessionEventTypes,\n type AgentStateChangedEvent,\n CloseReason,\n type UserInputTranscribedEvent,\n} from '../events.js';\nimport type { AudioOutput, TextOutput } from '../io.js';\nimport { TranscriptionSynchronizer } from '../transcription/synchronizer.js';\nimport { ParticipantAudioInputStream } from './_input.js';\nimport {\n ParalellTextOutput,\n ParticipantAudioOutput,\n ParticipantLegacyTranscriptionOutput,\n ParticipantTranscriptionOutput,\n} from './_output.js';\n\nexport interface TextInputEvent {\n text: string;\n info: TextStreamInfo;\n participant: RemoteParticipant;\n}\n\nexport type TextInputCallback = (sess: AgentSession, ev: TextInputEvent) => void | Promise<void>;\n\nconst DEFAULT_TEXT_INPUT_CALLBACK: TextInputCallback = (sess: AgentSession, ev: TextInputEvent) => {\n sess.interrupt();\n sess.generateReply({ userInput: ev.text });\n};\n\nconst DEFAULT_PARTICIPANT_KINDS: ParticipantKind[] = [\n ParticipantKind.CONNECTOR,\n ParticipantKind.SIP,\n ParticipantKind.STANDARD,\n];\n\nconst CLOSE_ON_DISCONNECT_REASONS: DisconnectReason[] = [\n DisconnectReason.CLIENT_INITIATED,\n DisconnectReason.ROOM_DELETED,\n DisconnectReason.USER_REJECTED,\n];\n\nexport interface RoomInputOptions {\n audioSampleRate: number;\n audioNumChannels: number;\n /** If not given, default to True. */\n textEnabled: boolean;\n /** If not given, default to True. */\n audioEnabled: boolean;\n /** If not given, default to False. */\n videoEnabled: boolean;\n /** The participant to link to. If not provided, link to the first participant.\n Can be overridden by the `participant` argument of RoomIO constructor or `set_participant`.\n */\n participantIdentity?: string;\n noiseCancellation?: NoiseCancellationOptions | FrameProcessor<AudioFrame>;\n textInputCallback?: TextInputCallback;\n /** Participant kinds accepted for auto subscription. If not provided,\n accept `DEFAULT_PARTICIPANT_KINDS`\n */\n participantKinds?: ParticipantKind[];\n /** Close the AgentSession if the linked participant disconnects with reasons in\n CLIENT_INITIATED, ROOM_DELETED, or USER_REJECTED.\n */\n closeOnDisconnect: boolean;\n}\n\nexport interface RoomOutputOptions {\n /** If not given, default to True. */\n transcriptionEnabled: boolean;\n /** If not given, default to True. */\n audioEnabled: boolean;\n audioSampleRate: number;\n audioNumChannels: number;\n /** False to disable transcription synchronization with audio output.\n Otherwise, transcription is emitted as quickly as available.\n */\n syncTranscription: boolean;\n /** The name of the audio track to publish. If not provided, default to \"roomio_audio\".\n */\n audioPublishOptions: TrackPublishOptions;\n}\n\nconst DEFAULT_ROOM_INPUT_OPTIONS: RoomInputOptions = {\n audioSampleRate: 24000,\n audioNumChannels: 1,\n textEnabled: true,\n audioEnabled: true,\n videoEnabled: false,\n textInputCallback: DEFAULT_TEXT_INPUT_CALLBACK,\n closeOnDisconnect: true,\n};\n\nconst DEFAULT_ROOM_OUTPUT_OPTIONS: RoomOutputOptions = {\n audioSampleRate: 24000,\n audioNumChannels: 1,\n transcriptionEnabled: true,\n audioEnabled: true,\n syncTranscription: true,\n audioPublishOptions: new TrackPublishOptions({ source: TrackSource.SOURCE_MICROPHONE }),\n};\n\nexport class RoomIO {\n private agentSession: AgentSession;\n private room: Room;\n private inputOptions: RoomInputOptions;\n private outputOptions: RoomOutputOptions;\n\n private audioInput?: ParticipantAudioInputStream;\n private participantAudioOutput?: ParticipantAudioOutput;\n private userTranscriptOutput?: ParalellTextOutput;\n private agentTranscriptOutput?: ParalellTextOutput;\n private transcriptionSynchronizer?: TranscriptionSynchronizer;\n private participantIdentity: string | null = null;\n\n private participantAvailableFuture: Future<RemoteParticipant> = new Future();\n private roomConnectedFuture: Future<void> = new Future();\n\n // Use stream API for transcript queue\n private userTranscriptStream = new IdentityTransform<UserInputTranscribedEvent>();\n private userTranscriptWriter: WritableStreamDefaultWriter<UserInputTranscribedEvent>;\n private forwardUserTranscriptTask?: Task<void>;\n private initTask?: Task<void>;\n\n private textStreamHandlerRegistered = false;\n\n private logger = log();\n\n constructor({\n agentSession,\n room,\n participant = null,\n inputOptions,\n outputOptions,\n }: {\n agentSession: AgentSession;\n room: Room;\n participant?: RemoteParticipant | string | null;\n inputOptions?: Partial<RoomInputOptions>;\n outputOptions?: Partial<RoomOutputOptions>;\n }) {\n this.agentSession = agentSession;\n this.room = room;\n this.inputOptions = { ...DEFAULT_ROOM_INPUT_OPTIONS, ...inputOptions };\n this.outputOptions = { ...DEFAULT_ROOM_OUTPUT_OPTIONS, ...outputOptions };\n\n this.userTranscriptWriter = this.userTranscriptStream.writable.getWriter();\n\n this.participantIdentity = participant\n ? typeof participant === 'string'\n ? participant\n : participant.identity\n : this.inputOptions.participantIdentity ?? null;\n }\n private async init(signal: AbortSignal): Promise<void> {\n await this.roomConnectedFuture.await;\n\n for (const participant of this.room.remoteParticipants.values()) {\n this.onParticipantConnected(participant);\n }\n if (signal.aborted) {\n return;\n }\n\n const participant = await this.participantAvailableFuture.await;\n this.setParticipant(participant.identity);\n\n // init agent outputs\n this.updateTranscriptionOutput({\n output: this.agentTranscriptOutput,\n participant: this.room.localParticipant?.identity ?? null,\n });\n\n await this.participantAudioOutput?.start(signal);\n }\n\n private onConnectionStateChanged = (state: ConnectionState) => {\n this.logger.debug({ state }, 'connection state changed');\n if (\n state === ConnectionState.CONN_CONNECTED &&\n this.room.isConnected &&\n !this.roomConnectedFuture.done\n ) {\n this.roomConnectedFuture.resolve();\n }\n };\n\n private onParticipantConnected = (participant: RemoteParticipant) => {\n if (this.participantAvailableFuture.done) {\n return;\n }\n\n if (this.participantIdentity) {\n if (participant.identity !== this.participantIdentity) {\n return;\n }\n } else if (\n // otherwise, skip participants that are marked as publishing for this agent\n participant.attributes?.[ATTRIBUTE_PUBLISH_ON_BEHALF] === this.room.localParticipant?.identity\n ) {\n return;\n }\n\n const acceptedKinds = this.inputOptions.participantKinds ?? DEFAULT_PARTICIPANT_KINDS;\n if (participant.info.kind !== undefined && !acceptedKinds.includes(participant.info.kind)) {\n return;\n }\n\n this.participantAvailableFuture.resolve(participant);\n };\n\n private onParticipantDisconnected = (participant: RemoteParticipant) => {\n if (participant.identity !== this.participantIdentity) {\n return;\n }\n this.participantAvailableFuture = new Future<RemoteParticipant>();\n if (\n this.inputOptions.closeOnDisconnect &&\n participant.disconnectReason &&\n CLOSE_ON_DISCONNECT_REASONS.includes(participant.disconnectReason)\n ) {\n this.logger.info(\n {\n participant: participant.identity,\n reason: DisconnectReason[participant.disconnectReason],\n },\n 'closing agent session due to participant disconnect ' +\n '(disable via `RoomInputOptions.closeOnDisconnect=False`)',\n );\n this.agentSession._closeSoon({\n reason: CloseReason.PARTICIPANT_DISCONNECTED,\n });\n }\n };\n\n private onUserInputTranscribed = (ev: UserInputTranscribedEvent) => {\n this.userTranscriptWriter.write(ev).catch((error) => {\n this.logger.error({ error }, 'Failed to write transcript event to stream');\n });\n };\n\n private onAgentStateChanged = async (ev: AgentStateChangedEvent) => {\n if (this.room.isConnected && this.room.localParticipant) {\n await this.room.localParticipant.setAttributes({\n [`lk.agent.state`]: ev.newState,\n });\n }\n };\n\n private onUserTextInput = (reader: TextStreamReader, participantInfo: { identity: string }) => {\n if (participantInfo.identity !== this.participantIdentity) {\n return;\n }\n\n const participant = this.room.remoteParticipants.get(participantInfo.identity);\n if (!participant) {\n this.logger.warn('participant not found, ignoring text input');\n return;\n }\n\n const readText = async () => {\n const text = await reader.readAll();\n\n const textInputResult = this.inputOptions.textInputCallback!(this.agentSession, {\n text,\n info: reader.info,\n participant,\n });\n\n // check if callback is a Promise\n if (textInputResult instanceof Promise) {\n await textInputResult;\n }\n };\n\n readText().catch((error) => {\n this.logger.error({ error }, 'Error reading text input');\n });\n };\n\n private async forwardUserTranscript(signal: AbortSignal): Promise<void> {\n const reader = this.userTranscriptStream.readable.getReader();\n try {\n while (!signal.aborted) {\n const { done, value } = await reader.read();\n if (done) break;\n\n const event = value;\n // IMPORTANT: need to await here to avoid race condition\n await this.userTranscriptOutput?.captureText(event.transcript);\n if (event.isFinal) {\n this.userTranscriptOutput?.flush();\n }\n }\n } catch (error) {\n this.logger.error({ error }, 'Error processing transcript stream');\n }\n }\n\n private createTranscriptionOutput(options: {\n isDeltaStream: boolean;\n participant: Participant | string | null;\n }) {\n return new ParalellTextOutput([\n new ParticipantLegacyTranscriptionOutput(\n this.room,\n options.isDeltaStream,\n options.participant,\n ),\n new ParticipantTranscriptionOutput(this.room, options.isDeltaStream, options.participant),\n ]);\n }\n\n private updateTranscriptionOutput({\n output,\n participant,\n }: {\n output?: ParalellTextOutput;\n participant: string | null;\n }) {\n if (!output) {\n return;\n }\n\n for (const sink of output._sinks) {\n if (\n sink instanceof ParticipantLegacyTranscriptionOutput ||\n sink instanceof ParticipantTranscriptionOutput\n ) {\n sink.setParticipant(participant);\n }\n }\n }\n\n get audioOutput(): AudioOutput | undefined {\n if (!this.transcriptionSynchronizer) {\n return this.participantAudioOutput;\n }\n\n return this.transcriptionSynchronizer.audioOutput;\n }\n\n get transcriptionOutput(): TextOutput | undefined {\n if (!this.transcriptionSynchronizer) {\n return this.agentTranscriptOutput;\n }\n\n return this.transcriptionSynchronizer.textOutput;\n }\n\n get isParticipantAvailable(): boolean {\n return this.participantAvailableFuture.done;\n }\n\n /** Switch to a different participant */\n setParticipant(participantIdentity: string | null) {\n this.logger.debug({ participantIdentity }, 'setting participant');\n if (participantIdentity === null) {\n this.unsetParticipant();\n return;\n }\n\n if (this.participantIdentity !== participantIdentity) {\n this.participantAvailableFuture = new Future<RemoteParticipant>();\n\n // check if new participant is already connected\n for (const participant of this.room.remoteParticipants.values()) {\n if (participant.identity === participantIdentity) {\n this.participantAvailableFuture.resolve(participant);\n break;\n }\n }\n }\n\n // update participant identity and handlers\n this.participantIdentity = participantIdentity;\n this.audioInput?.setParticipant(participantIdentity);\n this.updateTranscriptionOutput({\n output: this.userTranscriptOutput,\n participant: participantIdentity,\n });\n }\n\n unsetParticipant() {\n this.participantIdentity = null;\n this.participantAvailableFuture = new Future<RemoteParticipant>();\n this.audioInput?.setParticipant(null);\n this.updateTranscriptionOutput({\n output: this.userTranscriptOutput,\n participant: null,\n });\n }\n\n start() {\n if (this.inputOptions.textEnabled) {\n try {\n this.room.registerTextStreamHandler(TOPIC_CHAT, this.onUserTextInput);\n this.textStreamHandlerRegistered = true;\n } catch (error) {\n if (this.inputOptions.textEnabled) {\n this.logger.warn(`text stream handler for topic \"${TOPIC_CHAT}\" already set, ignoring`);\n }\n }\n }\n\n // -- create inputs --\n if (this.inputOptions.audioEnabled) {\n this.audioInput = new ParticipantAudioInputStream({\n room: this.room,\n sampleRate: this.inputOptions.audioSampleRate,\n numChannels: this.inputOptions.audioNumChannels,\n noiseCancellation: this.inputOptions.noiseCancellation,\n });\n }\n\n // -- create outputs --\n if (this.outputOptions.audioEnabled) {\n this.participantAudioOutput = new ParticipantAudioOutput(this.room, {\n sampleRate: this.outputOptions.audioSampleRate,\n numChannels: this.outputOptions.audioNumChannels,\n trackPublishOptions: this.outputOptions.audioPublishOptions,\n });\n }\n if (this.outputOptions.transcriptionEnabled) {\n this.userTranscriptOutput = this.createTranscriptionOutput({\n isDeltaStream: false,\n participant: this.participantIdentity,\n });\n // Start the transcript forwarding\n this.forwardUserTranscriptTask = Task.from((controller) =>\n this.forwardUserTranscript(controller.signal),\n );\n this.agentTranscriptOutput = this.createTranscriptionOutput({\n isDeltaStream: true,\n participant: null,\n });\n\n // use the RoomIO's audio output if available, otherwise use the agent's audio output\n // TODO(AJS-176): check for agent output\n const audioOutput = this.participantAudioOutput;\n if (this.outputOptions.syncTranscription && audioOutput) {\n this.transcriptionSynchronizer = new TranscriptionSynchronizer(\n audioOutput,\n this.agentTranscriptOutput,\n );\n }\n }\n\n // -- set the room event handlers --\n this.room.on(RoomEvent.ParticipantConnected, this.onParticipantConnected);\n this.room.on(RoomEvent.ConnectionStateChanged, this.onConnectionStateChanged);\n this.room.on(RoomEvent.ParticipantDisconnected, this.onParticipantDisconnected);\n if (this.room.isConnected) {\n this.onConnectionStateChanged(ConnectionState.CONN_CONNECTED);\n }\n\n this.initTask = Task.from((controller) => this.init(controller.signal));\n\n // attach the agent to the session\n if (this.audioInput) {\n this.agentSession.input.audio = this.audioInput;\n }\n if (this.audioOutput) {\n this.agentSession.output.audio = this.audioOutput;\n }\n if (this.transcriptionOutput) {\n this.agentSession.output.transcription = this.transcriptionOutput;\n }\n\n this.agentSession.on(AgentSessionEventTypes.AgentStateChanged, this.onAgentStateChanged);\n this.agentSession.on(AgentSessionEventTypes.UserInputTranscribed, this.onUserInputTranscribed);\n }\n\n async close() {\n this.room.off(RoomEvent.ParticipantConnected, this.onParticipantConnected);\n this.room.off(RoomEvent.ConnectionStateChanged, this.onConnectionStateChanged);\n this.room.off(RoomEvent.ParticipantDisconnected, this.onParticipantDisconnected);\n this.agentSession.off(AgentSessionEventTypes.UserInputTranscribed, this.onUserInputTranscribed);\n this.agentSession.off(AgentSessionEventTypes.AgentStateChanged, this.onAgentStateChanged);\n\n if (this.textStreamHandlerRegistered) {\n this.room.unregisterTextStreamHandler(TOPIC_CHAT);\n this.textStreamHandlerRegistered = false;\n }\n\n await this.initTask?.cancelAndWait();\n\n // Close stream FIRST so reader.read() in forwardUserTranscript can exit.\n // This is a workaround for a race condition in the stream API.\n this.userTranscriptWriter.close();\n await this.forwardUserTranscriptTask?.cancelAndWait();\n\n await this.audioInput?.close();\n await this.participantAudioOutput?.close();\n await this.transcriptionSynchronizer?.close();\n }\n}\n"],"mappings":"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","names":["participant"]}
1
+ {"version":3,"sources":["../../../src/voice/room_io/room_io.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport {\n type AudioFrame,\n ConnectionState,\n DisconnectReason,\n type FrameProcessor,\n type NoiseCancellationOptions,\n type Participant,\n ParticipantKind,\n type RemoteParticipant,\n type Room,\n RoomEvent,\n type TextStreamInfo,\n type TextStreamReader,\n TrackPublishOptions,\n TrackSource,\n} from '@livekit/rtc-node';\nimport type { WritableStreamDefaultWriter } from 'node:stream/web';\nimport { ATTRIBUTE_PUBLISH_ON_BEHALF, TOPIC_CHAT } from '../../constants.js';\nimport { log } from '../../log.js';\nimport { IdentityTransform } from '../../stream/identity_transform.js';\nimport { Future, Task } from '../../utils.js';\nimport { type AgentSession } from '../agent_session.js';\nimport {\n AgentSessionEventTypes,\n type AgentStateChangedEvent,\n CloseReason,\n type UserInputTranscribedEvent,\n} from '../events.js';\nimport type { AudioOutput, TextOutput } from '../io.js';\nimport { TranscriptionSynchronizer } from '../transcription/synchronizer.js';\nimport { ParticipantAudioInputStream } from './_input.js';\nimport {\n ParalellTextOutput,\n ParticipantAudioOutput,\n ParticipantLegacyTranscriptionOutput,\n ParticipantTranscriptionOutput,\n} from './_output.js';\n\nexport interface TextInputEvent {\n text: string;\n info: TextStreamInfo;\n participant: RemoteParticipant;\n}\n\nexport type TextInputCallback = (sess: AgentSession, ev: TextInputEvent) => void | Promise<void>;\n\nconst DEFAULT_TEXT_INPUT_CALLBACK: TextInputCallback = (sess: AgentSession, ev: TextInputEvent) => {\n sess.interrupt();\n sess.generateReply({ userInput: ev.text });\n};\n\nconst DEFAULT_PARTICIPANT_KINDS: ParticipantKind[] = [\n ParticipantKind.CONNECTOR,\n ParticipantKind.SIP,\n ParticipantKind.STANDARD,\n];\n\nconst CLOSE_ON_DISCONNECT_REASONS: DisconnectReason[] = [\n DisconnectReason.CLIENT_INITIATED,\n DisconnectReason.ROOM_DELETED,\n DisconnectReason.USER_REJECTED,\n];\n\nexport interface RoomInputOptions {\n audioSampleRate: number;\n audioNumChannels: number;\n /** If not given, default to True. */\n textEnabled: boolean;\n /** If not given, default to True. */\n audioEnabled: boolean;\n /** If not given, default to False. */\n videoEnabled: boolean;\n /** The participant to link to. If not provided, link to the first participant.\n Can be overridden by the `participant` argument of RoomIO constructor or `set_participant`.\n */\n participantIdentity?: string;\n noiseCancellation?: NoiseCancellationOptions | FrameProcessor<AudioFrame>;\n textInputCallback?: TextInputCallback;\n /** Participant kinds accepted for auto subscription. If not provided,\n accept `DEFAULT_PARTICIPANT_KINDS`\n */\n participantKinds?: ParticipantKind[];\n /** Close the AgentSession if the linked participant disconnects with reasons in\n CLIENT_INITIATED, ROOM_DELETED, or USER_REJECTED.\n */\n closeOnDisconnect: boolean;\n}\n\nexport interface RoomOutputOptions {\n /** If not given, default to True. */\n transcriptionEnabled: boolean;\n /** If not given, default to True. */\n audioEnabled: boolean;\n audioSampleRate: number;\n audioNumChannels: number;\n /** False to disable transcription synchronization with audio output.\n Otherwise, transcription is emitted as quickly as available.\n */\n syncTranscription: boolean;\n /** The name of the audio track to publish. If not provided, default to \"roomio_audio\".\n */\n audioPublishOptions: TrackPublishOptions;\n}\n\nconst DEFAULT_ROOM_INPUT_OPTIONS: RoomInputOptions = {\n audioSampleRate: 24000,\n audioNumChannels: 1,\n textEnabled: true,\n audioEnabled: true,\n videoEnabled: false,\n textInputCallback: DEFAULT_TEXT_INPUT_CALLBACK,\n closeOnDisconnect: true,\n};\n\nconst DEFAULT_ROOM_OUTPUT_OPTIONS: RoomOutputOptions = {\n audioSampleRate: 24000,\n audioNumChannels: 1,\n transcriptionEnabled: true,\n audioEnabled: true,\n syncTranscription: true,\n audioPublishOptions: new TrackPublishOptions({ source: TrackSource.SOURCE_MICROPHONE }),\n};\n\nexport class RoomIO {\n private agentSession: AgentSession;\n private room: Room;\n private inputOptions: RoomInputOptions;\n private outputOptions: RoomOutputOptions;\n\n private audioInput?: ParticipantAudioInputStream;\n private participantAudioOutput?: ParticipantAudioOutput;\n private userTranscriptOutput?: ParalellTextOutput;\n private agentTranscriptOutput?: ParalellTextOutput;\n private transcriptionSynchronizer?: TranscriptionSynchronizer;\n private participantIdentity: string | null = null;\n\n private participantAvailableFuture: Future<RemoteParticipant> = new Future();\n private roomConnectedFuture: Future<void> = new Future();\n\n // Use stream API for transcript queue\n private userTranscriptStream = new IdentityTransform<UserInputTranscribedEvent>();\n private userTranscriptWriter: WritableStreamDefaultWriter<UserInputTranscribedEvent>;\n private forwardUserTranscriptTask?: Task<void>;\n private initTask?: Task<void>;\n\n private textStreamHandlerRegistered = false;\n\n private logger = log();\n\n constructor({\n agentSession,\n room,\n participant = null,\n inputOptions,\n outputOptions,\n }: {\n agentSession: AgentSession;\n room: Room;\n participant?: RemoteParticipant | string | null;\n inputOptions?: Partial<RoomInputOptions>;\n outputOptions?: Partial<RoomOutputOptions>;\n }) {\n this.agentSession = agentSession;\n this.room = room;\n this.inputOptions = { ...DEFAULT_ROOM_INPUT_OPTIONS, ...inputOptions };\n this.outputOptions = { ...DEFAULT_ROOM_OUTPUT_OPTIONS, ...outputOptions };\n\n this.userTranscriptWriter = this.userTranscriptStream.writable.getWriter();\n\n this.participantIdentity = participant\n ? typeof participant === 'string'\n ? participant\n : participant.identity\n : this.inputOptions.participantIdentity ?? null;\n }\n private async init(signal: AbortSignal): Promise<void> {\n await this.roomConnectedFuture.await;\n\n for (const participant of this.room.remoteParticipants.values()) {\n this.onParticipantConnected(participant);\n }\n if (signal.aborted) {\n return;\n }\n\n const participant = await this.participantAvailableFuture.await;\n this.setParticipant(participant.identity);\n\n // init agent outputs\n this.updateTranscriptionOutput({\n output: this.agentTranscriptOutput,\n participant: this.room.localParticipant?.identity ?? null,\n });\n\n await this.participantAudioOutput?.start(signal);\n }\n\n private onConnectionStateChanged = (state: ConnectionState) => {\n this.logger.debug({ state }, 'connection state changed');\n if (\n state === ConnectionState.CONN_CONNECTED &&\n this.room.isConnected &&\n !this.roomConnectedFuture.done\n ) {\n this.roomConnectedFuture.resolve();\n }\n };\n\n private onParticipantConnected = (participant: RemoteParticipant) => {\n if (this.participantAvailableFuture.done) {\n return;\n }\n\n if (this.participantIdentity) {\n if (participant.identity !== this.participantIdentity) {\n return;\n }\n } else if (\n // otherwise, skip participants that are marked as publishing for this agent\n participant.attributes?.[ATTRIBUTE_PUBLISH_ON_BEHALF] === this.room.localParticipant?.identity\n ) {\n return;\n }\n\n const acceptedKinds = this.inputOptions.participantKinds ?? DEFAULT_PARTICIPANT_KINDS;\n if (participant.info.kind !== undefined && !acceptedKinds.includes(participant.info.kind)) {\n return;\n }\n\n this.participantAvailableFuture.resolve(participant);\n };\n\n private onParticipantDisconnected = (participant: RemoteParticipant) => {\n if (participant.identity !== this.participantIdentity) {\n return;\n }\n this.participantAvailableFuture = new Future<RemoteParticipant>();\n if (\n this.inputOptions.closeOnDisconnect &&\n participant.disconnectReason &&\n CLOSE_ON_DISCONNECT_REASONS.includes(participant.disconnectReason)\n ) {\n this.logger.info(\n {\n participant: participant.identity,\n reason: DisconnectReason[participant.disconnectReason],\n },\n 'closing agent session due to participant disconnect ' +\n '(disable via `RoomInputOptions.closeOnDisconnect=False`)',\n );\n this.agentSession._closeSoon({\n reason: CloseReason.PARTICIPANT_DISCONNECTED,\n });\n }\n };\n\n private onUserInputTranscribed = (ev: UserInputTranscribedEvent) => {\n this.userTranscriptWriter.write(ev).catch((error) => {\n this.logger.error({ error }, 'Failed to write transcript event to stream');\n });\n };\n\n private onAgentStateChanged = async (ev: AgentStateChangedEvent) => {\n if (this.room.isConnected && this.room.localParticipant) {\n await this.room.localParticipant.setAttributes({\n [`lk.agent.state`]: ev.newState,\n });\n }\n };\n\n private onUserTextInput = (reader: TextStreamReader, participantInfo: { identity: string }) => {\n if (participantInfo.identity !== this.participantIdentity) {\n return;\n }\n\n const participant = this.room.remoteParticipants.get(participantInfo.identity);\n if (!participant) {\n this.logger.warn('participant not found, ignoring text input');\n return;\n }\n\n const readText = async () => {\n const text = await reader.readAll();\n\n const textInputResult = this.inputOptions.textInputCallback!(this.agentSession, {\n text,\n info: reader.info,\n participant,\n });\n\n // check if callback is a Promise\n if (textInputResult instanceof Promise) {\n await textInputResult;\n }\n };\n\n readText().catch((error) => {\n this.logger.error({ error }, 'Error reading text input');\n });\n };\n\n private async forwardUserTranscript(signal: AbortSignal): Promise<void> {\n const reader = this.userTranscriptStream.readable.getReader();\n try {\n while (!signal.aborted) {\n const { done, value } = await reader.read();\n if (done) break;\n\n const event = value;\n // IMPORTANT: need to await here to avoid race condition\n await this.userTranscriptOutput?.captureText(event.transcript);\n if (event.isFinal) {\n this.userTranscriptOutput?.flush();\n }\n }\n } catch (error) {\n this.logger.error({ error }, 'Error processing transcript stream');\n }\n }\n\n private createTranscriptionOutput(options: {\n isDeltaStream: boolean;\n participant: Participant | string | null;\n }) {\n return new ParalellTextOutput([\n new ParticipantLegacyTranscriptionOutput(\n this.room,\n options.isDeltaStream,\n options.participant,\n ),\n new ParticipantTranscriptionOutput(this.room, options.isDeltaStream, options.participant),\n ]);\n }\n\n private updateTranscriptionOutput({\n output,\n participant,\n }: {\n output?: ParalellTextOutput;\n participant: string | null;\n }) {\n if (!output) {\n return;\n }\n\n for (const sink of output._sinks) {\n if (\n sink instanceof ParticipantLegacyTranscriptionOutput ||\n sink instanceof ParticipantTranscriptionOutput\n ) {\n sink.setParticipant(participant);\n }\n }\n }\n\n get audioOutput(): AudioOutput | undefined {\n if (!this.transcriptionSynchronizer) {\n return this.participantAudioOutput;\n }\n\n return this.transcriptionSynchronizer.audioOutput;\n }\n\n get transcriptionOutput(): TextOutput | undefined {\n if (!this.transcriptionSynchronizer) {\n return this.agentTranscriptOutput;\n }\n\n return this.transcriptionSynchronizer.textOutput;\n }\n\n get isParticipantAvailable(): boolean {\n return this.participantAvailableFuture.done;\n }\n\n get linkedParticipant(): RemoteParticipant | undefined {\n if (!this.isParticipantAvailable) {\n return undefined;\n }\n\n return this.participantAvailableFuture.result;\n }\n\n get localParticipant(): Participant | undefined {\n return this.room.localParticipant ?? undefined;\n }\n\n /** Switch to a different participant */\n setParticipant(participantIdentity: string | null) {\n this.logger.debug({ participantIdentity }, 'setting participant');\n if (participantIdentity === null) {\n this.unsetParticipant();\n return;\n }\n\n if (this.participantIdentity !== participantIdentity) {\n this.participantAvailableFuture = new Future<RemoteParticipant>();\n\n // check if new participant is already connected\n for (const participant of this.room.remoteParticipants.values()) {\n if (participant.identity === participantIdentity) {\n this.participantAvailableFuture.resolve(participant);\n break;\n }\n }\n }\n\n // update participant identity and handlers\n this.participantIdentity = participantIdentity;\n this.audioInput?.setParticipant(participantIdentity);\n this.updateTranscriptionOutput({\n output: this.userTranscriptOutput,\n participant: participantIdentity,\n });\n }\n\n unsetParticipant() {\n this.participantIdentity = null;\n this.participantAvailableFuture = new Future<RemoteParticipant>();\n this.audioInput?.setParticipant(null);\n this.updateTranscriptionOutput({\n output: this.userTranscriptOutput,\n participant: null,\n });\n }\n\n start() {\n if (this.inputOptions.textEnabled) {\n try {\n this.room.registerTextStreamHandler(TOPIC_CHAT, this.onUserTextInput);\n this.textStreamHandlerRegistered = true;\n } catch (error) {\n if (this.inputOptions.textEnabled) {\n this.logger.warn(`text stream handler for topic \"${TOPIC_CHAT}\" already set, ignoring`);\n }\n }\n }\n\n // -- create inputs --\n if (this.inputOptions.audioEnabled) {\n this.audioInput = new ParticipantAudioInputStream({\n room: this.room,\n sampleRate: this.inputOptions.audioSampleRate,\n numChannels: this.inputOptions.audioNumChannels,\n noiseCancellation: this.inputOptions.noiseCancellation,\n });\n }\n\n // -- create outputs --\n if (this.outputOptions.audioEnabled) {\n this.participantAudioOutput = new ParticipantAudioOutput(this.room, {\n sampleRate: this.outputOptions.audioSampleRate,\n numChannels: this.outputOptions.audioNumChannels,\n trackPublishOptions: this.outputOptions.audioPublishOptions,\n });\n }\n if (this.outputOptions.transcriptionEnabled) {\n this.userTranscriptOutput = this.createTranscriptionOutput({\n isDeltaStream: false,\n participant: this.participantIdentity,\n });\n // Start the transcript forwarding\n this.forwardUserTranscriptTask = Task.from((controller) =>\n this.forwardUserTranscript(controller.signal),\n );\n this.agentTranscriptOutput = this.createTranscriptionOutput({\n isDeltaStream: true,\n participant: null,\n });\n\n // use the RoomIO's audio output if available, otherwise use the agent's audio output\n // TODO(AJS-176): check for agent output\n const audioOutput = this.participantAudioOutput;\n if (this.outputOptions.syncTranscription && audioOutput) {\n this.transcriptionSynchronizer = new TranscriptionSynchronizer(\n audioOutput,\n this.agentTranscriptOutput,\n );\n }\n }\n\n // -- set the room event handlers --\n this.room.on(RoomEvent.ParticipantConnected, this.onParticipantConnected);\n this.room.on(RoomEvent.ConnectionStateChanged, this.onConnectionStateChanged);\n this.room.on(RoomEvent.ParticipantDisconnected, this.onParticipantDisconnected);\n if (this.room.isConnected) {\n this.onConnectionStateChanged(ConnectionState.CONN_CONNECTED);\n }\n\n this.initTask = Task.from((controller) => this.init(controller.signal));\n\n // attach the agent to the session\n if (this.audioInput) {\n this.agentSession.input.audio = this.audioInput;\n }\n if (this.audioOutput) {\n this.agentSession.output.audio = this.audioOutput;\n }\n if (this.transcriptionOutput) {\n this.agentSession.output.transcription = this.transcriptionOutput;\n }\n\n this.agentSession.on(AgentSessionEventTypes.AgentStateChanged, this.onAgentStateChanged);\n this.agentSession.on(AgentSessionEventTypes.UserInputTranscribed, this.onUserInputTranscribed);\n }\n\n async close() {\n this.room.off(RoomEvent.ParticipantConnected, this.onParticipantConnected);\n this.room.off(RoomEvent.ConnectionStateChanged, this.onConnectionStateChanged);\n this.room.off(RoomEvent.ParticipantDisconnected, this.onParticipantDisconnected);\n this.agentSession.off(AgentSessionEventTypes.UserInputTranscribed, this.onUserInputTranscribed);\n this.agentSession.off(AgentSessionEventTypes.AgentStateChanged, this.onAgentStateChanged);\n\n if (this.textStreamHandlerRegistered) {\n this.room.unregisterTextStreamHandler(TOPIC_CHAT);\n this.textStreamHandlerRegistered = false;\n }\n\n await this.initTask?.cancelAndWait();\n\n // Close stream FIRST so reader.read() in forwardUserTranscript can exit.\n // This is a workaround for a race condition in the stream API.\n this.userTranscriptWriter.close();\n await this.forwardUserTranscriptTask?.cancelAndWait();\n\n await this.audioInput?.close();\n await this.participantAudioOutput?.close();\n await this.transcriptionSynchronizer?.close();\n }\n}\n"],"mappings":"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","names":["participant"]}
@@ -58,6 +58,8 @@ class SpeechHandle {
58
58
  _numSteps = 1;
59
59
  /** @internal - OpenTelemetry context for the agent turn span */
60
60
  _agentTurnContext;
61
+ /** @internal - used by AgentTask/RunResult final output plumbing */
62
+ _maybeRunFinalOutput;
61
63
  itemAddedCallbacks = /* @__PURE__ */ new Set();
62
64
  doneCallbacks = /* @__PURE__ */ new Set();
63
65
  /** @internal Symbol marker for type identification */
@@ -128,7 +130,7 @@ class SpeechHandle {
128
130
  * has entirely played out, including any tool calls and response follow-ups.
129
131
  */
130
132
  async waitForPlayout() {
131
- const store = import_agent.asyncLocalStorage.getStore();
133
+ const store = import_agent.functionCallStorage.getStore();
132
134
  if (store && (store == null ? void 0 : store.functionCall)) {
133
135
  throw new Error(
134
136
  `Cannot call 'SpeechHandle.waitForPlayout()' from inside the function tool '${store.functionCall.name}'. This creates a circular wait: the speech handle is waiting for the function tool to complete, while the function tool is simultaneously waiting for the speech handle.
@@ -143,6 +145,10 @@ To wait for the assistant's spoken response prior to running this tool, use RunC
143
145
  await Promise.race(fs);
144
146
  }
145
147
  addDoneCallback(callback) {
148
+ if (this.done()) {
149
+ queueMicrotask(() => callback(this));
150
+ return;
151
+ }
146
152
  this.doneCallbacks.add(callback);
147
153
  }
148
154
  removeDoneCallback(callback) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/voice/speech_handle.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { Context } from '@opentelemetry/api';\nimport type { ChatItem } from '../llm/index.js';\nimport type { Task } from '../utils.js';\nimport { Event, Future, shortuuid } from '../utils.js';\nimport { asyncLocalStorage } from './agent.js';\n\n/** Symbol used to identify SpeechHandle instances */\nconst SPEECH_HANDLE_SYMBOL = Symbol.for('livekit.agents.SpeechHandle');\n\n/**\n * Type guard to check if a value is a SpeechHandle.\n */\nexport function isSpeechHandle(value: unknown): value is SpeechHandle {\n return (\n typeof value === 'object' &&\n value !== null &&\n SPEECH_HANDLE_SYMBOL in value &&\n (value as Record<symbol, boolean>)[SPEECH_HANDLE_SYMBOL] === true\n );\n}\n\nexport class SpeechHandle {\n /** Priority for messages that should be played after all other messages in the queue */\n static SPEECH_PRIORITY_LOW = 0;\n /** Every speech generates by the VoiceAgent defaults to this priority. */\n static SPEECH_PRIORITY_NORMAL = 5;\n /** Priority for important messages that should be played before others. */\n static SPEECH_PRIORITY_HIGH = 10;\n\n private interruptFut = new Future<void>();\n private authorizedEvent = new Event();\n private scheduledFut = new Future<void>();\n private doneFut = new Future<void>();\n private generations: Future<void>[] = [];\n private _chatItems: ChatItem[] = [];\n\n /** @internal */\n _tasks: Task<void>[] = [];\n\n /** @internal */\n _numSteps = 1;\n\n /** @internal - OpenTelemetry context for the agent turn span */\n _agentTurnContext?: Context;\n\n private itemAddedCallbacks: Set<(item: ChatItem) => void> = new Set();\n private doneCallbacks: Set<(sh: SpeechHandle) => void> = new Set();\n\n /** @internal Symbol marker for type identification */\n readonly [SPEECH_HANDLE_SYMBOL] = true;\n\n constructor(\n private _id: string,\n private _allowInterruptions: boolean,\n /** @internal */\n public _stepIndex: number,\n readonly parent?: SpeechHandle,\n ) {\n this.doneFut.await.finally(() => {\n for (const callback of this.doneCallbacks) {\n callback(this);\n }\n });\n }\n\n static create(options?: {\n allowInterruptions?: boolean;\n stepIndex?: number;\n parent?: SpeechHandle;\n }) {\n const { allowInterruptions = true, stepIndex = 0, parent } = options ?? {};\n\n return new SpeechHandle(shortuuid('speech_'), allowInterruptions, stepIndex, parent);\n }\n\n get interrupted(): boolean {\n return this.interruptFut.done;\n }\n\n get numSteps(): number {\n return this._numSteps;\n }\n\n get id(): string {\n return this._id;\n }\n\n get scheduled(): boolean {\n return this.scheduledFut.done;\n }\n\n get allowInterruptions(): boolean {\n return this._allowInterruptions;\n }\n\n /**\n * Allow or disallow interruptions on this SpeechHandle.\n *\n * When set to false, the SpeechHandle will no longer accept any incoming\n * interruption requests until re-enabled. If the handle is already\n * interrupted, clearing interruptions is not allowed.\n *\n * @param value - true to allow interruptions, false to disallow\n * @throws Error If attempting to disable interruptions when already interrupted\n */\n set allowInterruptions(value: boolean) {\n if (this.interrupted && !value) {\n throw new Error(\n 'Cannot set allow_interruptions to False, the SpeechHandle is already interrupted',\n );\n }\n this._allowInterruptions = value;\n }\n\n done(): boolean {\n return this.doneFut.done;\n }\n\n get chatItems(): ChatItem[] {\n return this._chatItems;\n }\n\n /**\n * Interrupt the current speech generation.\n *\n * @throws Error If this speech handle does not allow interruptions.\n *\n * @returns The same speech handle that was interrupted.\n */\n interrupt(force: boolean = false): SpeechHandle {\n if (!force && !this.allowInterruptions) {\n throw new Error('This generation handle does not allow interruptions');\n }\n\n this._cancel();\n return this;\n }\n\n /**\n * Waits for the entire assistant turn to complete playback.\n *\n * This method waits until the assistant has fully finished speaking,\n * including any finalization steps beyond initial response generation.\n * This is appropriate to call when you want to ensure the speech output\n * has entirely played out, including any tool calls and response follow-ups.\n */\n async waitForPlayout(): Promise<void> {\n const store = asyncLocalStorage.getStore();\n if (store && store?.functionCall) {\n throw new Error(\n `Cannot call 'SpeechHandle.waitForPlayout()' from inside the function tool '${store.functionCall.name}'. ` +\n 'This creates a circular wait: the speech handle is waiting for the function tool to complete, ' +\n 'while the function tool is simultaneously waiting for the speech handle.\\n' +\n \"To wait for the assistant's spoken response prior to running this tool, use RunContext.wait_for_playout() instead.\",\n );\n }\n await this.doneFut.await;\n }\n\n async waitIfNotInterrupted(aw: Promise<unknown>[]): Promise<void> {\n const allTasksPromise = Promise.all(aw);\n const fs: Promise<unknown>[] = [allTasksPromise, this.interruptFut.await];\n await Promise.race(fs);\n }\n\n addDoneCallback(callback: (sh: SpeechHandle) => void) {\n this.doneCallbacks.add(callback);\n }\n\n removeDoneCallback(callback: (sh: SpeechHandle) => void) {\n this.doneCallbacks.delete(callback);\n }\n\n /** @internal */\n _cancel(): SpeechHandle {\n if (this.done()) {\n return this;\n }\n\n if (!this.interruptFut.done) {\n this.interruptFut.resolve();\n }\n\n return this;\n }\n\n /** @internal */\n _authorizeGeneration(): void {\n const fut = new Future<void>();\n this.generations.push(fut);\n this.authorizedEvent.set();\n }\n\n /** @internal */\n _clearAuthorization(): void {\n this.authorizedEvent.clear();\n }\n\n /** @internal */\n async _waitForAuthorization(): Promise<void> {\n await this.authorizedEvent.wait();\n }\n\n /** @internal */\n async _waitForGeneration(stepIdx: number = -1): Promise<void> {\n if (this.generations.length === 0) {\n throw new Error('cannot use wait_for_generation: no active generation is running.');\n }\n\n const index = stepIdx === -1 ? this.generations.length - 1 : stepIdx;\n const generation = this.generations[index];\n if (!generation) {\n throw new Error(`Generation at index ${index} not found.`);\n }\n return generation.await;\n }\n\n /** @internal */\n async _waitForScheduled(): Promise<void> {\n return this.scheduledFut.await;\n }\n\n /** @internal */\n _markGenerationDone(): void {\n if (this.generations.length === 0) {\n throw new Error('cannot use mark_generation_done: no active generation is running.');\n }\n\n const lastGeneration = this.generations[this.generations.length - 1];\n if (lastGeneration && !lastGeneration.done) {\n lastGeneration.resolve();\n }\n }\n\n /** @internal */\n _markDone(): void {\n if (!this.doneFut.done) {\n this.doneFut.resolve();\n if (this.generations.length > 0) {\n this._markGenerationDone(); // preemptive generation could be cancelled before being scheduled\n }\n }\n }\n\n /** @internal */\n _markScheduled(): void {\n if (!this.scheduledFut.done) {\n this.scheduledFut.resolve();\n }\n }\n\n /** @internal */\n _addItemAddedCallback(callback: (item: ChatItem) => void): void {\n this.itemAddedCallbacks.add(callback);\n }\n\n /** @internal */\n _removeItemAddedCallback(callback: (item: ChatItem) => void): void {\n this.itemAddedCallbacks.delete(callback);\n }\n\n /** @internal */\n _itemAdded(items: ChatItem[]): void {\n for (const item of items) {\n for (const cb of this.itemAddedCallbacks) {\n cb(item);\n }\n this._chatItems.push(item);\n }\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/voice/speech_handle.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { Context } from '@opentelemetry/api';\nimport type { ChatItem } from '../llm/index.js';\nimport type { Task } from '../utils.js';\nimport { Event, Future, shortuuid } from '../utils.js';\nimport { functionCallStorage } from './agent.js';\n\n/** Symbol used to identify SpeechHandle instances */\nconst SPEECH_HANDLE_SYMBOL = Symbol.for('livekit.agents.SpeechHandle');\n\n/**\n * Type guard to check if a value is a SpeechHandle.\n */\nexport function isSpeechHandle(value: unknown): value is SpeechHandle {\n return (\n typeof value === 'object' &&\n value !== null &&\n SPEECH_HANDLE_SYMBOL in value &&\n (value as Record<symbol, boolean>)[SPEECH_HANDLE_SYMBOL] === true\n );\n}\n\nexport class SpeechHandle {\n /** Priority for messages that should be played after all other messages in the queue */\n static SPEECH_PRIORITY_LOW = 0;\n /** Every speech generates by the VoiceAgent defaults to this priority. */\n static SPEECH_PRIORITY_NORMAL = 5;\n /** Priority for important messages that should be played before others. */\n static SPEECH_PRIORITY_HIGH = 10;\n\n private interruptFut = new Future<void>();\n private authorizedEvent = new Event();\n private scheduledFut = new Future<void>();\n private doneFut = new Future<void>();\n private generations: Future<void>[] = [];\n private _chatItems: ChatItem[] = [];\n\n /** @internal */\n _tasks: Task<void>[] = [];\n\n /** @internal */\n _numSteps = 1;\n\n /** @internal - OpenTelemetry context for the agent turn span */\n _agentTurnContext?: Context;\n\n /** @internal - used by AgentTask/RunResult final output plumbing */\n _maybeRunFinalOutput?: unknown;\n\n private itemAddedCallbacks: Set<(item: ChatItem) => void> = new Set();\n private doneCallbacks: Set<(sh: SpeechHandle) => void> = new Set();\n\n /** @internal Symbol marker for type identification */\n readonly [SPEECH_HANDLE_SYMBOL] = true;\n\n constructor(\n private _id: string,\n private _allowInterruptions: boolean,\n /** @internal */\n public _stepIndex: number,\n readonly parent?: SpeechHandle,\n ) {\n this.doneFut.await.finally(() => {\n for (const callback of this.doneCallbacks) {\n callback(this);\n }\n });\n }\n\n static create(options?: {\n allowInterruptions?: boolean;\n stepIndex?: number;\n parent?: SpeechHandle;\n }) {\n const { allowInterruptions = true, stepIndex = 0, parent } = options ?? {};\n\n return new SpeechHandle(shortuuid('speech_'), allowInterruptions, stepIndex, parent);\n }\n\n get interrupted(): boolean {\n return this.interruptFut.done;\n }\n\n get numSteps(): number {\n return this._numSteps;\n }\n\n get id(): string {\n return this._id;\n }\n\n get scheduled(): boolean {\n return this.scheduledFut.done;\n }\n\n get allowInterruptions(): boolean {\n return this._allowInterruptions;\n }\n\n /**\n * Allow or disallow interruptions on this SpeechHandle.\n *\n * When set to false, the SpeechHandle will no longer accept any incoming\n * interruption requests until re-enabled. If the handle is already\n * interrupted, clearing interruptions is not allowed.\n *\n * @param value - true to allow interruptions, false to disallow\n * @throws Error If attempting to disable interruptions when already interrupted\n */\n set allowInterruptions(value: boolean) {\n if (this.interrupted && !value) {\n throw new Error(\n 'Cannot set allow_interruptions to False, the SpeechHandle is already interrupted',\n );\n }\n this._allowInterruptions = value;\n }\n\n done(): boolean {\n return this.doneFut.done;\n }\n\n get chatItems(): ChatItem[] {\n return this._chatItems;\n }\n\n /**\n * Interrupt the current speech generation.\n *\n * @throws Error If this speech handle does not allow interruptions.\n *\n * @returns The same speech handle that was interrupted.\n */\n interrupt(force: boolean = false): SpeechHandle {\n if (!force && !this.allowInterruptions) {\n throw new Error('This generation handle does not allow interruptions');\n }\n\n this._cancel();\n return this;\n }\n\n /**\n * Waits for the entire assistant turn to complete playback.\n *\n * This method waits until the assistant has fully finished speaking,\n * including any finalization steps beyond initial response generation.\n * This is appropriate to call when you want to ensure the speech output\n * has entirely played out, including any tool calls and response follow-ups.\n */\n async waitForPlayout(): Promise<void> {\n const store = functionCallStorage.getStore();\n if (store && store?.functionCall) {\n throw new Error(\n `Cannot call 'SpeechHandle.waitForPlayout()' from inside the function tool '${store.functionCall.name}'. ` +\n 'This creates a circular wait: the speech handle is waiting for the function tool to complete, ' +\n 'while the function tool is simultaneously waiting for the speech handle.\\n' +\n \"To wait for the assistant's spoken response prior to running this tool, use RunContext.wait_for_playout() instead.\",\n );\n }\n await this.doneFut.await;\n }\n\n async waitIfNotInterrupted(aw: Promise<unknown>[]): Promise<void> {\n const allTasksPromise = Promise.all(aw);\n const fs: Promise<unknown>[] = [allTasksPromise, this.interruptFut.await];\n await Promise.race(fs);\n }\n\n addDoneCallback(callback: (sh: SpeechHandle) => void) {\n if (this.done()) {\n queueMicrotask(() => callback(this));\n return;\n }\n this.doneCallbacks.add(callback);\n }\n\n removeDoneCallback(callback: (sh: SpeechHandle) => void) {\n this.doneCallbacks.delete(callback);\n }\n\n /** @internal */\n _cancel(): SpeechHandle {\n if (this.done()) {\n return this;\n }\n\n if (!this.interruptFut.done) {\n this.interruptFut.resolve();\n }\n\n return this;\n }\n\n /** @internal */\n _authorizeGeneration(): void {\n const fut = new Future<void>();\n this.generations.push(fut);\n this.authorizedEvent.set();\n }\n\n /** @internal */\n _clearAuthorization(): void {\n this.authorizedEvent.clear();\n }\n\n /** @internal */\n async _waitForAuthorization(): Promise<void> {\n await this.authorizedEvent.wait();\n }\n\n /** @internal */\n async _waitForGeneration(stepIdx: number = -1): Promise<void> {\n if (this.generations.length === 0) {\n throw new Error('cannot use wait_for_generation: no active generation is running.');\n }\n\n const index = stepIdx === -1 ? this.generations.length - 1 : stepIdx;\n const generation = this.generations[index];\n if (!generation) {\n throw new Error(`Generation at index ${index} not found.`);\n }\n return generation.await;\n }\n\n /** @internal */\n async _waitForScheduled(): Promise<void> {\n return this.scheduledFut.await;\n }\n\n /** @internal */\n _markGenerationDone(): void {\n if (this.generations.length === 0) {\n throw new Error('cannot use mark_generation_done: no active generation is running.');\n }\n\n const lastGeneration = this.generations[this.generations.length - 1];\n if (lastGeneration && !lastGeneration.done) {\n lastGeneration.resolve();\n }\n }\n\n /** @internal */\n _markDone(): void {\n if (!this.doneFut.done) {\n this.doneFut.resolve();\n if (this.generations.length > 0) {\n this._markGenerationDone(); // preemptive generation could be cancelled before being scheduled\n }\n }\n }\n\n /** @internal */\n _markScheduled(): void {\n if (!this.scheduledFut.done) {\n this.scheduledFut.resolve();\n }\n }\n\n /** @internal */\n _addItemAddedCallback(callback: (item: ChatItem) => void): void {\n this.itemAddedCallbacks.add(callback);\n }\n\n /** @internal */\n _removeItemAddedCallback(callback: (item: ChatItem) => void): void {\n this.itemAddedCallbacks.delete(callback);\n }\n\n /** @internal */\n _itemAdded(items: ChatItem[]): void {\n for (const item of items) {\n for (const cb of this.itemAddedCallbacks) {\n cb(item);\n }\n this._chatItems.push(item);\n }\n }\n}\n"],"mappings":"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","names":[]}
@@ -31,6 +31,8 @@ export declare class SpeechHandle {
31
31
  _numSteps: number;
32
32
  /** @internal - OpenTelemetry context for the agent turn span */
33
33
  _agentTurnContext?: Context;
34
+ /** @internal - used by AgentTask/RunResult final output plumbing */
35
+ _maybeRunFinalOutput?: unknown;
34
36
  private itemAddedCallbacks;
35
37
  private doneCallbacks;
36
38
  /** @internal Symbol marker for type identification */
@@ -31,6 +31,8 @@ export declare class SpeechHandle {
31
31
  _numSteps: number;
32
32
  /** @internal - OpenTelemetry context for the agent turn span */
33
33
  _agentTurnContext?: Context;
34
+ /** @internal - used by AgentTask/RunResult final output plumbing */
35
+ _maybeRunFinalOutput?: unknown;
34
36
  private itemAddedCallbacks;
35
37
  private doneCallbacks;
36
38
  /** @internal Symbol marker for type identification */
@@ -1 +1 @@
1
- {"version":3,"file":"speech_handle.d.ts","sourceRoot":"","sources":["../../src/voice/speech_handle.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"speech_handle.d.ts","sourceRoot":"","sources":["../../src/voice/speech_handle.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { Event, Future, shortuuid } from "../utils.js";
2
- import { asyncLocalStorage } from "./agent.js";
2
+ import { functionCallStorage } from "./agent.js";
3
3
  const SPEECH_HANDLE_SYMBOL = Symbol.for("livekit.agents.SpeechHandle");
4
4
  function isSpeechHandle(value) {
5
5
  return typeof value === "object" && value !== null && SPEECH_HANDLE_SYMBOL in value && value[SPEECH_HANDLE_SYMBOL] === true;
@@ -34,6 +34,8 @@ class SpeechHandle {
34
34
  _numSteps = 1;
35
35
  /** @internal - OpenTelemetry context for the agent turn span */
36
36
  _agentTurnContext;
37
+ /** @internal - used by AgentTask/RunResult final output plumbing */
38
+ _maybeRunFinalOutput;
37
39
  itemAddedCallbacks = /* @__PURE__ */ new Set();
38
40
  doneCallbacks = /* @__PURE__ */ new Set();
39
41
  /** @internal Symbol marker for type identification */
@@ -104,7 +106,7 @@ class SpeechHandle {
104
106
  * has entirely played out, including any tool calls and response follow-ups.
105
107
  */
106
108
  async waitForPlayout() {
107
- const store = asyncLocalStorage.getStore();
109
+ const store = functionCallStorage.getStore();
108
110
  if (store && (store == null ? void 0 : store.functionCall)) {
109
111
  throw new Error(
110
112
  `Cannot call 'SpeechHandle.waitForPlayout()' from inside the function tool '${store.functionCall.name}'. This creates a circular wait: the speech handle is waiting for the function tool to complete, while the function tool is simultaneously waiting for the speech handle.
@@ -119,6 +121,10 @@ To wait for the assistant's spoken response prior to running this tool, use RunC
119
121
  await Promise.race(fs);
120
122
  }
121
123
  addDoneCallback(callback) {
124
+ if (this.done()) {
125
+ queueMicrotask(() => callback(this));
126
+ return;
127
+ }
122
128
  this.doneCallbacks.add(callback);
123
129
  }
124
130
  removeDoneCallback(callback) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/voice/speech_handle.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { Context } from '@opentelemetry/api';\nimport type { ChatItem } from '../llm/index.js';\nimport type { Task } from '../utils.js';\nimport { Event, Future, shortuuid } from '../utils.js';\nimport { asyncLocalStorage } from './agent.js';\n\n/** Symbol used to identify SpeechHandle instances */\nconst SPEECH_HANDLE_SYMBOL = Symbol.for('livekit.agents.SpeechHandle');\n\n/**\n * Type guard to check if a value is a SpeechHandle.\n */\nexport function isSpeechHandle(value: unknown): value is SpeechHandle {\n return (\n typeof value === 'object' &&\n value !== null &&\n SPEECH_HANDLE_SYMBOL in value &&\n (value as Record<symbol, boolean>)[SPEECH_HANDLE_SYMBOL] === true\n );\n}\n\nexport class SpeechHandle {\n /** Priority for messages that should be played after all other messages in the queue */\n static SPEECH_PRIORITY_LOW = 0;\n /** Every speech generates by the VoiceAgent defaults to this priority. */\n static SPEECH_PRIORITY_NORMAL = 5;\n /** Priority for important messages that should be played before others. */\n static SPEECH_PRIORITY_HIGH = 10;\n\n private interruptFut = new Future<void>();\n private authorizedEvent = new Event();\n private scheduledFut = new Future<void>();\n private doneFut = new Future<void>();\n private generations: Future<void>[] = [];\n private _chatItems: ChatItem[] = [];\n\n /** @internal */\n _tasks: Task<void>[] = [];\n\n /** @internal */\n _numSteps = 1;\n\n /** @internal - OpenTelemetry context for the agent turn span */\n _agentTurnContext?: Context;\n\n private itemAddedCallbacks: Set<(item: ChatItem) => void> = new Set();\n private doneCallbacks: Set<(sh: SpeechHandle) => void> = new Set();\n\n /** @internal Symbol marker for type identification */\n readonly [SPEECH_HANDLE_SYMBOL] = true;\n\n constructor(\n private _id: string,\n private _allowInterruptions: boolean,\n /** @internal */\n public _stepIndex: number,\n readonly parent?: SpeechHandle,\n ) {\n this.doneFut.await.finally(() => {\n for (const callback of this.doneCallbacks) {\n callback(this);\n }\n });\n }\n\n static create(options?: {\n allowInterruptions?: boolean;\n stepIndex?: number;\n parent?: SpeechHandle;\n }) {\n const { allowInterruptions = true, stepIndex = 0, parent } = options ?? {};\n\n return new SpeechHandle(shortuuid('speech_'), allowInterruptions, stepIndex, parent);\n }\n\n get interrupted(): boolean {\n return this.interruptFut.done;\n }\n\n get numSteps(): number {\n return this._numSteps;\n }\n\n get id(): string {\n return this._id;\n }\n\n get scheduled(): boolean {\n return this.scheduledFut.done;\n }\n\n get allowInterruptions(): boolean {\n return this._allowInterruptions;\n }\n\n /**\n * Allow or disallow interruptions on this SpeechHandle.\n *\n * When set to false, the SpeechHandle will no longer accept any incoming\n * interruption requests until re-enabled. If the handle is already\n * interrupted, clearing interruptions is not allowed.\n *\n * @param value - true to allow interruptions, false to disallow\n * @throws Error If attempting to disable interruptions when already interrupted\n */\n set allowInterruptions(value: boolean) {\n if (this.interrupted && !value) {\n throw new Error(\n 'Cannot set allow_interruptions to False, the SpeechHandle is already interrupted',\n );\n }\n this._allowInterruptions = value;\n }\n\n done(): boolean {\n return this.doneFut.done;\n }\n\n get chatItems(): ChatItem[] {\n return this._chatItems;\n }\n\n /**\n * Interrupt the current speech generation.\n *\n * @throws Error If this speech handle does not allow interruptions.\n *\n * @returns The same speech handle that was interrupted.\n */\n interrupt(force: boolean = false): SpeechHandle {\n if (!force && !this.allowInterruptions) {\n throw new Error('This generation handle does not allow interruptions');\n }\n\n this._cancel();\n return this;\n }\n\n /**\n * Waits for the entire assistant turn to complete playback.\n *\n * This method waits until the assistant has fully finished speaking,\n * including any finalization steps beyond initial response generation.\n * This is appropriate to call when you want to ensure the speech output\n * has entirely played out, including any tool calls and response follow-ups.\n */\n async waitForPlayout(): Promise<void> {\n const store = asyncLocalStorage.getStore();\n if (store && store?.functionCall) {\n throw new Error(\n `Cannot call 'SpeechHandle.waitForPlayout()' from inside the function tool '${store.functionCall.name}'. ` +\n 'This creates a circular wait: the speech handle is waiting for the function tool to complete, ' +\n 'while the function tool is simultaneously waiting for the speech handle.\\n' +\n \"To wait for the assistant's spoken response prior to running this tool, use RunContext.wait_for_playout() instead.\",\n );\n }\n await this.doneFut.await;\n }\n\n async waitIfNotInterrupted(aw: Promise<unknown>[]): Promise<void> {\n const allTasksPromise = Promise.all(aw);\n const fs: Promise<unknown>[] = [allTasksPromise, this.interruptFut.await];\n await Promise.race(fs);\n }\n\n addDoneCallback(callback: (sh: SpeechHandle) => void) {\n this.doneCallbacks.add(callback);\n }\n\n removeDoneCallback(callback: (sh: SpeechHandle) => void) {\n this.doneCallbacks.delete(callback);\n }\n\n /** @internal */\n _cancel(): SpeechHandle {\n if (this.done()) {\n return this;\n }\n\n if (!this.interruptFut.done) {\n this.interruptFut.resolve();\n }\n\n return this;\n }\n\n /** @internal */\n _authorizeGeneration(): void {\n const fut = new Future<void>();\n this.generations.push(fut);\n this.authorizedEvent.set();\n }\n\n /** @internal */\n _clearAuthorization(): void {\n this.authorizedEvent.clear();\n }\n\n /** @internal */\n async _waitForAuthorization(): Promise<void> {\n await this.authorizedEvent.wait();\n }\n\n /** @internal */\n async _waitForGeneration(stepIdx: number = -1): Promise<void> {\n if (this.generations.length === 0) {\n throw new Error('cannot use wait_for_generation: no active generation is running.');\n }\n\n const index = stepIdx === -1 ? this.generations.length - 1 : stepIdx;\n const generation = this.generations[index];\n if (!generation) {\n throw new Error(`Generation at index ${index} not found.`);\n }\n return generation.await;\n }\n\n /** @internal */\n async _waitForScheduled(): Promise<void> {\n return this.scheduledFut.await;\n }\n\n /** @internal */\n _markGenerationDone(): void {\n if (this.generations.length === 0) {\n throw new Error('cannot use mark_generation_done: no active generation is running.');\n }\n\n const lastGeneration = this.generations[this.generations.length - 1];\n if (lastGeneration && !lastGeneration.done) {\n lastGeneration.resolve();\n }\n }\n\n /** @internal */\n _markDone(): void {\n if (!this.doneFut.done) {\n this.doneFut.resolve();\n if (this.generations.length > 0) {\n this._markGenerationDone(); // preemptive generation could be cancelled before being scheduled\n }\n }\n }\n\n /** @internal */\n _markScheduled(): void {\n if (!this.scheduledFut.done) {\n this.scheduledFut.resolve();\n }\n }\n\n /** @internal */\n _addItemAddedCallback(callback: (item: ChatItem) => void): void {\n this.itemAddedCallbacks.add(callback);\n }\n\n /** @internal */\n _removeItemAddedCallback(callback: (item: ChatItem) => void): void {\n this.itemAddedCallbacks.delete(callback);\n }\n\n /** @internal */\n _itemAdded(items: ChatItem[]): void {\n for (const item of items) {\n for (const cb of this.itemAddedCallbacks) {\n cb(item);\n }\n this._chatItems.push(item);\n }\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/voice/speech_handle.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { Context } from '@opentelemetry/api';\nimport type { ChatItem } from '../llm/index.js';\nimport type { Task } from '../utils.js';\nimport { Event, Future, shortuuid } from '../utils.js';\nimport { functionCallStorage } from './agent.js';\n\n/** Symbol used to identify SpeechHandle instances */\nconst SPEECH_HANDLE_SYMBOL = Symbol.for('livekit.agents.SpeechHandle');\n\n/**\n * Type guard to check if a value is a SpeechHandle.\n */\nexport function isSpeechHandle(value: unknown): value is SpeechHandle {\n return (\n typeof value === 'object' &&\n value !== null &&\n SPEECH_HANDLE_SYMBOL in value &&\n (value as Record<symbol, boolean>)[SPEECH_HANDLE_SYMBOL] === true\n );\n}\n\nexport class SpeechHandle {\n /** Priority for messages that should be played after all other messages in the queue */\n static SPEECH_PRIORITY_LOW = 0;\n /** Every speech generates by the VoiceAgent defaults to this priority. */\n static SPEECH_PRIORITY_NORMAL = 5;\n /** Priority for important messages that should be played before others. */\n static SPEECH_PRIORITY_HIGH = 10;\n\n private interruptFut = new Future<void>();\n private authorizedEvent = new Event();\n private scheduledFut = new Future<void>();\n private doneFut = new Future<void>();\n private generations: Future<void>[] = [];\n private _chatItems: ChatItem[] = [];\n\n /** @internal */\n _tasks: Task<void>[] = [];\n\n /** @internal */\n _numSteps = 1;\n\n /** @internal - OpenTelemetry context for the agent turn span */\n _agentTurnContext?: Context;\n\n /** @internal - used by AgentTask/RunResult final output plumbing */\n _maybeRunFinalOutput?: unknown;\n\n private itemAddedCallbacks: Set<(item: ChatItem) => void> = new Set();\n private doneCallbacks: Set<(sh: SpeechHandle) => void> = new Set();\n\n /** @internal Symbol marker for type identification */\n readonly [SPEECH_HANDLE_SYMBOL] = true;\n\n constructor(\n private _id: string,\n private _allowInterruptions: boolean,\n /** @internal */\n public _stepIndex: number,\n readonly parent?: SpeechHandle,\n ) {\n this.doneFut.await.finally(() => {\n for (const callback of this.doneCallbacks) {\n callback(this);\n }\n });\n }\n\n static create(options?: {\n allowInterruptions?: boolean;\n stepIndex?: number;\n parent?: SpeechHandle;\n }) {\n const { allowInterruptions = true, stepIndex = 0, parent } = options ?? {};\n\n return new SpeechHandle(shortuuid('speech_'), allowInterruptions, stepIndex, parent);\n }\n\n get interrupted(): boolean {\n return this.interruptFut.done;\n }\n\n get numSteps(): number {\n return this._numSteps;\n }\n\n get id(): string {\n return this._id;\n }\n\n get scheduled(): boolean {\n return this.scheduledFut.done;\n }\n\n get allowInterruptions(): boolean {\n return this._allowInterruptions;\n }\n\n /**\n * Allow or disallow interruptions on this SpeechHandle.\n *\n * When set to false, the SpeechHandle will no longer accept any incoming\n * interruption requests until re-enabled. If the handle is already\n * interrupted, clearing interruptions is not allowed.\n *\n * @param value - true to allow interruptions, false to disallow\n * @throws Error If attempting to disable interruptions when already interrupted\n */\n set allowInterruptions(value: boolean) {\n if (this.interrupted && !value) {\n throw new Error(\n 'Cannot set allow_interruptions to False, the SpeechHandle is already interrupted',\n );\n }\n this._allowInterruptions = value;\n }\n\n done(): boolean {\n return this.doneFut.done;\n }\n\n get chatItems(): ChatItem[] {\n return this._chatItems;\n }\n\n /**\n * Interrupt the current speech generation.\n *\n * @throws Error If this speech handle does not allow interruptions.\n *\n * @returns The same speech handle that was interrupted.\n */\n interrupt(force: boolean = false): SpeechHandle {\n if (!force && !this.allowInterruptions) {\n throw new Error('This generation handle does not allow interruptions');\n }\n\n this._cancel();\n return this;\n }\n\n /**\n * Waits for the entire assistant turn to complete playback.\n *\n * This method waits until the assistant has fully finished speaking,\n * including any finalization steps beyond initial response generation.\n * This is appropriate to call when you want to ensure the speech output\n * has entirely played out, including any tool calls and response follow-ups.\n */\n async waitForPlayout(): Promise<void> {\n const store = functionCallStorage.getStore();\n if (store && store?.functionCall) {\n throw new Error(\n `Cannot call 'SpeechHandle.waitForPlayout()' from inside the function tool '${store.functionCall.name}'. ` +\n 'This creates a circular wait: the speech handle is waiting for the function tool to complete, ' +\n 'while the function tool is simultaneously waiting for the speech handle.\\n' +\n \"To wait for the assistant's spoken response prior to running this tool, use RunContext.wait_for_playout() instead.\",\n );\n }\n await this.doneFut.await;\n }\n\n async waitIfNotInterrupted(aw: Promise<unknown>[]): Promise<void> {\n const allTasksPromise = Promise.all(aw);\n const fs: Promise<unknown>[] = [allTasksPromise, this.interruptFut.await];\n await Promise.race(fs);\n }\n\n addDoneCallback(callback: (sh: SpeechHandle) => void) {\n if (this.done()) {\n queueMicrotask(() => callback(this));\n return;\n }\n this.doneCallbacks.add(callback);\n }\n\n removeDoneCallback(callback: (sh: SpeechHandle) => void) {\n this.doneCallbacks.delete(callback);\n }\n\n /** @internal */\n _cancel(): SpeechHandle {\n if (this.done()) {\n return this;\n }\n\n if (!this.interruptFut.done) {\n this.interruptFut.resolve();\n }\n\n return this;\n }\n\n /** @internal */\n _authorizeGeneration(): void {\n const fut = new Future<void>();\n this.generations.push(fut);\n this.authorizedEvent.set();\n }\n\n /** @internal */\n _clearAuthorization(): void {\n this.authorizedEvent.clear();\n }\n\n /** @internal */\n async _waitForAuthorization(): Promise<void> {\n await this.authorizedEvent.wait();\n }\n\n /** @internal */\n async _waitForGeneration(stepIdx: number = -1): Promise<void> {\n if (this.generations.length === 0) {\n throw new Error('cannot use wait_for_generation: no active generation is running.');\n }\n\n const index = stepIdx === -1 ? this.generations.length - 1 : stepIdx;\n const generation = this.generations[index];\n if (!generation) {\n throw new Error(`Generation at index ${index} not found.`);\n }\n return generation.await;\n }\n\n /** @internal */\n async _waitForScheduled(): Promise<void> {\n return this.scheduledFut.await;\n }\n\n /** @internal */\n _markGenerationDone(): void {\n if (this.generations.length === 0) {\n throw new Error('cannot use mark_generation_done: no active generation is running.');\n }\n\n const lastGeneration = this.generations[this.generations.length - 1];\n if (lastGeneration && !lastGeneration.done) {\n lastGeneration.resolve();\n }\n }\n\n /** @internal */\n _markDone(): void {\n if (!this.doneFut.done) {\n this.doneFut.resolve();\n if (this.generations.length > 0) {\n this._markGenerationDone(); // preemptive generation could be cancelled before being scheduled\n }\n }\n }\n\n /** @internal */\n _markScheduled(): void {\n if (!this.scheduledFut.done) {\n this.scheduledFut.resolve();\n }\n }\n\n /** @internal */\n _addItemAddedCallback(callback: (item: ChatItem) => void): void {\n this.itemAddedCallbacks.add(callback);\n }\n\n /** @internal */\n _removeItemAddedCallback(callback: (item: ChatItem) => void): void {\n this.itemAddedCallbacks.delete(callback);\n }\n\n /** @internal */\n _itemAdded(items: ChatItem[]): void {\n for (const item of items) {\n for (const cb of this.itemAddedCallbacks) {\n cb(item);\n }\n this._chatItems.push(item);\n }\n }\n}\n"],"mappings":"AAMA,SAAS,OAAO,QAAQ,iBAAiB;AACzC,SAAS,2BAA2B;AAGpC,MAAM,uBAAuB,OAAO,IAAI,6BAA6B;AAK9D,SAAS,eAAe,OAAuC;AACpE,SACE,OAAO,UAAU,YACjB,UAAU,QACV,wBAAwB,SACvB,MAAkC,oBAAoB,MAAM;AAEjE;AAEO,MAAM,aAAa;AAAA,EAiCxB,YACU,KACA,qBAED,YACE,QACT;AALQ;AACA;AAED;AACE;AAET,SAAK,QAAQ,MAAM,QAAQ,MAAM;AAC/B,iBAAW,YAAY,KAAK,eAAe;AACzC,iBAAS,IAAI;AAAA,MACf;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA,EA3CA,OAAO,sBAAsB;AAAA;AAAA,EAE7B,OAAO,yBAAyB;AAAA;AAAA,EAEhC,OAAO,uBAAuB;AAAA,EAEtB,eAAe,IAAI,OAAa;AAAA,EAChC,kBAAkB,IAAI,MAAM;AAAA,EAC5B,eAAe,IAAI,OAAa;AAAA,EAChC,UAAU,IAAI,OAAa;AAAA,EAC3B,cAA8B,CAAC;AAAA,EAC/B,aAAyB,CAAC;AAAA;AAAA,EAGlC,SAAuB,CAAC;AAAA;AAAA,EAGxB,YAAY;AAAA;AAAA,EAGZ;AAAA;AAAA,EAGA;AAAA,EAEQ,qBAAoD,oBAAI,IAAI;AAAA,EAC5D,gBAAiD,oBAAI,IAAI;AAAA;AAAA,EAGjE,CAAU,oBAAoB,IAAI;AAAA,EAgBlC,OAAO,OAAO,SAIX;AACD,UAAM,EAAE,qBAAqB,MAAM,YAAY,GAAG,OAAO,IAAI,WAAW,CAAC;AAEzE,WAAO,IAAI,aAAa,UAAU,SAAS,GAAG,oBAAoB,WAAW,MAAM;AAAA,EACrF;AAAA,EAEA,IAAI,cAAuB;AACzB,WAAO,KAAK,aAAa;AAAA,EAC3B;AAAA,EAEA,IAAI,WAAmB;AACrB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,KAAa;AACf,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,YAAqB;AACvB,WAAO,KAAK,aAAa;AAAA,EAC3B;AAAA,EAEA,IAAI,qBAA8B;AAChC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,IAAI,mBAAmB,OAAgB;AACrC,QAAI,KAAK,eAAe,CAAC,OAAO;AAC9B,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,SAAK,sBAAsB;AAAA,EAC7B;AAAA,EAEA,OAAgB;AACd,WAAO,KAAK,QAAQ;AAAA,EACtB;AAAA,EAEA,IAAI,YAAwB;AAC1B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,UAAU,QAAiB,OAAqB;AAC9C,QAAI,CAAC,SAAS,CAAC,KAAK,oBAAoB;AACtC,YAAM,IAAI,MAAM,qDAAqD;AAAA,IACvE;AAEA,SAAK,QAAQ;AACb,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,iBAAgC;AACpC,UAAM,QAAQ,oBAAoB,SAAS;AAC3C,QAAI,UAAS,+BAAO,eAAc;AAChC,YAAM,IAAI;AAAA,QACR,8EAA8E,MAAM,aAAa,IAAI;AAAA;AAAA,MAIvG;AAAA,IACF;AACA,UAAM,KAAK,QAAQ;AAAA,EACrB;AAAA,EAEA,MAAM,qBAAqB,IAAuC;AAChE,UAAM,kBAAkB,QAAQ,IAAI,EAAE;AACtC,UAAM,KAAyB,CAAC,iBAAiB,KAAK,aAAa,KAAK;AACxE,UAAM,QAAQ,KAAK,EAAE;AAAA,EACvB;AAAA,EAEA,gBAAgB,UAAsC;AACpD,QAAI,KAAK,KAAK,GAAG;AACf,qBAAe,MAAM,SAAS,IAAI,CAAC;AACnC;AAAA,IACF;AACA,SAAK,cAAc,IAAI,QAAQ;AAAA,EACjC;AAAA,EAEA,mBAAmB,UAAsC;AACvD,SAAK,cAAc,OAAO,QAAQ;AAAA,EACpC;AAAA;AAAA,EAGA,UAAwB;AACtB,QAAI,KAAK,KAAK,GAAG;AACf,aAAO;AAAA,IACT;AAEA,QAAI,CAAC,KAAK,aAAa,MAAM;AAC3B,WAAK,aAAa,QAAQ;AAAA,IAC5B;AAEA,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,uBAA6B;AAC3B,UAAM,MAAM,IAAI,OAAa;AAC7B,SAAK,YAAY,KAAK,GAAG;AACzB,SAAK,gBAAgB,IAAI;AAAA,EAC3B;AAAA;AAAA,EAGA,sBAA4B;AAC1B,SAAK,gBAAgB,MAAM;AAAA,EAC7B;AAAA;AAAA,EAGA,MAAM,wBAAuC;AAC3C,UAAM,KAAK,gBAAgB,KAAK;AAAA,EAClC;AAAA;AAAA,EAGA,MAAM,mBAAmB,UAAkB,IAAmB;AAC5D,QAAI,KAAK,YAAY,WAAW,GAAG;AACjC,YAAM,IAAI,MAAM,kEAAkE;AAAA,IACpF;AAEA,UAAM,QAAQ,YAAY,KAAK,KAAK,YAAY,SAAS,IAAI;AAC7D,UAAM,aAAa,KAAK,YAAY,KAAK;AACzC,QAAI,CAAC,YAAY;AACf,YAAM,IAAI,MAAM,uBAAuB,KAAK,aAAa;AAAA,IAC3D;AACA,WAAO,WAAW;AAAA,EACpB;AAAA;AAAA,EAGA,MAAM,oBAAmC;AACvC,WAAO,KAAK,aAAa;AAAA,EAC3B;AAAA;AAAA,EAGA,sBAA4B;AAC1B,QAAI,KAAK,YAAY,WAAW,GAAG;AACjC,YAAM,IAAI,MAAM,mEAAmE;AAAA,IACrF;AAEA,UAAM,iBAAiB,KAAK,YAAY,KAAK,YAAY,SAAS,CAAC;AACnE,QAAI,kBAAkB,CAAC,eAAe,MAAM;AAC1C,qBAAe,QAAQ;AAAA,IACzB;AAAA,EACF;AAAA;AAAA,EAGA,YAAkB;AAChB,QAAI,CAAC,KAAK,QAAQ,MAAM;AACtB,WAAK,QAAQ,QAAQ;AACrB,UAAI,KAAK,YAAY,SAAS,GAAG;AAC/B,aAAK,oBAAoB;AAAA,MAC3B;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGA,iBAAuB;AACrB,QAAI,CAAC,KAAK,aAAa,MAAM;AAC3B,WAAK,aAAa,QAAQ;AAAA,IAC5B;AAAA,EACF;AAAA;AAAA,EAGA,sBAAsB,UAA0C;AAC9D,SAAK,mBAAmB,IAAI,QAAQ;AAAA,EACtC;AAAA;AAAA,EAGA,yBAAyB,UAA0C;AACjE,SAAK,mBAAmB,OAAO,QAAQ;AAAA,EACzC;AAAA;AAAA,EAGA,WAAW,OAAyB;AAClC,eAAW,QAAQ,OAAO;AACxB,iBAAW,MAAM,KAAK,oBAAoB;AACxC,WAAG,IAAI;AAAA,MACT;AACA,WAAK,WAAW,KAAK,IAAI;AAAA,IAC3B;AAAA,EACF;AACF;","names":[]}
@@ -40,16 +40,18 @@ class RunResult {
40
40
  _events = [];
41
41
  doneFut = new import_utils.Future();
42
42
  userInput;
43
+ outputType;
44
+ finalOutputValue;
45
+ hasFinalOutput = false;
43
46
  handles = /* @__PURE__ */ new Set();
44
47
  lastSpeechHandle;
45
48
  runAssert;
46
- // TODO(brian): Add typed output support for parity with Python
47
- // - Add outputType?: new (...args: unknown[]) => T
48
- // - Add finalOutput?: T
49
- // - Implement markDone() to extract final_output from SpeechHandle.maybeRunFinalOutput
50
- // - See Python: run_result.py lines 182-201
49
+ // Store per-handle closures so _unwatchHandle can remove callbacks symmetrically.
50
+ doneCallbacks = /* @__PURE__ */ new Map();
51
+ itemAddedCallback = (item) => this._itemAdded(item);
51
52
  constructor(options) {
52
53
  this.userInput = options == null ? void 0 : options.userInput;
54
+ this.outputType = options == null ? void 0 : options.outputType;
53
55
  }
54
56
  /**
55
57
  * List of all recorded events generated during the run.
@@ -80,11 +82,15 @@ ${eventsStr}
80
82
  }
81
83
  /**
82
84
  * Returns the final output of the run after completion.
83
- *
84
- * @throws Error - Not implemented yet.
85
85
  */
86
86
  get finalOutput() {
87
- throw new Error("finalOutput is not yet implemented in JS.");
87
+ if (!this.doneFut.done) {
88
+ throw new Error("cannot retrieve finalOutput, RunResult is not done");
89
+ }
90
+ if (!this.hasFinalOutput) {
91
+ throw new Error("no final output");
92
+ }
93
+ return this.finalOutputValue;
88
94
  }
89
95
  /**
90
96
  * Indicates whether the run has finished processing all events.
@@ -146,13 +152,14 @@ ${eventsStr}
146
152
  * Watch a speech handle or task for completion.
147
153
  */
148
154
  _watchHandle(handle) {
155
+ if (this.handles.has(handle)) return;
149
156
  this.handles.add(handle);
150
157
  if ((0, import_speech_handle.isSpeechHandle)(handle)) {
151
- handle._addItemAddedCallback(this._itemAdded.bind(this));
158
+ handle._addItemAddedCallback(this.itemAddedCallback);
152
159
  }
153
- handle.addDoneCallback(() => {
154
- this._markDoneIfNeeded(handle);
155
- });
160
+ const doneCallback = () => this._markDoneIfNeeded(handle);
161
+ this.doneCallbacks.set(handle, doneCallback);
162
+ handle.addDoneCallback(doneCallback);
156
163
  }
157
164
  /**
158
165
  * @internal
@@ -160,22 +167,66 @@ ${eventsStr}
160
167
  */
161
168
  _unwatchHandle(handle) {
162
169
  this.handles.delete(handle);
170
+ const doneCallback = this.doneCallbacks.get(handle);
171
+ if (doneCallback) {
172
+ handle.removeDoneCallback(doneCallback);
173
+ this.doneCallbacks.delete(handle);
174
+ }
163
175
  if ((0, import_speech_handle.isSpeechHandle)(handle)) {
164
- handle._removeItemAddedCallback(this._itemAdded.bind(this));
176
+ handle._removeItemAddedCallback(this.itemAddedCallback);
165
177
  }
166
178
  }
179
+ /** @internal */
180
+ _watchedHandleCount() {
181
+ return this.handles.size;
182
+ }
183
+ /** @internal – Reject the run with an error (e.g. when deferred generateReply fails). */
184
+ _reject(error) {
185
+ if (!this.doneFut.done) {
186
+ this.doneFut.reject(error);
187
+ }
188
+ }
189
+ /** @internal */
167
190
  _markDoneIfNeeded(handle) {
168
191
  if ((0, import_speech_handle.isSpeechHandle)(handle)) {
169
192
  this.lastSpeechHandle = handle;
170
193
  }
171
- if ([...this.handles].every((h) => (0, import_speech_handle.isSpeechHandle)(h) ? h.done() : h.done)) {
194
+ const allDone = [...this.handles].every((h) => (0, import_speech_handle.isSpeechHandle)(h) ? h.done() : h.done);
195
+ if (allDone) {
172
196
  this._markDone();
173
197
  }
174
198
  }
175
199
  _markDone() {
176
- if (!this.doneFut.done) {
200
+ if (this.doneFut.done) {
201
+ return;
202
+ }
203
+ if (!this.lastSpeechHandle) {
204
+ this.doneFut.resolve();
205
+ return;
206
+ }
207
+ const finalOutput = this.lastSpeechHandle._maybeRunFinalOutput;
208
+ if (finalOutput instanceof Error) {
209
+ this.doneFut.reject(finalOutput);
210
+ return;
211
+ }
212
+ if (this.outputType) {
213
+ const result = this.outputType.safeParse(finalOutput);
214
+ if (!result.success) {
215
+ this.doneFut.reject(
216
+ new Error(`Expected output matching provided zod schema: ${result.error.message}`)
217
+ );
218
+ return;
219
+ }
220
+ this.finalOutputValue = result.data;
221
+ this.hasFinalOutput = true;
177
222
  this.doneFut.resolve();
223
+ return;
224
+ }
225
+ if (finalOutput !== void 0) {
226
+ this.finalOutputValue = finalOutput;
227
+ this.hasFinalOutput = true;
178
228
  }
229
+ this.doneFut.resolve();
179
230
  }
180
231
  /**
181
232
  * Find the correct insertion index to maintain chronological order.