@livekit/agents 1.0.18 → 1.0.20

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 (174) 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/tts.cjs +1 -1
  11. package/dist/inference/tts.cjs.map +1 -1
  12. package/dist/inference/tts.js +1 -1
  13. package/dist/inference/tts.js.map +1 -1
  14. package/dist/ipc/job_proc_lazy_main.cjs +6 -2
  15. package/dist/ipc/job_proc_lazy_main.cjs.map +1 -1
  16. package/dist/ipc/job_proc_lazy_main.js +6 -2
  17. package/dist/ipc/job_proc_lazy_main.js.map +1 -1
  18. package/dist/job.cjs +31 -0
  19. package/dist/job.cjs.map +1 -1
  20. package/dist/job.d.cts +6 -0
  21. package/dist/job.d.ts +6 -0
  22. package/dist/job.d.ts.map +1 -1
  23. package/dist/job.js +31 -0
  24. package/dist/job.js.map +1 -1
  25. package/dist/llm/chat_context.cjs +33 -0
  26. package/dist/llm/chat_context.cjs.map +1 -1
  27. package/dist/llm/chat_context.d.cts +22 -2
  28. package/dist/llm/chat_context.d.ts +22 -2
  29. package/dist/llm/chat_context.d.ts.map +1 -1
  30. package/dist/llm/chat_context.js +32 -0
  31. package/dist/llm/chat_context.js.map +1 -1
  32. package/dist/llm/index.cjs +2 -0
  33. package/dist/llm/index.cjs.map +1 -1
  34. package/dist/llm/index.d.cts +1 -1
  35. package/dist/llm/index.d.ts +1 -1
  36. package/dist/llm/index.d.ts.map +1 -1
  37. package/dist/llm/index.js +2 -0
  38. package/dist/llm/index.js.map +1 -1
  39. package/dist/llm/llm.cjs.map +1 -1
  40. package/dist/llm/llm.d.ts.map +1 -1
  41. package/dist/llm/llm.js.map +1 -1
  42. package/dist/llm/provider_format/google.test.cjs +48 -0
  43. package/dist/llm/provider_format/google.test.cjs.map +1 -1
  44. package/dist/llm/provider_format/google.test.js +54 -1
  45. package/dist/llm/provider_format/google.test.js.map +1 -1
  46. package/dist/llm/provider_format/index.d.cts +1 -1
  47. package/dist/llm/provider_format/index.d.ts +1 -1
  48. package/dist/llm/provider_format/openai.cjs +1 -2
  49. package/dist/llm/provider_format/openai.cjs.map +1 -1
  50. package/dist/llm/provider_format/openai.js +1 -2
  51. package/dist/llm/provider_format/openai.js.map +1 -1
  52. package/dist/llm/provider_format/openai.test.cjs +32 -0
  53. package/dist/llm/provider_format/openai.test.cjs.map +1 -1
  54. package/dist/llm/provider_format/openai.test.js +38 -1
  55. package/dist/llm/provider_format/openai.test.js.map +1 -1
  56. package/dist/log.cjs.map +1 -1
  57. package/dist/log.d.ts.map +1 -1
  58. package/dist/log.js.map +1 -1
  59. package/dist/stt/stt.cjs +3 -0
  60. package/dist/stt/stt.cjs.map +1 -1
  61. package/dist/stt/stt.d.cts +1 -0
  62. package/dist/stt/stt.d.ts +1 -0
  63. package/dist/stt/stt.d.ts.map +1 -1
  64. package/dist/stt/stt.js +3 -0
  65. package/dist/stt/stt.js.map +1 -1
  66. package/dist/telemetry/index.cjs +51 -0
  67. package/dist/telemetry/index.cjs.map +1 -0
  68. package/dist/telemetry/index.d.cts +4 -0
  69. package/dist/telemetry/index.d.ts +4 -0
  70. package/dist/telemetry/index.d.ts.map +1 -0
  71. package/dist/telemetry/index.js +12 -0
  72. package/dist/telemetry/index.js.map +1 -0
  73. package/dist/telemetry/trace_types.cjs +191 -0
  74. package/dist/telemetry/trace_types.cjs.map +1 -0
  75. package/dist/telemetry/trace_types.d.cts +56 -0
  76. package/dist/telemetry/trace_types.d.ts +56 -0
  77. package/dist/telemetry/trace_types.d.ts.map +1 -0
  78. package/dist/telemetry/trace_types.js +113 -0
  79. package/dist/telemetry/trace_types.js.map +1 -0
  80. package/dist/telemetry/traces.cjs +196 -0
  81. package/dist/telemetry/traces.cjs.map +1 -0
  82. package/dist/telemetry/traces.d.cts +97 -0
  83. package/dist/telemetry/traces.d.ts +97 -0
  84. package/dist/telemetry/traces.d.ts.map +1 -0
  85. package/dist/telemetry/traces.js +173 -0
  86. package/dist/telemetry/traces.js.map +1 -0
  87. package/dist/telemetry/utils.cjs +86 -0
  88. package/dist/telemetry/utils.cjs.map +1 -0
  89. package/dist/telemetry/utils.d.cts +5 -0
  90. package/dist/telemetry/utils.d.ts +5 -0
  91. package/dist/telemetry/utils.d.ts.map +1 -0
  92. package/dist/telemetry/utils.js +51 -0
  93. package/dist/telemetry/utils.js.map +1 -0
  94. package/dist/tts/tts.cjs +3 -0
  95. package/dist/tts/tts.cjs.map +1 -1
  96. package/dist/tts/tts.d.cts +1 -0
  97. package/dist/tts/tts.d.ts +1 -0
  98. package/dist/tts/tts.d.ts.map +1 -1
  99. package/dist/tts/tts.js +3 -0
  100. package/dist/tts/tts.js.map +1 -1
  101. package/dist/vad.cjs +3 -0
  102. package/dist/vad.cjs.map +1 -1
  103. package/dist/vad.d.cts +1 -0
  104. package/dist/vad.d.ts +1 -0
  105. package/dist/vad.d.ts.map +1 -1
  106. package/dist/vad.js +3 -0
  107. package/dist/vad.js.map +1 -1
  108. package/dist/voice/agent.cjs +15 -0
  109. package/dist/voice/agent.cjs.map +1 -1
  110. package/dist/voice/agent.d.cts +4 -1
  111. package/dist/voice/agent.d.ts +4 -1
  112. package/dist/voice/agent.d.ts.map +1 -1
  113. package/dist/voice/agent.js +15 -0
  114. package/dist/voice/agent.js.map +1 -1
  115. package/dist/voice/agent_activity.cjs +5 -0
  116. package/dist/voice/agent_activity.cjs.map +1 -1
  117. package/dist/voice/agent_activity.d.ts.map +1 -1
  118. package/dist/voice/agent_activity.js +5 -0
  119. package/dist/voice/agent_activity.js.map +1 -1
  120. package/dist/voice/agent_session.cjs +29 -1
  121. package/dist/voice/agent_session.cjs.map +1 -1
  122. package/dist/voice/agent_session.d.cts +6 -2
  123. package/dist/voice/agent_session.d.ts +6 -2
  124. package/dist/voice/agent_session.d.ts.map +1 -1
  125. package/dist/voice/agent_session.js +30 -2
  126. package/dist/voice/agent_session.js.map +1 -1
  127. package/dist/voice/audio_recognition.cjs +1 -1
  128. package/dist/voice/audio_recognition.cjs.map +1 -1
  129. package/dist/voice/audio_recognition.d.ts.map +1 -1
  130. package/dist/voice/audio_recognition.js +1 -1
  131. package/dist/voice/audio_recognition.js.map +1 -1
  132. package/dist/voice/generation.cjs.map +1 -1
  133. package/dist/voice/generation.d.ts.map +1 -1
  134. package/dist/voice/generation.js.map +1 -1
  135. package/dist/voice/index.cjs +2 -0
  136. package/dist/voice/index.cjs.map +1 -1
  137. package/dist/voice/index.d.cts +1 -0
  138. package/dist/voice/index.d.ts +1 -0
  139. package/dist/voice/index.d.ts.map +1 -1
  140. package/dist/voice/index.js +1 -0
  141. package/dist/voice/index.js.map +1 -1
  142. package/dist/voice/report.cjs +69 -0
  143. package/dist/voice/report.cjs.map +1 -0
  144. package/dist/voice/report.d.cts +26 -0
  145. package/dist/voice/report.d.ts +26 -0
  146. package/dist/voice/report.d.ts.map +1 -0
  147. package/dist/voice/report.js +44 -0
  148. package/dist/voice/report.js.map +1 -0
  149. package/package.json +10 -3
  150. package/src/index.ts +2 -1
  151. package/src/inference/tts.ts +1 -1
  152. package/src/ipc/job_proc_lazy_main.ts +10 -2
  153. package/src/job.ts +48 -0
  154. package/src/llm/chat_context.ts +53 -1
  155. package/src/llm/index.ts +1 -0
  156. package/src/llm/llm.ts +2 -0
  157. package/src/llm/provider_format/google.test.ts +72 -1
  158. package/src/llm/provider_format/openai.test.ts +55 -1
  159. package/src/llm/provider_format/openai.ts +3 -2
  160. package/src/log.ts +1 -0
  161. package/src/stt/stt.ts +4 -0
  162. package/src/telemetry/index.ts +10 -0
  163. package/src/telemetry/trace_types.ts +88 -0
  164. package/src/telemetry/traces.ts +266 -0
  165. package/src/telemetry/utils.ts +61 -0
  166. package/src/tts/tts.ts +8 -0
  167. package/src/vad.ts +4 -0
  168. package/src/voice/agent.ts +22 -0
  169. package/src/voice/agent_activity.ts +9 -0
  170. package/src/voice/agent_session.ts +44 -1
  171. package/src/voice/audio_recognition.ts +3 -1
  172. package/src/voice/generation.ts +3 -0
  173. package/src/voice/index.ts +1 -0
  174. package/src/voice/report.ts +77 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/voice/index.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nexport { Agent, StopResponse, type AgentOptions, type ModelSettings } from './agent.js';\nexport { AgentSession, type AgentSessionOptions } from './agent_session.js';\nexport * from './avatar/index.js';\nexport * from './background_audio.js';\nexport * from './events.js';\nexport * from './room_io/index.js';\nexport { RunContext } from './run_context.js';\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,mBAA2E;AAC3E,2BAAuD;AACvD,0BAAc,8BALd;AAMA,0BAAc,kCANd;AAOA,0BAAc,wBAPd;AAQA,0BAAc,+BARd;AASA,yBAA2B;","names":[]}
