@livekit/agents 1.0.17 → 1.0.19

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 (216) hide show
  1. package/dist/index.cjs +3 -0
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +2 -1
  4. package/dist/index.d.ts +2 -1
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +2 -0
  7. package/dist/index.js.map +1 -1
  8. package/dist/inference/api_protos.d.cts +12 -12
  9. package/dist/inference/api_protos.d.ts +12 -12
  10. package/dist/inference/llm.cjs +35 -13
  11. package/dist/inference/llm.cjs.map +1 -1
  12. package/dist/inference/llm.d.cts +10 -5
  13. package/dist/inference/llm.d.ts +10 -5
  14. package/dist/inference/llm.d.ts.map +1 -1
  15. package/dist/inference/llm.js +35 -13
  16. package/dist/inference/llm.js.map +1 -1
  17. package/dist/inference/tts.cjs +1 -1
  18. package/dist/inference/tts.cjs.map +1 -1
  19. package/dist/inference/tts.js +1 -1
  20. package/dist/inference/tts.js.map +1 -1
  21. package/dist/ipc/job_proc_lazy_main.cjs +6 -2
  22. package/dist/ipc/job_proc_lazy_main.cjs.map +1 -1
  23. package/dist/ipc/job_proc_lazy_main.js +6 -2
  24. package/dist/ipc/job_proc_lazy_main.js.map +1 -1
  25. package/dist/job.cjs +31 -0
  26. package/dist/job.cjs.map +1 -1
  27. package/dist/job.d.cts +6 -0
  28. package/dist/job.d.ts +6 -0
  29. package/dist/job.d.ts.map +1 -1
  30. package/dist/job.js +31 -0
  31. package/dist/job.js.map +1 -1
  32. package/dist/llm/chat_context.cjs +33 -0
  33. package/dist/llm/chat_context.cjs.map +1 -1
  34. package/dist/llm/chat_context.d.cts +22 -2
  35. package/dist/llm/chat_context.d.ts +22 -2
  36. package/dist/llm/chat_context.d.ts.map +1 -1
  37. package/dist/llm/chat_context.js +32 -0
  38. package/dist/llm/chat_context.js.map +1 -1
  39. package/dist/llm/index.cjs +2 -0
  40. package/dist/llm/index.cjs.map +1 -1
  41. package/dist/llm/index.d.cts +1 -1
  42. package/dist/llm/index.d.ts +1 -1
  43. package/dist/llm/index.d.ts.map +1 -1
  44. package/dist/llm/index.js +2 -0
  45. package/dist/llm/index.js.map +1 -1
  46. package/dist/llm/llm.cjs.map +1 -1
  47. package/dist/llm/llm.d.cts +1 -1
  48. package/dist/llm/llm.d.ts +1 -1
  49. package/dist/llm/llm.d.ts.map +1 -1
  50. package/dist/llm/llm.js.map +1 -1
  51. package/dist/llm/provider_format/google.cjs.map +1 -1
  52. package/dist/llm/provider_format/google.d.cts +1 -1
  53. package/dist/llm/provider_format/google.d.ts +1 -1
  54. package/dist/llm/provider_format/google.d.ts.map +1 -1
  55. package/dist/llm/provider_format/google.js.map +1 -1
  56. package/dist/llm/provider_format/google.test.cjs +48 -0
  57. package/dist/llm/provider_format/google.test.cjs.map +1 -1
  58. package/dist/llm/provider_format/google.test.js +54 -1
  59. package/dist/llm/provider_format/google.test.js.map +1 -1
  60. package/dist/llm/provider_format/index.d.cts +1 -1
  61. package/dist/llm/provider_format/index.d.ts +1 -1
  62. package/dist/llm/provider_format/index.d.ts.map +1 -1
  63. package/dist/llm/provider_format/openai.cjs +1 -2
  64. package/dist/llm/provider_format/openai.cjs.map +1 -1
  65. package/dist/llm/provider_format/openai.js +1 -2
  66. package/dist/llm/provider_format/openai.js.map +1 -1
  67. package/dist/llm/provider_format/openai.test.cjs +32 -0
  68. package/dist/llm/provider_format/openai.test.cjs.map +1 -1
  69. package/dist/llm/provider_format/openai.test.js +38 -1
  70. package/dist/llm/provider_format/openai.test.js.map +1 -1
  71. package/dist/llm/realtime.cjs.map +1 -1
  72. package/dist/llm/realtime.d.cts +4 -0
  73. package/dist/llm/realtime.d.ts +4 -0
  74. package/dist/llm/realtime.d.ts.map +1 -1
  75. package/dist/llm/realtime.js.map +1 -1
  76. package/dist/llm/utils.cjs +2 -2
  77. package/dist/llm/utils.cjs.map +1 -1
  78. package/dist/llm/utils.d.cts +1 -1
  79. package/dist/llm/utils.d.ts +1 -1
  80. package/dist/llm/utils.d.ts.map +1 -1
  81. package/dist/llm/utils.js +2 -2
  82. package/dist/llm/utils.js.map +1 -1
  83. package/dist/llm/zod-utils.cjs +6 -3
  84. package/dist/llm/zod-utils.cjs.map +1 -1
  85. package/dist/llm/zod-utils.d.cts +1 -1
  86. package/dist/llm/zod-utils.d.ts +1 -1
  87. package/dist/llm/zod-utils.d.ts.map +1 -1
  88. package/dist/llm/zod-utils.js +6 -3
  89. package/dist/llm/zod-utils.js.map +1 -1
  90. package/dist/llm/zod-utils.test.cjs +83 -0
  91. package/dist/llm/zod-utils.test.cjs.map +1 -1
  92. package/dist/llm/zod-utils.test.js +83 -0
  93. package/dist/llm/zod-utils.test.js.map +1 -1
  94. package/dist/log.cjs.map +1 -1
  95. package/dist/log.d.ts.map +1 -1
  96. package/dist/log.js.map +1 -1
  97. package/dist/telemetry/index.cjs +51 -0
  98. package/dist/telemetry/index.cjs.map +1 -0
  99. package/dist/telemetry/index.d.cts +4 -0
  100. package/dist/telemetry/index.d.ts +4 -0
  101. package/dist/telemetry/index.d.ts.map +1 -0
  102. package/dist/telemetry/index.js +12 -0
  103. package/dist/telemetry/index.js.map +1 -0
  104. package/dist/telemetry/trace_types.cjs +191 -0
  105. package/dist/telemetry/trace_types.cjs.map +1 -0
  106. package/dist/telemetry/trace_types.d.cts +56 -0
  107. package/dist/telemetry/trace_types.d.ts +56 -0
  108. package/dist/telemetry/trace_types.d.ts.map +1 -0
  109. package/dist/telemetry/trace_types.js +113 -0
  110. package/dist/telemetry/trace_types.js.map +1 -0
  111. package/dist/telemetry/traces.cjs +196 -0
  112. package/dist/telemetry/traces.cjs.map +1 -0
  113. package/dist/telemetry/traces.d.cts +97 -0
  114. package/dist/telemetry/traces.d.ts +97 -0
  115. package/dist/telemetry/traces.d.ts.map +1 -0
  116. package/dist/telemetry/traces.js +173 -0
  117. package/dist/telemetry/traces.js.map +1 -0
  118. package/dist/telemetry/utils.cjs +86 -0
  119. package/dist/telemetry/utils.cjs.map +1 -0
  120. package/dist/telemetry/utils.d.cts +5 -0
  121. package/dist/telemetry/utils.d.ts +5 -0
  122. package/dist/telemetry/utils.d.ts.map +1 -0
  123. package/dist/telemetry/utils.js +51 -0
  124. package/dist/telemetry/utils.js.map +1 -0
  125. package/dist/tts/tts.cjs.map +1 -1
  126. package/dist/tts/tts.d.ts.map +1 -1
  127. package/dist/tts/tts.js.map +1 -1
  128. package/dist/utils.cjs.map +1 -1
  129. package/dist/utils.d.cts +7 -0
  130. package/dist/utils.d.ts +7 -0
  131. package/dist/utils.d.ts.map +1 -1
  132. package/dist/utils.js.map +1 -1
  133. package/dist/voice/agent.cjs +15 -0
  134. package/dist/voice/agent.cjs.map +1 -1
  135. package/dist/voice/agent.d.cts +4 -1
  136. package/dist/voice/agent.d.ts +4 -1
  137. package/dist/voice/agent.d.ts.map +1 -1
  138. package/dist/voice/agent.js +15 -0
  139. package/dist/voice/agent.js.map +1 -1
  140. package/dist/voice/agent_activity.cjs +71 -20
  141. package/dist/voice/agent_activity.cjs.map +1 -1
  142. package/dist/voice/agent_activity.d.ts.map +1 -1
  143. package/dist/voice/agent_activity.js +71 -20
  144. package/dist/voice/agent_activity.js.map +1 -1
  145. package/dist/voice/agent_session.cjs +69 -2
  146. package/dist/voice/agent_session.cjs.map +1 -1
  147. package/dist/voice/agent_session.d.cts +11 -2
  148. package/dist/voice/agent_session.d.ts +11 -2
  149. package/dist/voice/agent_session.d.ts.map +1 -1
  150. package/dist/voice/agent_session.js +70 -3
  151. package/dist/voice/agent_session.js.map +1 -1
  152. package/dist/voice/audio_recognition.cjs.map +1 -1
  153. package/dist/voice/audio_recognition.d.ts.map +1 -1
  154. package/dist/voice/audio_recognition.js.map +1 -1
  155. package/dist/voice/generation.cjs.map +1 -1
  156. package/dist/voice/generation.d.ts.map +1 -1
  157. package/dist/voice/generation.js.map +1 -1
  158. package/dist/voice/index.cjs +2 -0
  159. package/dist/voice/index.cjs.map +1 -1
  160. package/dist/voice/index.d.cts +1 -0
  161. package/dist/voice/index.d.ts +1 -0
  162. package/dist/voice/index.d.ts.map +1 -1
  163. package/dist/voice/index.js +1 -0
  164. package/dist/voice/index.js.map +1 -1
  165. package/dist/voice/interruption_detection.test.cjs +114 -0
  166. package/dist/voice/interruption_detection.test.cjs.map +1 -0
  167. package/dist/voice/interruption_detection.test.js +113 -0
  168. package/dist/voice/interruption_detection.test.js.map +1 -0
  169. package/dist/voice/report.cjs +69 -0
  170. package/dist/voice/report.cjs.map +1 -0
  171. package/dist/voice/report.d.cts +26 -0
  172. package/dist/voice/report.d.ts +26 -0
  173. package/dist/voice/report.d.ts.map +1 -0
  174. package/dist/voice/report.js +44 -0
  175. package/dist/voice/report.js.map +1 -0
  176. package/dist/voice/room_io/room_io.cjs +3 -0
  177. package/dist/voice/room_io/room_io.cjs.map +1 -1
  178. package/dist/voice/room_io/room_io.d.cts +1 -0
  179. package/dist/voice/room_io/room_io.d.ts +1 -0
  180. package/dist/voice/room_io/room_io.d.ts.map +1 -1
  181. package/dist/voice/room_io/room_io.js +3 -0
  182. package/dist/voice/room_io/room_io.js.map +1 -1
  183. package/package.json +12 -5
  184. package/src/index.ts +2 -1
  185. package/src/inference/llm.ts +53 -21
  186. package/src/inference/tts.ts +1 -1
  187. package/src/ipc/job_proc_lazy_main.ts +10 -2
  188. package/src/job.ts +48 -0
  189. package/src/llm/__snapshots__/zod-utils.test.ts.snap +218 -0
  190. package/src/llm/chat_context.ts +53 -1
  191. package/src/llm/index.ts +1 -0
  192. package/src/llm/llm.ts +3 -1
  193. package/src/llm/provider_format/google.test.ts +72 -1
  194. package/src/llm/provider_format/google.ts +4 -4
  195. package/src/llm/provider_format/openai.test.ts +55 -1
  196. package/src/llm/provider_format/openai.ts +3 -2
  197. package/src/llm/realtime.ts +8 -1
  198. package/src/llm/utils.ts +7 -2
  199. package/src/llm/zod-utils.test.ts +101 -0
  200. package/src/llm/zod-utils.ts +12 -3
  201. package/src/log.ts +1 -0
  202. package/src/telemetry/index.ts +10 -0
  203. package/src/telemetry/trace_types.ts +88 -0
  204. package/src/telemetry/traces.ts +266 -0
  205. package/src/telemetry/utils.ts +61 -0
  206. package/src/tts/tts.ts +4 -0
  207. package/src/utils.ts +17 -0
  208. package/src/voice/agent.ts +22 -0
  209. package/src/voice/agent_activity.ts +102 -24
  210. package/src/voice/agent_session.ts +98 -1
  211. package/src/voice/audio_recognition.ts +2 -0
  212. package/src/voice/generation.ts +3 -0
  213. package/src/voice/index.ts +1 -0
  214. package/src/voice/interruption_detection.test.ts +151 -0
  215. package/src/voice/report.ts +77 -0
  216. package/src/voice/room_io/room_io.ts +4 -0
