@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livekit/agents",
3
- "version": "1.0.17",
3
+ "version": "1.0.19",
4
4
  "description": "LiveKit Agents - Node.js",
5
5
  "main": "dist/index.js",
6
6
  "require": "dist/index.cjs",
@@ -39,21 +39,28 @@
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",
50
- "openai": "^4.91.1",
56
+ "livekit-server-sdk": "^2.14.1",
57
+ "openai": "^6.8.1",
51
58
  "pidusage": "^4.0.1",
52
59
  "pino": "^8.19.0",
53
60
  "pino-pretty": "^11.0.0",
54
61
  "sharp": "0.34.3",
55
62
  "uuid": "^11.1.0",
56
- "ws": "^8.16.0",
63
+ "ws": "^8.18.0",
57
64
  "zod-to-json-schema": "^3.24.6"
58
65
  },
59
66
  "peerDependencies": {
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 };
@@ -7,6 +7,7 @@ import {
7
7
  APIStatusError,
8
8
  APITimeoutError,
9
9
  DEFAULT_API_CONNECT_OPTIONS,
10
+ type Expand,
10
11
  toError,
11
12
  } from '../index.js';
12
13
  import * as llm from '../llm/index.js';
@@ -34,9 +35,10 @@ export type KimiModels = 'moonshotai/kimi-k2-instruct';
34
35
 
35
36
  export type DeepSeekModels = 'deepseek-ai/deepseek-v3';
36
37
 
37
- type ChatCompletionPredictionContentParam = OpenAI.Chat.Completions.ChatCompletionPredictionContent;
38
- type WebSearchOptions = OpenAI.Chat.Completions.ChatCompletionCreateParams.WebSearchOptions;
39
- type ToolChoice = OpenAI.Chat.Completions.ChatCompletionCreateParams['tool_choice'];
38
+ type ChatCompletionPredictionContentParam =
39
+ Expand<OpenAI.Chat.Completions.ChatCompletionPredictionContent>;
40
+ type WebSearchOptions = Expand<OpenAI.Chat.Completions.ChatCompletionCreateParams.WebSearchOptions>;
41
+ type ToolChoice = Expand<OpenAI.Chat.Completions.ChatCompletionCreateParams['tool_choice']>;
40
42
  type Verbosity = 'low' | 'medium' | 'high';
41
43
 