1
+ {"version":3,"sources":["../../src/voice/index.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nexport { Agent, StopResponse, type AgentOptions, type ModelSettings } from './agent.js';\nexport { AgentSession, type AgentSessionOptions } from './agent_session.js';\nexport * from './avatar/index.js';\nexport * from './background_audio.js';\nexport * from './events.js';\nexport * from './report.js';\nexport * from './room_io/index.js';\nexport { RunContext } from './run_context.js';\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,mBAA2E;AAC3E,2BAAuD;AACvD,0BAAc,8BALd;AAMA,0BAAc,kCANd;AAOA,0BAAc,wBAPd;AAQA,0BAAc,wBARd;AASA,0BAAc,+BATd;AAUA,yBAA2B;","names":[]}
@@ -3,6 +3,7 @@ export { AgentSession, type AgentSessionOptions } from './agent_session.js';
3
3
  export * from './avatar/index.js';
4
4
  export * from './background_audio.js';
5
5
  export * from './events.js';
6
+ export * from './report.js';
6
7
  export * from './room_io/index.js';
7
8
  export { RunContext } from './run_context.js';
8
9
  //# sourceMappingURL=index.d.ts.map
@@ -3,6 +3,7 @@ export { AgentSession, type AgentSessionOptions } from './agent_session.js';
3
3
  export * from './avatar/index.js';
4
4
  export * from './background_audio.js';
5
5
  export * from './events.js';
6
+ export * from './report.js';
6
7
  export * from './room_io/index.js';
7
8
  export { RunContext } from './run_context.js';
8
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/voice/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,YAAY,EAAE,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AACxF,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC5E,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,aAAa,CAAC;AAC5B,cAAc,oBAAoB,CAAC;AACnC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/voice/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,YAAY,EAAE,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AACxF,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC5E,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,oBAAoB,CAAC;AACnC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC"}
@@ -3,6 +3,7 @@ import { AgentSession } from "./agent_session.js";
3
3
  export * from "./avatar/index.js";
4
4
  export * from "./background_audio.js";
5
5
  export * from "./events.js";
6
+ export * from "./report.js";
6
7
  export * from "./room_io/index.js";
7
8
  import { RunContext } from "./run_context.js";
8
9
  export {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/voice/index.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nexport { Agent, StopResponse, type AgentOptions, type ModelSettings } from './agent.js';\nexport { AgentSession, type AgentSessionOptions } from './agent_session.js';\nexport * from './avatar/index.js';\nexport * from './background_audio.js';\nexport * from './events.js';\nexport * from './room_io/index.js';\nexport { RunContext } from './run_context.js';\n"],"mappings":"AAGA,SAAS,OAAO,oBAA2D;AAC3E,SAAS,oBAA8C;AACvD,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,SAAS,kBAAkB;","names":[]}
1
+ {"version":3,"sources":["../../src/voice/index.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nexport { Agent, StopResponse, type AgentOptions, type ModelSettings } from './agent.js';\nexport { AgentSession, type AgentSessionOptions } from './agent_session.js';\nexport * from './avatar/index.js';\nexport * from './background_audio.js';\nexport * from './events.js';\nexport * from './report.js';\nexport * from './room_io/index.js';\nexport { RunContext } from './run_context.js';\n"],"mappings":"AAGA,SAAS,OAAO,oBAA2D;AAC3E,SAAS,oBAA8C;AACvD,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,SAAS,kBAAkB;","names":[]}
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var report_exports = {};
20
+ __export(report_exports, {
21
+ createSessionReport: () => createSessionReport,
22
+ sessionReportToJSON: () => sessionReportToJSON
23
+ });
24
+ module.exports = __toCommonJS(report_exports);
25
+ function createSessionReport(opts) {
26
+ return {
27
+ jobId: opts.jobId,
28
+ roomId: opts.roomId,
29
+ room: opts.room,
30
+ options: opts.options,
31
+ events: opts.events,
32
+ chatHistory: opts.chatHistory,
33
+ enableUserDataTraining: opts.enableUserDataTraining ?? false,
34
+ timestamp: opts.timestamp ?? Date.now()
35
+ };
36
+ }
37
+ function sessionReportToJSON(report) {
38
+ const events = [];
39
+ for (const event of report.events) {
40
+ if (event.type === "metrics_collected") {
41
+ continue;
42
+ }
43
+ events.push({ ...event });
44
+ }
45
+ return {
46
+ job_id: report.jobId,
47
+ room_id: report.roomId,
48
+ room: report.room,
49
+ events,
50
+ options: {
51
+ allow_interruptions: report.options.allowInterruptions,
52
+ discard_audio_if_uninterruptible: report.options.discardAudioIfUninterruptible,
53
+ min_interruption_duration: report.options.minInterruptionDuration,
54
+ min_interruption_words: report.options.minInterruptionWords,
55
+ min_endpointing_delay: report.options.minEndpointingDelay,
56
+ max_endpointing_delay: report.options.maxEndpointingDelay,
57
+ max_tool_steps: report.options.maxToolSteps
58
+ },
59
+ chat_history: report.chatHistory.toJSON({ excludeTimestamp: false }),
60
+ enable_user_data_training: report.enableUserDataTraining,
61
+ timestamp: report.timestamp
62
+ };
63
+ }
64
+ // Annotate the CommonJS export names for ESM import in node:
65
+ 0 && (module.exports = {
66
+ createSessionReport,
67
+ sessionReportToJSON
68
+ });
69
+ //# sourceMappingURL=report.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/voice/report.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { ChatContext } from '../llm/chat_context.js';\nimport type { VoiceOptions } from './agent_session.js';\nimport type { AgentEvent } from './events.js';\n\nexport interface SessionReport {\n jobId: string;\n roomId: string;\n room: string;\n options: VoiceOptions;\n events: AgentEvent[];\n chatHistory: ChatContext;\n enableUserDataTraining: boolean;\n timestamp: number;\n}\n\nexport interface SessionReportOptions {\n jobId: string;\n roomId: string;\n room: string;\n options: VoiceOptions;\n events: AgentEvent[];\n chatHistory: ChatContext;\n enableUserDataTraining?: boolean;\n timestamp?: number;\n}\n\nexport function createSessionReport(opts: SessionReportOptions): SessionReport {\n return {\n jobId: opts.jobId,\n roomId: opts.roomId,\n room: opts.room,\n options: opts.options,\n events: opts.events,\n chatHistory: opts.chatHistory,\n enableUserDataTraining: opts.enableUserDataTraining ?? false,\n timestamp: opts.timestamp ?? Date.now(),\n };\n}\n\n// TODO(brian): PR5 - Add uploadSessionReport() function that creates multipart form with:\n// - header: protobuf MetricsRecordingHeader (room_id, duration, start_time)\n// - chat_history: JSON serialized chat history (use sessionReportToJSON)\n// - audio: audio recording file if available (ogg format)\n// - Uploads to LiveKit Cloud observability endpoint with JWT auth\nexport function sessionReportToJSON(report: SessionReport): Record<string, unknown> {\n const events: Record<string, unknown>[] = [];\n\n for (const event of report.events) {\n if (event.type === 'metrics_collected') {\n continue; // metrics are too noisy, Cloud is using the chat_history as the source of truth\n }\n\n events.push({ ...event });\n }\n\n return {\n job_id: report.jobId,\n room_id: report.roomId,\n room: report.room,\n events,\n options: {\n allow_interruptions: report.options.allowInterruptions,\n discard_audio_if_uninterruptible: report.options.discardAudioIfUninterruptible,\n min_interruption_duration: report.options.minInterruptionDuration,\n min_interruption_words: report.options.minInterruptionWords,\n min_endpointing_delay: report.options.minEndpointingDelay,\n max_endpointing_delay: report.options.maxEndpointingDelay,\n max_tool_steps: report.options.maxToolSteps,\n },\n chat_history: report.chatHistory.toJSON({ excludeTimestamp: false }),\n enable_user_data_training: report.enableUserDataTraining,\n timestamp: report.timestamp,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA6BO,SAAS,oBAAoB,MAA2C;AAC7E,SAAO;AAAA,IACL,OAAO,KAAK;AAAA,IACZ,QAAQ,KAAK;AAAA,IACb,MAAM,KAAK;AAAA,IACX,SAAS,KAAK;AAAA,IACd,QAAQ,KAAK;AAAA,IACb,aAAa,KAAK;AAAA,IAClB,wBAAwB,KAAK,0BAA0B;AAAA,IACvD,WAAW,KAAK,aAAa,KAAK,IAAI;AAAA,EACxC;AACF;AAOO,SAAS,oBAAoB,QAAgD;AAClF,QAAM,SAAoC,CAAC;AAE3C,aAAW,SAAS,OAAO,QAAQ;AACjC,QAAI,MAAM,SAAS,qBAAqB;AACtC;AAAA,IACF;AAEA,WAAO,KAAK,EAAE,GAAG,MAAM,CAAC;AAAA,EAC1B;AAEA,SAAO;AAAA,IACL,QAAQ,OAAO;AAAA,IACf,SAAS,OAAO;AAAA,IAChB,MAAM,OAAO;AAAA,IACb;AAAA,IACA,SAAS;AAAA,MACP,qBAAqB,OAAO,QAAQ;AAAA,MACpC,kCAAkC,OAAO,QAAQ;AAAA,MACjD,2BAA2B,OAAO,QAAQ;AAAA,MAC1C,wBAAwB,OAAO,QAAQ;AAAA,MACvC,uBAAuB,OAAO,QAAQ;AAAA,MACtC,uBAAuB,OAAO,QAAQ;AAAA,MACtC,gBAAgB,OAAO,QAAQ;AAAA,IACjC;AAAA,IACA,cAAc,OAAO,YAAY,OAAO,EAAE,kBAAkB,MAAM,CAAC;AAAA,IACnE,2BAA2B,OAAO;AAAA,IAClC,WAAW,OAAO;AAAA,EACpB;AACF;","names":[]}
@@ -0,0 +1,26 @@
1
+ import type { ChatContext } from '../llm/chat_context.js';
2
+ import type { VoiceOptions } from './agent_session.js';
3
+ import type { AgentEvent } from './events.js';
4
+ export interface SessionReport {
5
+ jobId: string;
6
+ roomId: string;
7
+ room: string;
8
+ options: VoiceOptions;
9
+ events: AgentEvent[];
10
+ chatHistory: ChatContext;
11
+ enableUserDataTraining: boolean;
12
+ timestamp: number;
13
+ }
14
+ export interface SessionReportOptions {
15
+ jobId: string;
16
+ roomId: string;
17
+ room: string;
18
+ options: VoiceOptions;
19
+ events: AgentEvent[];
20
+ chatHistory: ChatContext;
21
+ enableUserDataTraining?: boolean;
22
+ timestamp?: number;
23
+ }
24
+ export declare function createSessionReport(opts: SessionReportOptions): SessionReport;
25
+ export declare function sessionReportToJSON(report: SessionReport): Record<string, unknown>;
26
+ //# sourceMappingURL=report.d.ts.map
@@ -0,0 +1,26 @@
1
+ import type { ChatContext } from '../llm/chat_context.js';
2
+ import type { VoiceOptions } from './agent_session.js';
3
+ import type { AgentEvent } from './events.js';
4
+ export interface SessionReport {
5
+ jobId: string;
6
+ roomId: string;
7
+ room: string;
8
+ options: VoiceOptions;
9
+ events: AgentEvent[];
10
+ chatHistory: ChatContext;
11
+ enableUserDataTraining: boolean;
12
+ timestamp: number;
13
+ }
14
+ export interface SessionReportOptions {
15
+ jobId: string;
16
+ roomId: string;
17
+ room: string;
18
+ options: VoiceOptions;
19
+ events: AgentEvent[];
20
+ chatHistory: ChatContext;
21
+ enableUserDataTraining?: boolean;
22
+ timestamp?: number;
23
+ }
24
+ export declare function createSessionReport(opts: SessionReportOptions): SessionReport;
25
+ export declare function sessionReportToJSON(report: SessionReport): Record<string, unknown>;
26
+ //# sourceMappingURL=report.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../src/voice/report.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,YAAY,CAAC;IACtB,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,WAAW,EAAE,WAAW,CAAC;IACzB,sBAAsB,EAAE,OAAO,CAAC;IAChC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,YAAY,CAAC;IACtB,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,WAAW,EAAE,WAAW,CAAC;IACzB,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,oBAAoB,GAAG,aAAa,CAW7E;AAOD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA6BlF"}
@@ -0,0 +1,44 @@
1
+ function createSessionReport(opts) {
2
+ return {
3
+ jobId: opts.jobId,
4
+ roomId: opts.roomId,
5
+ room: opts.room,
6
+ options: opts.options,
7
+ events: opts.events,
8
+ chatHistory: opts.chatHistory,
9
+ enableUserDataTraining: opts.enableUserDataTraining ?? false,
10
+ timestamp: opts.timestamp ?? Date.now()
11
+ };
12
+ }
13
+ function sessionReportToJSON(report) {
14
+ const events = [];
15
+ for (const event of report.events) {
16
+ if (event.type === "metrics_collected") {
17
+ continue;
18
+ }
19
+ events.push({ ...event });
20
+ }
21
+ return {
22
+ job_id: report.jobId,
23
+ room_id: report.roomId,
24
+ room: report.room,
25
+ events,
26
+ options: {
27
+ allow_interruptions: report.options.allowInterruptions,
28
+ discard_audio_if_uninterruptible: report.options.discardAudioIfUninterruptible,
29
+ min_interruption_duration: report.options.minInterruptionDuration,
30
+ min_interruption_words: report.options.minInterruptionWords,
31
+ min_endpointing_delay: report.options.minEndpointingDelay,
32
+ max_endpointing_delay: report.options.maxEndpointingDelay,
33
+ max_tool_steps: report.options.maxToolSteps
34
+ },
35
+ chat_history: report.chatHistory.toJSON({ excludeTimestamp: false }),
36
+ enable_user_data_training: report.enableUserDataTraining,
37
+ timestamp: report.timestamp
38
+ };
39
+ }
40
+ export {
41
+ createSessionReport,
42
+ sessionReportToJSON
43
+ };
44
+ //# sourceMappingURL=report.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/voice/report.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { ChatContext } from '../llm/chat_context.js';\nimport type { VoiceOptions } from './agent_session.js';\nimport type { AgentEvent } from './events.js';\n\nexport interface SessionReport {\n jobId: string;\n roomId: string;\n room: string;\n options: VoiceOptions;\n events: AgentEvent[];\n chatHistory: ChatContext;\n enableUserDataTraining: boolean;\n timestamp: number;\n}\n\nexport interface SessionReportOptions {\n jobId: string;\n roomId: string;\n room: string;\n options: VoiceOptions;\n events: AgentEvent[];\n chatHistory: ChatContext;\n enableUserDataTraining?: boolean;\n timestamp?: number;\n}\n\nexport function createSessionReport(opts: SessionReportOptions): SessionReport {\n return {\n jobId: opts.jobId,\n roomId: opts.roomId,\n room: opts.room,\n options: opts.options,\n events: opts.events,\n chatHistory: opts.chatHistory,\n enableUserDataTraining: opts.enableUserDataTraining ?? false,\n timestamp: opts.timestamp ?? Date.now(),\n };\n}\n\n// TODO(brian): PR5 - Add uploadSessionReport() function that creates multipart form with:\n// - header: protobuf MetricsRecordingHeader (room_id, duration, start_time)\n// - chat_history: JSON serialized chat history (use sessionReportToJSON)\n// - audio: audio recording file if available (ogg format)\n// - Uploads to LiveKit Cloud observability endpoint with JWT auth\nexport function sessionReportToJSON(report: SessionReport): Record<string, unknown> {\n const events: Record<string, unknown>[] = [];\n\n for (const event of report.events) {\n if (event.type === 'metrics_collected') {\n continue; // metrics are too noisy, Cloud is using the chat_history as the source of truth\n }\n\n events.push({ ...event });\n }\n\n return {\n job_id: report.jobId,\n room_id: report.roomId,\n room: report.room,\n events,\n options: {\n allow_interruptions: report.options.allowInterruptions,\n discard_audio_if_uninterruptible: report.options.discardAudioIfUninterruptible,\n min_interruption_duration: report.options.minInterruptionDuration,\n min_interruption_words: report.options.minInterruptionWords,\n min_endpointing_delay: report.options.minEndpointingDelay,\n max_endpointing_delay: report.options.maxEndpointingDelay,\n max_tool_steps: report.options.maxToolSteps,\n },\n chat_history: report.chatHistory.toJSON({ excludeTimestamp: false }),\n enable_user_data_training: report.enableUserDataTraining,\n timestamp: report.timestamp,\n };\n}\n"],"mappings":"AA6BO,SAAS,oBAAoB,MAA2C;AAC7E,SAAO;AAAA,IACL,OAAO,KAAK;AAAA,IACZ,QAAQ,KAAK;AAAA,IACb,MAAM,KAAK;AAAA,IACX,SAAS,KAAK;AAAA,IACd,QAAQ,KAAK;AAAA,IACb,aAAa,KAAK;AAAA,IAClB,wBAAwB,KAAK,0BAA0B;AAAA,IACvD,WAAW,KAAK,aAAa,KAAK,IAAI;AAAA,EACxC;AACF;AAOO,SAAS,oBAAoB,QAAgD;AAClF,QAAM,SAAoC,CAAC;AAE3C,aAAW,SAAS,OAAO,QAAQ;AACjC,QAAI,MAAM,SAAS,qBAAqB;AACtC;AAAA,IACF;AAEA,WAAO,KAAK,EAAE,GAAG,MAAM,CAAC;AAAA,EAC1B;AAEA,SAAO;AAAA,IACL,QAAQ,OAAO;AAAA,IACf,SAAS,OAAO;AAAA,IAChB,MAAM,OAAO;AAAA,IACb;AAAA,IACA,SAAS;AAAA,MACP,qBAAqB,OAAO,QAAQ;AAAA,MACpC,kCAAkC,OAAO,QAAQ;AAAA,MACjD,2BAA2B,OAAO,QAAQ;AAAA,MAC1C,wBAAwB,OAAO,QAAQ;AAAA,MACvC,uBAAuB,OAAO,QAAQ;AAAA,MACtC,uBAAuB,OAAO,QAAQ;AAAA,MACtC,gBAAgB,OAAO,QAAQ;AAAA,IACjC;AAAA,IACA,cAAc,OAAO,YAAY,OAAO,EAAE,kBAAkB,MAAM,CAAC;AAAA,IACnE,2BAA2B,OAAO;AAAA,IAClC,WAAW,OAAO;AAAA,EACpB;AACF;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livekit/agents",
3
- "version": "1.0.18",
3
+ "version": "1.0.20",
4
4
  "description": "LiveKit Agents - Node.js",
5
5
  "main": "dist/index.js",
6
6
  "require": "dist/index.cjs",
@@ -39,14 +39,21 @@
39
39
  "dependencies": {
40
40
  "@ffmpeg-installer/ffmpeg": "^1.1.0",
41
41
  "@livekit/mutex": "^1.1.1",
42
- "@livekit/protocol": "^1.41.0",
42
+ "@livekit/protocol": "^1.43.0",
43
43
  "@livekit/typed-emitter": "^3.0.0",
44
+ "@opentelemetry/api": "^1.9.0",
45
+ "@opentelemetry/exporter-trace-otlp-http": "^0.54.0",
46
+ "@opentelemetry/otlp-exporter-base": "^0.208.0",
47
+ "@opentelemetry/resources": "^1.28.0",
48
+ "@opentelemetry/sdk-trace-base": "^1.28.0",
49
+ "@opentelemetry/sdk-trace-node": "^1.28.0",
50
+ "@opentelemetry/semantic-conventions": "^1.28.0",
44
51
  "@types/pidusage": "^2.0.5",
45
52
  "commander": "^12.0.0",
46
53
  "fluent-ffmpeg": "^2.1.3",
47
54
  "heap-js": "^2.6.0",
48
55
  "json-schema": "^0.4.0",
49
- "livekit-server-sdk": "^2.13.3",
56
+ "livekit-server-sdk": "^2.14.1",
50
57
  "openai": "^6.8.1",
51
58
  "pidusage": "^4.0.1",
52
59
  "pino": "^8.19.0",
package/src/index.ts CHANGED
@@ -16,6 +16,7 @@ import * as llm from './llm/index.js';
16
16
  import * as metrics from './metrics/index.js';
17
17
  import * as stream from './stream/index.js';
18
18
  import * as stt from './stt/index.js';
19
+ import * as telemetry from './telemetry/index.js';
19
20
  import * as tokenize from './tokenize/index.js';
20
21
  import * as tts from './tts/index.js';
21
22
  import * as voice from './voice/index.js';
@@ -34,4 +35,4 @@ export * from './vad.js';
34
35
  export * from './version.js';
35
36
  export * from './worker.js';
36
37
 
37
- export { cli, inference, ipc, llm, metrics, stream, stt, tokenize, tts, voice };
38
+ export { cli, inference, ipc, llm, metrics, stream, stt, telemetry, tokenize, tts, voice };
@@ -421,7 +421,7 @@ export class SynthesizeStream<TModel extends TTSModels> extends BaseSynthesizeSt
421
421
  createRecvTask(),
422
422
  ]);
423
423
  } catch (e) {
424
- this.#logger.error('Error in SynthesizeStream', { error: e });
424
+ this.#logger.error({ error: e }, 'Error in SynthesizeStream');
425
425
  } finally {
426
426
  resourceCleanup();
427
427
  }