@@ -28,6 +28,7 @@ function isStopResponse(value) {
28
28
  return value !== void 0 && value !== null && typeof value === "object" && STOP_RESPONSE_SYMBOL in value;
29
29
  }
30
30
  class Agent {
31
+ _id;
31
32
  turnDetection;
32
33
  _stt;
33
34
  _vad;
@@ -42,6 +43,7 @@ class Agent {
42
43
  /** @internal */
43
44
  _tools;
44
45
  constructor({
46
+ id,
45
47
  instructions,
46
48
  chatCtx,
47
49
  tools,
@@ -51,6 +53,16 @@ class Agent {
51
53
  llm,
52
54
  tts
53
55
  }) {
56
+ if (id) {
57
+ this._id = id;
58
+ } else {
59
+ const className = this.constructor.name;
60
+ if (className === "Agent") {
61
+ this._id = "default_agent";
62
+ } else {
63
+ this._id = className.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "");
64
+ }
65
+ }
54
66
  this._instructions = instructions;
55
67
  this._tools = { ...tools };
56
68
  this._chatCtx = chatCtx ? chatCtx.copy({
@@ -90,6 +102,9 @@ class Agent {
90
102
  get chatCtx() {
91
103
  return new ReadonlyChatContext(this._chatCtx.items);
92
104
  }
105
+ get id() {
106
+ return this._id;
107
+ }
93
108
  get instructions() {
94
109
  return this._instructions;
95
110
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/voice/agent.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { AudioFrame } from '@livekit/rtc-node';\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport { ReadableStream } from 'node:stream/web';\nimport {\n LLM as InferenceLLM,\n STT as InferenceSTT,\n TTS as InferenceTTS,\n type LLMModels,\n type STTModelString,\n type TTSModelString,\n} from '../inference/index.js';\nimport { ReadonlyChatContext } from '../llm/chat_context.js';\nimport type { ChatMessage, FunctionCall, RealtimeModel } from '../llm/index.js';\nimport {\n type ChatChunk,\n ChatContext,\n LLM,\n type ToolChoice,\n type ToolContext,\n} from '../llm/index.js';\nimport type { STT, SpeechEvent } from '../stt/index.js';\nimport { StreamAdapter as STTStreamAdapter } from '../stt/index.js';\nimport { SentenceTokenizer as BasicSentenceTokenizer } from '../tokenize/basic/index.js';\nimport type { TTS } from '../tts/index.js';\nimport { SynthesizeStream, StreamAdapter as TTSStreamAdapter } from '../tts/index.js';\nimport type { VAD } from '../vad.js';\nimport type { AgentActivity } from './agent_activity.js';\nimport type { AgentSession, TurnDetectionMode } from './agent_session.js';\n\nexport const asyncLocalStorage = new AsyncLocalStorage<{ functionCall?: FunctionCall }>();\nexport const STOP_RESPONSE_SYMBOL = Symbol('StopResponse');\n\nexport class StopResponse extends Error {\n constructor() {\n super();\n this.name = 'StopResponse';\n\n Object.defineProperty(this, STOP_RESPONSE_SYMBOL, {\n value: true,\n });\n }\n}\n\nexport function isStopResponse(value: unknown): value is StopResponse {\n return (\n value !== undefined &&\n value !== null &&\n typeof value === 'object' &&\n STOP_RESPONSE_SYMBOL in value\n );\n}\n\nexport interface ModelSettings {\n /** The tool choice to use when calling the LLM. */\n toolChoice?: ToolChoice;\n}\n\nexport interface AgentOptions<UserData> {\n instructions: string;\n chatCtx?: ChatContext;\n tools?: ToolContext<UserData>;\n turnDetection?: TurnDetectionMode;\n stt?: STT | STTModelString;\n vad?: VAD;\n llm?: LLM | RealtimeModel | LLMModels;\n tts?: TTS | TTSModelString;\n allowInterruptions?: boolean;\n minConsecutiveSpeechDelay?: number;\n}\n\nexport class Agent<UserData = any> {\n private turnDetection?: TurnDetectionMode;\n private _stt?: STT;\n private _vad?: VAD;\n private _llm?: LLM | RealtimeModel;\n private _tts?: TTS;\n\n /** @internal */\n _agentActivity?: AgentActivity;\n\n /** @internal */\n _chatCtx: ChatContext;\n\n /** @internal */\n _instructions: string;\n\n /** @internal */\n _tools?: ToolContext<UserData>;\n\n constructor({\n instructions,\n chatCtx,\n tools,\n turnDetection,\n stt,\n vad,\n llm,\n tts,\n }: AgentOptions<UserData>) {\n this._instructions = instructions;\n this._tools = { ...tools };\n this._chatCtx = chatCtx\n ? chatCtx.copy({\n toolCtx: this._tools,\n })\n : ChatContext.empty();\n\n this.turnDetection = turnDetection;\n this._vad = vad;\n\n if (typeof stt === 'string') {\n this._stt = InferenceSTT.fromModelString(stt);\n } else {\n this._stt = stt;\n }\n\n if (typeof llm === 'string') {\n this._llm = InferenceLLM.fromModelString(llm);\n } else {\n this._llm = llm;\n }\n\n if (typeof tts === 'string') {\n this._tts = InferenceTTS.fromModelString(tts);\n } else {\n this._tts = tts;\n }\n\n this._agentActivity = undefined;\n }\n\n get vad(): VAD | undefined {\n return this._vad;\n }\n\n get stt(): STT | undefined {\n return this._stt;\n }\n\n get llm(): LLM | RealtimeModel | undefined {\n return this._llm;\n }\n\n get tts(): TTS | undefined {\n return this._tts;\n }\n\n get chatCtx(): ReadonlyChatContext {\n return new ReadonlyChatContext(this._chatCtx.items);\n }\n\n get instructions(): string {\n return this._instructions;\n }\n\n get toolCtx(): ToolContext<UserData> {\n return { ...this._tools };\n }\n\n get session(): AgentSession<UserData> {\n return this.getActivityOrThrow().agentSession as AgentSession<UserData>;\n }\n\n async onEnter(): Promise<void> {}\n\n async onExit(): Promise<void> {}\n\n async transcriptionNode(\n text: ReadableStream<string>,\n modelSettings: ModelSettings,\n ): Promise<ReadableStream<string> | null> {\n return Agent.default.transcriptionNode(this, text, modelSettings);\n }\n\n async onUserTurnCompleted(_chatCtx: ChatContext, _newMessage: ChatMessage): Promise<void> {}\n\n async sttNode(\n audio: ReadableStream<AudioFrame>,\n modelSettings: ModelSettings,\n ): Promise<ReadableStream<SpeechEvent | string> | null> {\n return Agent.default.sttNode(this, audio, modelSettings);\n }\n\n async llmNode(\n chatCtx: ChatContext,\n toolCtx: ToolContext,\n modelSettings: ModelSettings,\n ): Promise<ReadableStream<ChatChunk | string> | null> {\n return Agent.default.llmNode(this, chatCtx, toolCtx, modelSettings);\n }\n\n async ttsNode(\n text: ReadableStream<string>,\n modelSettings: ModelSettings,\n ): Promise<ReadableStream<AudioFrame> | null> {\n return Agent.default.ttsNode(this, text, modelSettings);\n }\n\n async realtimeAudioOutputNode(\n audio: ReadableStream<AudioFrame>,\n modelSettings: ModelSettings,\n ): Promise<ReadableStream<AudioFrame> | null> {\n return Agent.default.realtimeAudioOutputNode(this, audio, modelSettings);\n }\n\n // realtime_audio_output_node\n\n getActivityOrThrow(): AgentActivity {\n if (!this._agentActivity) {\n throw new Error('Agent activity not found');\n }\n return this._agentActivity;\n }\n\n async updateChatCtx(chatCtx: ChatContext): Promise<void> {\n if (!this._agentActivity) {\n this._chatCtx = chatCtx.copy({ toolCtx: this.toolCtx });\n return;\n }\n\n this._agentActivity.updateChatCtx(chatCtx);\n }\n\n static default = {\n async sttNode(\n agent: Agent,\n audio: ReadableStream<AudioFrame>,\n _modelSettings: ModelSettings,\n ): Promise<ReadableStream<SpeechEvent | string> | null> {\n const activity = agent.getActivityOrThrow();\n if (!activity.stt) {\n throw new Error('sttNode called but no STT node is available');\n }\n\n let wrapped_stt = activity.stt;\n\n if (!wrapped_stt.capabilities.streaming) {\n if (!agent.vad) {\n throw new Error(\n 'STT does not support streaming, add a VAD to the AgentTask/VoiceAgent to enable streaming',\n );\n }\n wrapped_stt = new STTStreamAdapter(wrapped_stt, agent.vad);\n }\n\n const stream = wrapped_stt.stream();\n stream.updateInputStream(audio);\n\n return new ReadableStream({\n async start(controller) {\n for await (const event of stream) {\n controller.enqueue(event);\n }\n controller.close();\n },\n cancel() {\n stream.detachInputStream();\n stream.close();\n },\n });\n },\n\n async llmNode(\n agent: Agent,\n chatCtx: ChatContext,\n toolCtx: ToolContext,\n modelSettings: ModelSettings,\n ): Promise<ReadableStream<ChatChunk | string> | null> {\n const activity = agent.getActivityOrThrow();\n if (!activity.llm) {\n throw new Error('llmNode called but no LLM node is available');\n }\n\n if (!(activity.llm instanceof LLM)) {\n throw new Error(\n 'llmNode should only be used with LLM (non-multimodal/realtime APIs) nodes',\n );\n }\n\n // TODO(brian): make parallelToolCalls configurable\n const { toolChoice } = modelSettings;\n\n const stream = activity.llm.chat({\n chatCtx,\n toolCtx,\n toolChoice,\n parallelToolCalls: true,\n });\n return new ReadableStream({\n async start(controller) {\n for await (const chunk of stream) {\n controller.enqueue(chunk);\n }\n controller.close();\n },\n cancel() {\n stream.close();\n },\n });\n },\n\n async ttsNode(\n agent: Agent,\n text: ReadableStream<string>,\n _modelSettings: ModelSettings,\n ): Promise<ReadableStream<AudioFrame> | null> {\n const activity = agent.getActivityOrThrow();\n if (!activity.tts) {\n throw new Error('ttsNode called but no TTS node is available');\n }\n\n let wrapped_tts = activity.tts;\n\n if (!activity.tts.capabilities.streaming) {\n wrapped_tts = new TTSStreamAdapter(wrapped_tts, new BasicSentenceTokenizer());\n }\n\n const stream = wrapped_tts.stream();\n stream.updateInputStream(text);\n\n return new ReadableStream({\n async start(controller) {\n for await (const chunk of stream) {\n if (chunk === SynthesizeStream.END_OF_STREAM) {\n break;\n }\n controller.enqueue(chunk.frame);\n }\n controller.close();\n },\n cancel() {\n stream.close();\n },\n });\n },\n\n async transcriptionNode(\n agent: Agent,\n text: ReadableStream<string>,\n _modelSettings: ModelSettings,\n ): Promise<ReadableStream<string> | null> {\n return text;\n },\n\n async realtimeAudioOutputNode(\n _agent: Agent,\n audio: ReadableStream<AudioFrame>,\n _modelSettings: ModelSettings,\n ): Promise<ReadableStream<AudioFrame> | null> {\n return audio;\n },\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/voice/agent.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { AudioFrame } from '@livekit/rtc-node';\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport { ReadableStream } from 'node:stream/web';\nimport {\n LLM as InferenceLLM,\n STT as InferenceSTT,\n TTS as InferenceTTS,\n type LLMModels,\n type STTModelString,\n type TTSModelString,\n} from '../inference/index.js';\nimport { ReadonlyChatContext } from '../llm/chat_context.js';\nimport type { ChatMessage, FunctionCall, RealtimeModel } from '../llm/index.js';\nimport {\n type ChatChunk,\n ChatContext,\n LLM,\n type ToolChoice,\n type ToolContext,\n} from '../llm/index.js';\nimport type { STT, SpeechEvent } from '../stt/index.js';\nimport { StreamAdapter as STTStreamAdapter } from '../stt/index.js';\nimport { SentenceTokenizer as BasicSentenceTokenizer } from '../tokenize/basic/index.js';\nimport type { TTS } from '../tts/index.js';\nimport { SynthesizeStream, StreamAdapter as TTSStreamAdapter } from '../tts/index.js';\nimport type { VAD } from '../vad.js';\nimport type { AgentActivity } from './agent_activity.js';\nimport type { AgentSession, TurnDetectionMode } from './agent_session.js';\n\nexport const asyncLocalStorage = new AsyncLocalStorage<{ functionCall?: FunctionCall }>();\nexport const STOP_RESPONSE_SYMBOL = Symbol('StopResponse');\n\nexport class StopResponse extends Error {\n constructor() {\n super();\n this.name = 'StopResponse';\n\n Object.defineProperty(this, STOP_RESPONSE_SYMBOL, {\n value: true,\n });\n }\n}\n\nexport function isStopResponse(value: unknown): value is StopResponse {\n return (\n value !== undefined &&\n value !== null &&\n typeof value === 'object' &&\n STOP_RESPONSE_SYMBOL in value\n );\n}\n\nexport interface ModelSettings {\n /** The tool choice to use when calling the LLM. */\n toolChoice?: ToolChoice;\n}\n\nexport interface AgentOptions<UserData> {\n id?: string;\n instructions: string;\n chatCtx?: ChatContext;\n tools?: ToolContext<UserData>;\n turnDetection?: TurnDetectionMode;\n stt?: STT | STTModelString;\n vad?: VAD;\n llm?: LLM | RealtimeModel | LLMModels;\n tts?: TTS | TTSModelString;\n allowInterruptions?: boolean;\n minConsecutiveSpeechDelay?: number;\n}\n\nexport class Agent<UserData = any> {\n private _id: string;\n private turnDetection?: TurnDetectionMode;\n private _stt?: STT;\n private _vad?: VAD;\n private _llm?: LLM | RealtimeModel;\n private _tts?: TTS;\n\n /** @internal */\n _agentActivity?: AgentActivity;\n\n /** @internal */\n _chatCtx: ChatContext;\n\n /** @internal */\n _instructions: string;\n\n /** @internal */\n _tools?: ToolContext<UserData>;\n\n constructor({\n id,\n instructions,\n chatCtx,\n tools,\n turnDetection,\n stt,\n vad,\n llm,\n tts,\n }: AgentOptions<UserData>) {\n if (id) {\n this._id = id;\n } else {\n // Convert class name to snake_case\n const className = this.constructor.name;\n if (className === 'Agent') {\n this._id = 'default_agent';\n } else {\n this._id = className\n .replace(/([A-Z])/g, '_$1')\n .toLowerCase()\n .replace(/^_/, '');\n }\n }\n\n this._instructions = instructions;\n this._tools = { ...tools };\n this._chatCtx = chatCtx\n ? chatCtx.copy({\n toolCtx: this._tools,\n })\n : ChatContext.empty();\n\n this.turnDetection = turnDetection;\n this._vad = vad;\n\n if (typeof stt === 'string') {\n this._stt = InferenceSTT.fromModelString(stt);\n } else {\n this._stt = stt;\n }\n\n if (typeof llm === 'string') {\n this._llm = InferenceLLM.fromModelString(llm);\n } else {\n this._llm = llm;\n }\n\n if (typeof tts === 'string') {\n this._tts = InferenceTTS.fromModelString(tts);\n } else {\n this._tts = tts;\n }\n\n this._agentActivity = undefined;\n }\n\n get vad(): VAD | undefined {\n return this._vad;\n }\n\n get stt(): STT | undefined {\n return this._stt;\n }\n\n get llm(): LLM | RealtimeModel | undefined {\n return this._llm;\n }\n\n get tts(): TTS | undefined {\n return this._tts;\n }\n\n get chatCtx(): ReadonlyChatContext {\n return new ReadonlyChatContext(this._chatCtx.items);\n }\n\n get id(): string {\n return this._id;\n }\n\n get instructions(): string {\n return this._instructions;\n }\n\n get toolCtx(): ToolContext<UserData> {\n return { ...this._tools };\n }\n\n get session(): AgentSession<UserData> {\n return this.getActivityOrThrow().agentSession as AgentSession<UserData>;\n }\n\n async onEnter(): Promise<void> {}\n\n async onExit(): Promise<void> {}\n\n async transcriptionNode(\n text: ReadableStream<string>,\n modelSettings: ModelSettings,\n ): Promise<ReadableStream<string> | null> {\n return Agent.default.transcriptionNode(this, text, modelSettings);\n }\n\n async onUserTurnCompleted(_chatCtx: ChatContext, _newMessage: ChatMessage): Promise<void> {}\n\n async sttNode(\n audio: ReadableStream<AudioFrame>,\n modelSettings: ModelSettings,\n ): Promise<ReadableStream<SpeechEvent | string> | null> {\n return Agent.default.sttNode(this, audio, modelSettings);\n }\n\n async llmNode(\n chatCtx: ChatContext,\n toolCtx: ToolContext,\n modelSettings: ModelSettings,\n ): Promise<ReadableStream<ChatChunk | string> | null> {\n return Agent.default.llmNode(this, chatCtx, toolCtx, modelSettings);\n }\n\n async ttsNode(\n text: ReadableStream<string>,\n modelSettings: ModelSettings,\n ): Promise<ReadableStream<AudioFrame> | null> {\n return Agent.default.ttsNode(this, text, modelSettings);\n }\n\n async realtimeAudioOutputNode(\n audio: ReadableStream<AudioFrame>,\n modelSettings: ModelSettings,\n ): Promise<ReadableStream<AudioFrame> | null> {\n return Agent.default.realtimeAudioOutputNode(this, audio, modelSettings);\n }\n\n // realtime_audio_output_node\n\n getActivityOrThrow(): AgentActivity {\n if (!this._agentActivity) {\n throw new Error('Agent activity not found');\n }\n return this._agentActivity;\n }\n\n async updateChatCtx(chatCtx: ChatContext): Promise<void> {\n if (!this._agentActivity) {\n this._chatCtx = chatCtx.copy({ toolCtx: this.toolCtx });\n return;\n }\n\n this._agentActivity.updateChatCtx(chatCtx);\n }\n\n static default = {\n async sttNode(\n agent: Agent,\n audio: ReadableStream<AudioFrame>,\n _modelSettings: ModelSettings,\n ): Promise<ReadableStream<SpeechEvent | string> | null> {\n const activity = agent.getActivityOrThrow();\n if (!activity.stt) {\n throw new Error('sttNode called but no STT node is available');\n }\n\n let wrapped_stt = activity.stt;\n\n if (!wrapped_stt.capabilities.streaming) {\n if (!agent.vad) {\n throw new Error(\n 'STT does not support streaming, add a VAD to the AgentTask/VoiceAgent to enable streaming',\n );\n }\n wrapped_stt = new STTStreamAdapter(wrapped_stt, agent.vad);\n }\n\n const stream = wrapped_stt.stream();\n stream.updateInputStream(audio);\n\n return new ReadableStream({\n async start(controller) {\n for await (const event of stream) {\n controller.enqueue(event);\n }\n controller.close();\n },\n cancel() {\n stream.detachInputStream();\n stream.close();\n },\n });\n },\n\n async llmNode(\n agent: Agent,\n chatCtx: ChatContext,\n toolCtx: ToolContext,\n modelSettings: ModelSettings,\n ): Promise<ReadableStream<ChatChunk | string> | null> {\n const activity = agent.getActivityOrThrow();\n if (!activity.llm) {\n throw new Error('llmNode called but no LLM node is available');\n }\n\n if (!(activity.llm instanceof LLM)) {\n throw new Error(\n 'llmNode should only be used with LLM (non-multimodal/realtime APIs) nodes',\n );\n }\n\n // TODO(brian): make parallelToolCalls configurable\n const { toolChoice } = modelSettings;\n\n const stream = activity.llm.chat({\n chatCtx,\n toolCtx,\n toolChoice,\n parallelToolCalls: true,\n });\n return new ReadableStream({\n async start(controller) {\n for await (const chunk of stream) {\n controller.enqueue(chunk);\n }\n controller.close();\n },\n cancel() {\n stream.close();\n },\n });\n },\n\n async ttsNode(\n agent: Agent,\n text: ReadableStream<string>,\n _modelSettings: ModelSettings,\n ): Promise<ReadableStream<AudioFrame> | null> {\n const activity = agent.getActivityOrThrow();\n if (!activity.tts) {\n throw new Error('ttsNode called but no TTS node is available');\n }\n\n let wrapped_tts = activity.tts;\n\n if (!activity.tts.capabilities.streaming) {\n wrapped_tts = new TTSStreamAdapter(wrapped_tts, new BasicSentenceTokenizer());\n }\n\n const stream = wrapped_tts.stream();\n stream.updateInputStream(text);\n\n return new ReadableStream({\n async start(controller) {\n for await (const chunk of stream) {\n if (chunk === SynthesizeStream.END_OF_STREAM) {\n break;\n }\n controller.enqueue(chunk.frame);\n }\n controller.close();\n },\n cancel() {\n stream.close();\n },\n });\n },\n\n async transcriptionNode(\n agent: Agent,\n text: ReadableStream<string>,\n _modelSettings: ModelSettings,\n ): Promise<ReadableStream<string> | null> {\n return text;\n },\n\n async realtimeAudioOutputNode(\n _agent: Agent,\n audio: ReadableStream<AudioFrame>,\n _modelSettings: ModelSettings,\n ): Promise<ReadableStream<AudioFrame> | null> {\n return audio;\n },\n };\n}\n"],"mappings":"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","names":[]}
@@ -155,6 +155,11 @@ class AgentActivity {
155
155
  } catch (error) {
156
156
  this.logger.error(error, "failed to update the tools");
157
157
  }
158
+ if (!this.llm.capabilities.audioOutput && !this.tts && this.agentSession.output.audio) {
159
+ this.logger.error(
160
+ "audio output is enabled but RealtimeModel has no audio modality and no TTS is set. Either enable audio modality in the RealtimeModel or set a TTS model."
161
+ );
162
+ }
158
163
  } else if (this.llm instanceof import_llm.LLM) {
159
164
  try {
160
165
  (0, import_generation.updateInstructions)({
@@ -452,7 +457,9 @@ class AgentActivity {
452
457
  }
453
458
  if (this.stt && this.agentSession.options.minInterruptionWords > 0 && this.audioRecognition) {
454
459
  const text = this.audioRecognition.currentTranscript;
455
- if (text && (0, import_word.splitWords)(text, true).length < this.agentSession.options.minInterruptionWords) {
460
+ const normalizedText = text ?? "";
461
+ const wordCount = (0, import_word.splitWords)(normalizedText, true).length;
462
+ if (wordCount < this.agentSession.options.minInterruptionWords) {
456
463
  return;
457
464
  }
458
465
  }
@@ -554,10 +561,19 @@ class AgentActivity {
554
561
  this.logger.warn({ user_input: info.newTranscript }, "skipping user input, task is draining");
555
562
  return true;
556
563
  }
557
- if (this.stt && this.turnDetection !== "manual" && this._currentSpeech && this._currentSpeech.allowInterruptions && !this._currentSpeech.interrupted && this.agentSession.options.minInterruptionWords > 0 && info.newTranscript.split(" ").length < this.agentSession.options.minInterruptionWords) {
558
- this.cancelPreemptiveGeneration();
559
- this.logger.info("skipping user input, new_transcript is too short");
560
- return false;
564
+ if (this.stt && this.turnDetection !== "manual" && this._currentSpeech && this._currentSpeech.allowInterruptions && !this._currentSpeech.interrupted && this.agentSession.options.minInterruptionWords > 0) {
565
+ const wordCount = (0, import_word.splitWords)(info.newTranscript, true).length;
566
+ if (wordCount < this.agentSession.options.minInterruptionWords) {
567
+ this.cancelPreemptiveGeneration();
568
+ this.logger.info(
569
+ {
570
+ wordCount,
571
+ minInterruptionWords: this.agentSession.options.minInterruptionWords
572
+ },
573
+ "skipping user input, word count below minimum interruption threshold"
574
+ );
575
+ return false;
576
+ }
561
577
  }
562
578
  const oldTask = this._userTurnCompletedTask;
563
579
  this._userTurnCompletedTask = this.createSpeechTask({
@@ -887,6 +903,7 @@ ${instructions}` : instructions,
887
903
  this.agentSession._updateAgentState("listening");
888
904
  }
889
905
  }
906
+ // TODO(brian): PR3 - Wrap entire pipelineReplyTask() method with tracer.startActiveSpan('agent_turn')
890
907
  async pipelineReplyTask(speechHandle, chatCtx, toolCtx, modelSettings, replyAbortController, instructions, newMessage, toolsMessages) {
891
908
  var _a, _b, _c;
892
909
  speechHandleStorage.enterWith(speechHandle);
@@ -1200,7 +1217,22 @@ ${instructions}` : instructions,
1200
1217
  );
1201
1218
  break;
1202
1219
  }
1203
- const trNodeResult = await this.agent.transcriptionNode(msg.textStream, modelSettings);
1220
+ const msgModalities = msg.modalities ? await msg.modalities : void 0;
1221
+ let ttsTextInput = null;
1222
+ let trTextInput;
1223
+ if (msgModalities && !msgModalities.includes("audio") && this.tts) {
1224
+ if (this.llm instanceof import_llm.RealtimeModel && this.llm.capabilities.audioOutput) {
1225
+ this.logger.warn(
1226
+ "text response received from realtime API, falling back to use a TTS model."
1227
+ );
1228
+ }
1229
+ const [_ttsTextInput, _trTextInput] = msg.textStream.tee();
1230
+ ttsTextInput = _ttsTextInput;
1231
+ trTextInput = _trTextInput;
1232
+ } else {
1233
+ trTextInput = msg.textStream;
1234
+ }
1235
+ const trNodeResult = await this.agent.transcriptionNode(trTextInput, modelSettings);
1204
1236
  let textOut = null;
1205
1237
  if (trNodeResult) {
1206
1238
  const [textForwardTask, _textOut] = (0, import_generation.performTextForwarding)(
@@ -1213,28 +1245,44 @@ ${instructions}` : instructions,
1213
1245
  }
1214
1246
  let audioOut = null;
1215
1247
  if (audioOutput) {
1216
- const realtimeAudio = await this.agent.realtimeAudioOutputNode(
1217
- msg.audioStream,
1218
- modelSettings
1219
- );
1220
- if (realtimeAudio) {
1248
+ let realtimeAudioResult = null;
1249
+ if (ttsTextInput) {
1250
+ const [ttsTask, ttsStream] = (0, import_generation.performTTSInference)(
1251
+ (...args) => this.agent.ttsNode(...args),
1252
+ ttsTextInput,
1253
+ modelSettings,
1254
+ abortController
1255
+ );
1256
+ tasks.push(ttsTask);
1257
+ realtimeAudioResult = ttsStream;
1258
+ } else if (msgModalities && msgModalities.includes("audio")) {
1259
+ realtimeAudioResult = await this.agent.realtimeAudioOutputNode(
1260
+ msg.audioStream,
1261
+ modelSettings
1262
+ );
1263
+ } else if (this.llm instanceof import_llm.RealtimeModel && this.llm.capabilities.audioOutput) {
1264
+ this.logger.error(
1265
+ "Text message received from Realtime API with audio modality. This usually happens when text chat context is synced to the API. Try to add a TTS model as fallback or use text modality with TTS instead."
1266
+ );
1267
+ } else {
1268
+ this.logger.warn(
1269
+ "audio output is enabled but neither tts nor realtime audio is available"
1270
+ );
1271
+ }
1272
+ if (realtimeAudioResult) {
1221
1273
  const [forwardTask, _audioOut] = (0, import_generation.performAudioForwarding)(
1222
- realtimeAudio,
1274
+ realtimeAudioResult,
1223
1275
  audioOutput,
1224
1276
  abortController
1225
1277
  );
1226
1278
  forwardTasks.push(forwardTask);
1227
1279
  audioOut = _audioOut;
1228
1280
  audioOut.firstFrameFut.await.finally(onFirstFrame);
1229
- } else {
1230
- this.logger.warn(
1231
- "audio output is enabled but neither tts nor realtime audio is available"
1232
- );
1233
1281
  }
1234
1282
  } else if (textOut) {
1235
1283
  textOut.firstTextFut.await.finally(onFirstFrame);
1236
1284
  }
1237
- outputs.push([msg.messageId, textOut, audioOut]);
1285
+ outputs.push([msg.messageId, textOut, audioOut, msgModalities]);
1238
1286
  }
1239
1287
  await (0, import_utils.waitFor)(forwardTasks);
1240
1288
  } catch (error) {
@@ -1304,7 +1352,7 @@ ${instructions}` : instructions,
1304
1352
  replyAbortController.abort();
1305
1353
  await (0, import_utils.cancelAndWait)(tasks, AgentActivity.REPLY_TASK_CANCEL_TIMEOUT);
1306
1354
  if (messageOutputs.length > 0) {
1307
- const [msgId, textOut, audioOut] = messageOutputs[0];
1355
+ const [msgId, textOut, audioOut, msgModalities] = messageOutputs[0];
1308
1356
  let forwardedText = (textOut == null ? void 0 : textOut.text) || "";
1309
1357
  if (audioOutput) {
1310
1358
  audioOutput.clearBuffer();
@@ -1324,7 +1372,9 @@ ${instructions}` : instructions,
1324
1372
  }
1325
1373
  this.realtimeSession.truncate({
1326
1374
  messageId: msgId,
1327
- audioEndMs: Math.floor(playbackPosition)
1375
+ audioEndMs: Math.floor(playbackPosition),
1376
+ modalities: msgModalities,
1377
+ audioTranscript: forwardedText
1328
1378
  });
1329
1379
  }
1330
1380
  if (forwardedText) {
@@ -1348,7 +1398,7 @@ ${instructions}` : instructions,
1348
1398
  return;
1349
1399
  }
1350
1400
  if (messageOutputs.length > 0) {
1351
- const [msgId, textOut, _] = messageOutputs[0];
1401
+ const [msgId, textOut, _, __] = messageOutputs[0];
1352
1402
  const message = import_chat_context.ChatMessage.create({
1353
1403
  role: "assistant",
1354
1404
  content: (textOut == null ? void 0 : textOut.text) || "",
@@ -1510,6 +1560,7 @@ ${instructions}` : instructions,
1510
1560
  speechHandle._markScheduled();
1511
1561
  this.wakeupMainTask();
1512
1562
  }
1563
+ // TODO(brian): PR3 - Wrap entire drain() method with tracer.startActiveSpan('drain_agent_activity', { attributes: { 'lk.agent_label': this.agent.label } })
1513
1564
  async drain() {
1514
1565
  var _a;
1515
1566
  const unlock = await this.lock.lock();