42
44
  export interface ChatCompletionOptions extends Record<string, unknown> {
@@ -86,6 +88,7 @@ export interface InferenceLLMOptions {
86
88
  apiKey: string;
87
89
  apiSecret: string;
88
90
  modelOptions: ChatCompletionOptions;
91
+ strictToolSchema?: boolean;
89
92
  }
90
93
 
91
94
  export interface GatewayOptions {
@@ -107,10 +110,19 @@ export class LLM extends llm.LLM {
107
110
  apiKey?: string;
108
111
  apiSecret?: string;
109
112
  modelOptions?: InferenceLLMOptions['modelOptions'];
113
+ strictToolSchema?: boolean;
110
114
  }) {
111
115
  super();
112
116
 
113
- const { model, provider, baseURL, apiKey, apiSecret, modelOptions } = opts;
117
+ const {
118
+ model,
119
+ provider,
120
+ baseURL,
121
+ apiKey,
122
+ apiSecret,
123
+ modelOptions,
124
+ strictToolSchema = false,
125
+ } = opts;
114
126
 
115
127
  const lkBaseURL = baseURL || process.env.LIVEKIT_INFERENCE_URL || DEFAULT_BASE_URL;
116
128
  const lkApiKey = apiKey || process.env.LIVEKIT_INFERENCE_API_KEY || process.env.LIVEKIT_API_KEY;
@@ -131,6 +143,7 @@ export class LLM extends llm.LLM {
131
143
  apiKey: lkApiKey,
132
144
  apiSecret: lkApiSecret,
133
145
  modelOptions: modelOptions || {},
146
+ strictToolSchema,
134
147
  };
135
148
 
136
149
  this.client = new OpenAI({
@@ -180,9 +193,13 @@ export class LLM extends llm.LLM {
180
193
  modelOptions.parallel_tool_calls = parallelToolCalls;
181
194
  }
182
195
 
183
- toolChoice = toolChoice !== undefined ? toolChoice : this.opts.modelOptions.tool_choice;
196
+ toolChoice =
197
+ toolChoice !== undefined
198
+ ? toolChoice
199
+ : (this.opts.modelOptions.tool_choice as llm.ToolChoice | undefined);
200
+
184
201
  if (toolChoice) {
185
- modelOptions.tool_choice = toolChoice;
202
+ modelOptions.tool_choice = toolChoice as ToolChoice;
186
203
  }
187
204
 
188
205
  // TODO(AJS-270): Add response_format support here
@@ -197,6 +214,7 @@ export class LLM extends llm.LLM {
197
214
  toolCtx,
198
215
  connOptions,
199
216
  modelOptions,
217
+ strictToolSchema: this.opts.strictToolSchema ?? false, // default to false if not set
200
218
  gatewayOptions: {
201
219
  apiKey: this.opts.apiKey,
202
220
  apiSecret: this.opts.apiSecret,
@@ -211,6 +229,7 @@ export class LLMStream extends llm.LLMStream {
211
229
  private providerFmt: llm.ProviderFormat;
212
230
  private client: OpenAI;
213
231
  private modelOptions: Record<string, unknown>;
232
+ private strictToolSchema: boolean;
214
233
 
215
234
  private gatewayOptions?: GatewayOptions;
216
235
  private toolCallId?: string;
@@ -230,6 +249,7 @@ export class LLMStream extends llm.LLMStream {
230
249
  connOptions,
231
250
  modelOptions,
232
251
  providerFmt,
252
+ strictToolSchema,
233
253
  }: {
234
254
  model: LLMModels;
235
255
  provider?: string;
@@ -238,8 +258,9 @@ export class LLMStream extends llm.LLMStream {
238
258
  toolCtx?: llm.ToolContext;
239
259
  gatewayOptions?: GatewayOptions;
240
260
  connOptions: APIConnectOptions;
241
- modelOptions: Record<string, any>;
261
+ modelOptions: Record<string, unknown>;
242
262
  providerFmt?: llm.ProviderFormat;
263
+ strictToolSchema: boolean;
243
264
  },
244
265
  ) {
245
266
  super(llm, { chatCtx, toolCtx, connOptions });
@@ -249,6 +270,7 @@ export class LLMStream extends llm.LLMStream {
249
270
  this.providerFmt = providerFmt || 'openai';
250
271
  this.modelOptions = modelOptions;
251
272
  this.model = model;
273
+ this.strictToolSchema = strictToolSchema;
252
274
  }
253
275
 
254
276
  protected async run(): Promise<void> {
@@ -263,16 +285,26 @@ export class LLMStream extends llm.LLMStream {
263
285
  )) as OpenAI.ChatCompletionMessageParam[];
264
286
 
265
287
  const tools = this.toolCtx
266
- ? Object.entries(this.toolCtx).map(([name, func]) => ({
267
- type: 'function' as const,
268
- function: {
269
- name,
270
- description: func.description,
271
- parameters: llm.toJsonSchema(
272
- func.parameters,
273
- ) as unknown as OpenAI.Chat.Completions.ChatCompletionTool['function']['parameters'],
274
- },
275
- }))
288
+ ? Object.entries(this.toolCtx).map(([name, func]) => {
289
+ const oaiParams = {
290
+ type: 'function' as const,
291
+ function: {
292
+ name,
293
+ description: func.description,
294
+ parameters: llm.toJsonSchema(
295
+ func.parameters,
296
+ true,
297
+ this.strictToolSchema,
298
+ ) as unknown as OpenAI.Chat.Completions.ChatCompletionFunctionTool['function']['parameters'],
299
+ } as OpenAI.Chat.Completions.ChatCompletionFunctionTool['function'],
300
+ };
301
+
302
+ if (this.strictToolSchema) {
303
+ oaiParams.function.strict = true;
304
+ }
305
+
306
+ return oaiParams;
307
+ })
276
308
  : undefined;
277
309
 
278
310
  const requestOptions: Record<string, unknown> = { ...this.modelOptions };
@@ -345,7 +377,7 @@ export class LLMStream extends llm.LLMStream {
345
377
  options: {
346
378
  statusCode: error.status,
347
379
  body: error.error,
348
- requestId: error.request_id,
380
+ requestId: error.requestID,
349
381
  retryable,
350
382
  },
351
383
  });
@@ -387,10 +419,10 @@ export class LLMStream extends llm.LLMStream {
387
419
  *
388
420
  * Choice(delta=ChoiceDelta(content=None, function_call=None, refusal=None, role='assistant', tool_calls=None), finish_reason=None, index=0, logprobs=None)
389
421
  * [ChoiceDeltaToolCall(index=0, id='call_LaVeHWUHpef9K1sd5UO8TtLg', function=ChoiceDeltaToolCallFunction(arguments='', name='get_weather'), type='function')]
390
- * [ChoiceDeltaToolCall(index=0, id=None, function=ChoiceDeltaToolCallFunction(arguments='{"location": "P', name=None), type=None)]
391
- * [ChoiceDeltaToolCall(index=0, id=None, function=ChoiceDeltaToolCallFunction(arguments='aris}', name=None), type=None)]
422
+ * [ChoiceDeltaToolCall(index=0, id=None, function=ChoiceDeltaToolCallFunction(arguments='\{"location": "P', name=None), type=None)]
423
+ * [ChoiceDeltaToolCall(index=0, id=None, function=ChoiceDeltaToolCallFunction(arguments='aris\}', name=None), type=None)]
392
424
  * [ChoiceDeltaToolCall(index=1, id='call_ThU4OmMdQXnnVmpXGOCknXIB', function=ChoiceDeltaToolCallFunction(arguments='', name='get_weather'), type='function')]
393
- * [ChoiceDeltaToolCall(index=1, id=None, function=ChoiceDeltaToolCallFunction(arguments='{"location": "T', name=None), type=None)]
425
+ * [ChoiceDeltaToolCall(index=1, id=None, function=ChoiceDeltaToolCallFunction(arguments='\{"location": "T', name=None), type=None)]
394
426
  * [ChoiceDeltaToolCall(index=1, id=None, function=ChoiceDeltaToolCallFunction(arguments='okyo', name=None), type=None)]
395
427
  * Choice(delta=ChoiceDelta(content=None, function_call=None, refusal=None, role=None, tool_calls=None), finish_reason='tool_calls', index=0, logprobs=None)
396
428
  */
@@ -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
  *
@@ -339,3 +339,221 @@ exports[`Zod Utils > zodSchemaToJsonSchema > Zod v4 schemas > should handle v4 s
339
339
  "type": "object",
340
340
  }
341
341
  `;
342
+
343
+ exports[`Zod Utils > zodSchemaToJsonSchema > strict parameter > should handle arrays in strict mode 1`] = `
344
+ {
345
+ "$schema": "http://json-schema.org/draft-07/schema#",
346
+ "additionalProperties": false,
347
+ "properties": {
348
+ "numbers": {
349
+ "items": {
350
+ "type": "number",
351
+ },
352
+ "type": "array",
353
+ },
354
+ "tags": {
355
+ "items": {
356
+ "type": "string",
357
+ },
358
+ "type": "array",
359
+ },
360
+ },
361
+ "required": [
362
+ "tags",
363
+ "numbers",
364
+ ],
365
+ "type": "object",
366
+ }
367
+ `;
368
+
369
+ exports[`Zod Utils > zodSchemaToJsonSchema > strict parameter > should handle default values in strict mode 1`] = `
370
+ {
371
+ "$schema": "http://json-schema.org/draft-07/schema#",
372
+ "additionalProperties": false,
373
+ "properties": {
374
+ "active": {
375
+ "default": true,
376
+ "type": "boolean",
377
+ },
378
+ "name": {
379
+ "type": "string",
380
+ },
381
+ "role": {
382
+ "default": "user",
383
+ "type": "string",
384
+ },
385
+ },
386
+ "required": [
387
+ "name",
388
+ "role",
389
+ "active",
390
+ ],
391
+ "type": "object",
392
+ }
393
+ `;
394
+
395
+ exports[`Zod Utils > zodSchemaToJsonSchema > strict parameter > should handle nested objects in strict mode 1`] = `
396
+ {
397
+ "$schema": "http://json-schema.org/draft-07/schema#",
398
+ "additionalProperties": false,
399
+ "properties": {
400
+ "metadata": {
401
+ "additionalProperties": false,
402
+ "properties": {
403
+ "created": {
404
+ "type": "string",
405
+ },
406
+ },
407
+ "required": [
408
+ "created",
409
+ ],
410
+ "type": "object",
411
+ },
412
+ "user": {
413
+ "additionalProperties": false,
414
+ "properties": {
415
+ "email": {
416
+ "anyOf": [
417
+ {
418
+ "type": "string",
419
+ },
420
+ {
421
+ "type": "null",
422
+ },
423
+ ],
424
+ },
425
+ "name": {
426
+ "type": "string",
427
+ },
428
+ },
429
+ "required": [
430
+ "name",
431
+ "email",
432
+ ],
433
+ "type": "object",
434
+ },
435
+ },
436
+ "required": [
437
+ "user",
438
+ "metadata",
439
+ ],
440
+ "type": "object",
441
+ }
442
+ `;
443
+
444
+ exports[`Zod Utils > zodSchemaToJsonSchema > strict parameter > should handle nullable fields in strict mode 1`] = `
445
+ {
446
+ "$schema": "http://json-schema.org/draft-07/schema#",
447
+ "additionalProperties": false,
448
+ "properties": {
449
+ "optional": {
450
+ "anyOf": [
451
+ {
452
+ "type": "string",
453
+ },
454
+ {
455
+ "type": "null",
456
+ },
457
+ ],
458
+ },
459
+ "required": {
460
+ "type": "string",
461
+ },
462
+ },
463
+ "required": [
464
+ "required",
465
+ "optional",
466
+ ],
467
+ "type": "object",
468
+ }
469
+ `;
470
+
471
+ exports[`Zod Utils > zodSchemaToJsonSchema > strict parameter > should handle optional fields in strict mode 1`] = `
472
+ {
473
+ "$schema": "http://json-schema.org/draft-07/schema#",
474
+ "additionalProperties": false,
475
+ "properties": {
476
+ "optional": {
477
+ "anyOf": [
478
+ {
479
+ "type": "string",
480
+ },
481
+ {
482
+ "type": "null",
483
+ },
484
+ ],
485
+ },
486
+ "required": {
487
+ "type": "string",
488
+ },
489
+ },
490
+ "required": [
491
+ "required",
492
+ "optional",
493
+ ],
494
+ "type": "object",
495
+ }
496
+ `;
497
+
498
+ exports[`Zod Utils > zodSchemaToJsonSchema > strict parameter > should handle v3 schemas in strict mode 1`] = `
499
+ {
500
+ "$schema": "https://json-schema.org/draft/2019-09/schema#",
501
+ "additionalProperties": false,
502
+ "properties": {
503
+ "age": {
504
+ "type": [
505
+ "number",
506
+ "null",
507
+ ],
508
+ },
509
+ "name": {
510
+ "type": "string",
511
+ },
512
+ },
513
+ "required": [
514
+ "name",
515
+ "age",
516
+ ],
517
+ "type": "object",
518
+ }
519
+ `;
520
+
521
+ exports[`Zod Utils > zodSchemaToJsonSchema > strict parameter > should produce standard JSON schema with strict: false 1`] = `
522
+ {
523
+ "$schema": "http://json-schema.org/draft-07/schema#",
524
+ "additionalProperties": false,
525
+ "properties": {
526
+ "age": {
527
+ "type": "number",
528
+ },
529
+ "name": {
530
+ "type": "string",
531
+ },
532
+ },
533
+ "required": [
534
+ "name",
535
+ "age",
536
+ ],
537
+ "type": "object",
538
+ }
539
+ `;
540
+
541
+ exports[`Zod Utils > zodSchemaToJsonSchema > strict parameter > should produce strict JSON schema with strict: true 1`] = `
542
+ {
543
+ "$schema": "http://json-schema.org/draft-07/schema#",
544
+ "additionalProperties": false,
545
+ "properties": {
546
+ "age": {
547
+ "type": "number",
548
+ },
549
+ "name": {
550
+ "type": "string",
551
+ },
552
+ },
553
+ "required": [
554
+ "name",
555
+ "age",
556
+ ],
557
+ "type": "object",
558
+ }
559
+ `;
@@ -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
@@ -78,7 +78,7 @@ export abstract class LLM extends (EventEmitter as new () => TypedEmitter<LLMCal
78
78
  connOptions?: APIConnectOptions;
79
79
  parallelToolCalls?: boolean;
80
80
  toolChoice?: ToolChoice;
81
- extraKwargs?: Record<string, any>;
81
+ extraKwargs?: Record<string, unknown>;
82
82
  }): LLMStream;
83
83
 
84
84
  /**
@@ -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) {