@@ -88,7 +88,7 @@ const startJob = (
88
88
 
89
89
  const ctx = new JobContext(proc, info, room, onConnect, onShutdown, new InfClient());
90
90
 
91
- const task = new Promise<void>(async () => {
91
+ const task = (async () => {
92
92
  const unconnectedTimeout = setTimeout(() => {
93
93
  if (!(connect || shutdown)) {
94
94
  logger.warn(
@@ -109,6 +109,14 @@ const startJob = (
109
109
  process.send!({ case: 'exiting', value: { reason: close[1] } });
110
110
  });
111
111
 
112
+ // Close the primary agent session if it exists
113
+ if (ctx._primaryAgentSession) {
114
+ await ctx._primaryAgentSession.close();
115
+ }
116
+
117
+ // Generate and save/upload session report
118
+ await ctx._onSessionEnd();
119
+
112
120
  await room.disconnect();
113
121
  logger.debug('disconnected from room');
114
122
 
@@ -122,7 +130,7 @@ const startJob = (
122
130
 
123
131
  process.send!({ case: 'done' });
124
132
  joinFuture.resolve();
125
- });
133
+ })();
126
134
 
127
135
  return { ctx, task };
128
136
  };
package/src/job.ts CHANGED
@@ -14,6 +14,8 @@ import { AsyncLocalStorage } from 'node:async_hooks';
14
14
  import type { Logger } from 'pino';
15
15
  import type { InferenceExecutor } from './ipc/inference_executor.js';
16
16
  import { log } from './log.js';
17
+ import type { AgentSession } from './voice/agent_session.js';
18
+ import { type SessionReport, createSessionReport } from './voice/report.js';
17
19
 
18
20
  // AsyncLocalStorage for job context, similar to Python's contextvars
19
21
  const jobContextStorage = new AsyncLocalStorage<JobContext>();
@@ -79,6 +81,8 @@ export class FunctionExistsError extends Error {
79
81
  }
80
82
 
81
83
  /** The job and environment context as seen by the agent, accessible by the entrypoint function. */
84
+ // TODO(brian): PR3 - Add @tracer.startActiveSpan('job_entrypoint') wrapper in entrypoint
85
+ // TODO(brian): PR5 - Add uploadSessionReport() call in cleanup/session end
82
86
  export class JobContext {
83
87
  #proc: JobProcess;
84
88
  #info: RunningJobInfo;
@@ -97,6 +101,9 @@ export class JobContext {
97
101
  #logger: Logger;
98
102
  #inferenceExecutor: InferenceExecutor;
99
103
 
104
+ /** @internal */
105
+ _primaryAgentSession?: AgentSession;
106
+
100
107
  private connected: boolean = false;
101
108
 
102
109
  constructor(
@@ -232,6 +239,47 @@ export class JobContext {
232
239
  this.connected = true;
233
240
  }
234
241
 
242
+ makeSessionReport(session?: AgentSession): SessionReport {
243
+ const targetSession = session || this._primaryAgentSession;
244
+
245
+ if (!targetSession) {
246
+ throw new Error('Cannot prepare report, no AgentSession was found');
247
+ }
248
+
249
+ // TODO(brian): implement and check recorder io
250
+ // TODO(brian): PR5 - Ensure chat history serialization includes all required fields (use sessionReportToJSON helper)
251
+
252
+ return createSessionReport({
253
+ jobId: this.job.id,
254
+ roomId: this.job.room?.sid || '',
255
+ room: this.job.room?.name || '',
256
+ options: targetSession.options,
257
+ events: targetSession._recordedEvents,
258
+ enableUserDataTraining: true,
259
+ chatHistory: targetSession.history.copy(),
260
+ });
261
+ }
262
+
263
+ async _onSessionEnd(): Promise<void> {
264
+ const session = this._primaryAgentSession;
265
+ if (!session) {
266
+ return;
267
+ }
268
+
269
+ const report = this.makeSessionReport(session);
270
+
271
+ // TODO(brian): Implement CLI/console
272
+
273
+ // TODO(brian): PR5 - Call uploadSessionReport() if report.enableUserDataTraining is true
274
+ // TODO(brian): PR5 - Upload includes: multipart form with header (protobuf), chat_history (JSON), and audio recording (if available)
275
+
276
+ this.#logger.debug('Session ended, report generated', {
277
+ jobId: report.jobId,
278
+ roomId: report.roomId,
279
+ eventsCount: report.events.length,
280
+ });
281
+ }
282
+
235
283
  /**
236
284
  * Gracefully shuts down the job, and runs all shutdown promises.
237
285
  *
@@ -300,7 +300,59 @@ export class FunctionCallOutput {
300
300
  }
301
301
  }
302
302
 
303
- export type ChatItem = ChatMessage | FunctionCall | FunctionCallOutput;
303
+ export class AgentHandoffItem {
304
+ readonly id: string;
305
+
306
+ readonly type = 'agent_handoff' as const;
307
+
308
+ oldAgentId: string | undefined;
309
+
310
+ newAgentId: string;
311
+
312
+ createdAt: number;
313
+
314
+ constructor(params: {
315
+ oldAgentId?: string;
316
+ newAgentId: string;
317
+ id?: string;
318
+ createdAt?: number;
319
+ }) {
320
+ const { oldAgentId, newAgentId, id = shortuuid('item_'), createdAt = Date.now() } = params;
321
+ this.id = id;
322
+ this.oldAgentId = oldAgentId;
323
+ this.newAgentId = newAgentId;
324
+ this.createdAt = createdAt;
325
+ }
326
+
327
+ static create(params: {
328
+ oldAgentId?: string;
329
+ newAgentId: string;
330
+ id?: string;
331
+ createdAt?: number;
332
+ }) {
333
+ return new AgentHandoffItem(params);
334
+ }
335
+
336
+ toJSON(excludeTimestamp: boolean = false): JSONValue {
337
+ const result: JSONValue = {
338
+ id: this.id,
339
+ type: this.type,
340
+ newAgentId: this.newAgentId,
341
+ };
342
+
343
+ if (this.oldAgentId !== undefined) {
344
+ result.oldAgentId = this.oldAgentId;
345
+ }
346
+
347
+ if (!excludeTimestamp) {
348
+ result.createdAt = this.createdAt;
349
+ }
350
+
351
+ return result;
352
+ }
353
+ }
354
+
355
+ export type ChatItem = ChatMessage | FunctionCall | FunctionCallOutput | AgentHandoffItem;
304
356
 
305
357
  export class ChatContext {
306
358
  protected _items: ChatItem[];
package/src/llm/index.ts CHANGED
@@ -17,6 +17,7 @@ export {
17
17
  } from './tool_context.js';
18
18
 
19
19
  export {
20
+ AgentHandoffItem,
20
21
  ChatContext,
21
22
  ChatMessage,
22
23
  createAudioContent,
package/src/llm/llm.ts CHANGED
@@ -136,8 +136,10 @@ export abstract class LLMStream implements AsyncIterableIterator<ChatChunk> {
136
136
  }
137
137
 
138
138
  private async mainTask() {
139
+ // TODO(brian): PR3 - Add span wrapping: tracer.startActiveSpan('llm_request', ..., { endOnExit: false })
139
140
  for (let i = 0; i < this._connOptions.maxRetry + 1; i++) {
140
141
  try {
142
+ // TODO(brian): PR3 - Add span for retry attempts: tracer.startActiveSpan('llm_request_run', ...)
141
143
  return await this.run();
142
144
  } catch (error) {
143
145
  if (error instanceof APIError) {
@@ -4,7 +4,12 @@
4
4
  import { VideoBufferType, VideoFrame } from '@livekit/rtc-node';
5
5
  import { beforeEach, describe, expect, it, vi } from 'vitest';
6
6
  import { initializeLogger } from '../../log.js';
7
- import { ChatContext, FunctionCall, FunctionCallOutput } from '../chat_context.js';
7
+ import {
8
+ AgentHandoffItem,
9
+ ChatContext,
10
+ FunctionCall,
11
+ FunctionCallOutput,
12
+ } from '../chat_context.js';
8
13
  import { serializeImage } from '../utils.js';
9
14
  import { toChatCtx } from './google.js';
10
15
 
@@ -769,4 +774,70 @@ describe('Google Provider Format - toChatCtx', () => {
769
774
  ]);
770
775
  expect(formatData.systemMessages).toBeNull();
771
776
  });
777
+
778
+ it('should filter out agent handoff items', async () => {
779
+ const ctx = ChatContext.empty();
780
+
781
+ ctx.addMessage({ role: 'user', content: 'Hello' });
782
+
783
+ // Insert an agent handoff item
784
+ const handoff = new AgentHandoffItem({
785
+ oldAgentId: 'agent_1',
786
+ newAgentId: 'agent_2',
787
+ });
788
+ ctx.insert(handoff);
789
+
790
+ ctx.addMessage({ role: 'assistant', content: 'Hi there!' });
791
+
792
+ const [result, formatData] = await toChatCtx(ctx, false);
793
+
794
+ // Agent handoff should be filtered out, only messages should remain
795
+ expect(result).toEqual([
796
+ {
797
+ role: 'user',
798
+ parts: [{ text: 'Hello' }],
799
+ },
800
+ {
801
+ role: 'model',
802
+ parts: [{ text: 'Hi there!' }],
803
+ },
804
+ ]);
805
+ expect(formatData.systemMessages).toBeNull();
806
+ });
807
+
808
+ it('should handle multiple agent handoffs without errors', async () => {
809
+ const ctx = ChatContext.empty();
810
+
811
+ ctx.addMessage({ role: 'user', content: 'Start' });
812
+
813
+ // Multiple handoffs
814
+ ctx.insert(new AgentHandoffItem({ oldAgentId: undefined, newAgentId: 'agent_1' }));
815
+ ctx.addMessage({ role: 'assistant', content: 'Response from agent 1' });
816
+
817
+ ctx.insert(new AgentHandoffItem({ oldAgentId: 'agent_1', newAgentId: 'agent_2' }));
818
+ ctx.addMessage({ role: 'assistant', content: 'Response from agent 2' });
819
+
820
+ ctx.insert(new AgentHandoffItem({ oldAgentId: 'agent_2', newAgentId: 'agent_3' }));
821
+ ctx.addMessage({ role: 'assistant', content: 'Response from agent 3' });
822
+
823
+ const [result, formatData] = await toChatCtx(ctx, false);
824
+
825
+ // All handoffs should be filtered out
826
+ // Note: Google provider groups consecutive messages by the same role
827
+ expect(result).toEqual([
828
+ {
829
+ role: 'user',
830
+ parts: [{ text: 'Start' }],
831
+ },
832
+ {
833
+ role: 'model',
834
+ parts: [
835
+ { text: 'Response from agent 1' },
836
+ { text: 'Response from agent 2' },
837
+ { text: 'Response from agent 3' },
838
+ ],
839
+ },
840
+ ]);
841
+ expect(formatData.systemMessages).toBeNull();
842
+ });
772
843
  });
@@ -4,7 +4,12 @@
4
4
  import { VideoBufferType, VideoFrame } from '@livekit/rtc-node';
5
5
  import { beforeEach, describe, expect, it, vi } from 'vitest';
6
6
  import { initializeLogger } from '../../log.js';
7
- import { ChatContext, FunctionCall, FunctionCallOutput } from '../chat_context.js';
7
+ import {
8
+ AgentHandoffItem,
9
+ ChatContext,
10
+ FunctionCall,
11
+ FunctionCallOutput,
12
+ } from '../chat_context.js';
8
13
  import { serializeImage } from '../utils.js';
9
14
  import { toChatCtx } from './openai.js';
10
15
 
@@ -578,4 +583,53 @@ describe('toChatCtx', () => {
578
583
  },
579
584
  ]);
580
585
  });
586
+
587
+ it('should filter out agent handoff items', async () => {
588
+ const ctx = ChatContext.empty();
589
+
590
+ ctx.addMessage({ role: 'user', content: 'Hello' });
591
+
592
+ // Insert an agent handoff item
593
+ const handoff = new AgentHandoffItem({
594
+ oldAgentId: 'agent_1',
595
+ newAgentId: 'agent_2',
596
+ });
597
+ ctx.insert(handoff);
598
+
599
+ ctx.addMessage({ role: 'assistant', content: 'Hi there!' });
600
+
601
+ const result = await toChatCtx(ctx);
602
+
603
+ // Agent handoff should be filtered out, only messages should remain
604
+ expect(result).toEqual([
605
+ { role: 'user', content: 'Hello' },
606
+ { role: 'assistant', content: 'Hi there!' },
607
+ ]);
608
+ });
609
+
610
+ it('should handle multiple agent handoffs without errors', async () => {
611
+ const ctx = ChatContext.empty();
612
+
613
+ ctx.addMessage({ role: 'user', content: 'Start' });
614
+
615
+ // Multiple handoffs
616
+ ctx.insert(new AgentHandoffItem({ oldAgentId: undefined, newAgentId: 'agent_1' }));
617
+ ctx.addMessage({ role: 'assistant', content: 'Response from agent 1' });
618
+
619
+ ctx.insert(new AgentHandoffItem({ oldAgentId: 'agent_1', newAgentId: 'agent_2' }));
620
+ ctx.addMessage({ role: 'assistant', content: 'Response from agent 2' });
621
+
622
+ ctx.insert(new AgentHandoffItem({ oldAgentId: 'agent_2', newAgentId: 'agent_3' }));
623
+ ctx.addMessage({ role: 'assistant', content: 'Response from agent 3' });
624
+
625
+ const result = await toChatCtx(ctx);
626
+
627
+ // All handoffs should be filtered out
628
+ expect(result).toEqual([
629
+ { role: 'user', content: 'Start' },
630
+ { role: 'assistant', content: 'Response from agent 1' },
631
+ { role: 'assistant', content: 'Response from agent 2' },
632
+ { role: 'assistant', content: 'Response from agent 3' },
633
+ ]);
634
+ });
581
635
  });
@@ -78,9 +78,10 @@ async function toChatItem(item: ChatItem) {
78
78
  tool_call_id: item.callId,
79
79
  content: item.output,
80
80
  };
81
- } else {
82
- throw new Error(`Unsupported item type: ${item['type']}`);
83
81
  }
82
+ // Skip other item types (e.g., agent_handoff)
83
+ // These should be filtered by groupToolCalls, but this is a safety net
84
+ throw new Error(`Unsupported item type: ${item['type']}`);
84
85
  }
85
86
 
86
87
  async function toImageContent(content: ImageContent) {
package/src/log.ts CHANGED
@@ -42,4 +42,5 @@ export const initializeLogger = ({ pretty, level }: LoggerOptions) => {
42
42
  if (level) {
43
43
  logger.level = level;
44
44
  }
45
+ // TODO(brian): PR4 - Add Pino bridge to OTEL LoggingHandler for structured logging integration
45
46
  };