@livekit/agents 1.0.21 → 1.0.23

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 (149) hide show
  1. package/dist/inference/api_protos.cjs +2 -2
  2. package/dist/inference/api_protos.cjs.map +1 -1
  3. package/dist/inference/api_protos.d.cts +16 -16
  4. package/dist/inference/api_protos.d.ts +16 -16
  5. package/dist/inference/api_protos.js +2 -2
  6. package/dist/inference/api_protos.js.map +1 -1
  7. package/dist/inference/stt.cjs +42 -30
  8. package/dist/inference/stt.cjs.map +1 -1
  9. package/dist/inference/stt.d.ts.map +1 -1
  10. package/dist/inference/stt.js +42 -30
  11. package/dist/inference/stt.js.map +1 -1
  12. package/dist/inference/tts.cjs +2 -3
  13. package/dist/inference/tts.cjs.map +1 -1
  14. package/dist/inference/tts.d.ts.map +1 -1
  15. package/dist/inference/tts.js +2 -3
  16. package/dist/inference/tts.js.map +1 -1
  17. package/dist/ipc/job_proc_lazy_main.cjs +35 -1
  18. package/dist/ipc/job_proc_lazy_main.cjs.map +1 -1
  19. package/dist/ipc/job_proc_lazy_main.js +13 -1
  20. package/dist/ipc/job_proc_lazy_main.js.map +1 -1
  21. package/dist/job.cjs +52 -6
  22. package/dist/job.cjs.map +1 -1
  23. package/dist/job.d.cts +2 -0
  24. package/dist/job.d.ts +2 -0
  25. package/dist/job.d.ts.map +1 -1
  26. package/dist/job.js +52 -6
  27. package/dist/job.js.map +1 -1
  28. package/dist/llm/llm.cjs +38 -3
  29. package/dist/llm/llm.cjs.map +1 -1
  30. package/dist/llm/llm.d.cts +1 -0
  31. package/dist/llm/llm.d.ts +1 -0
  32. package/dist/llm/llm.d.ts.map +1 -1
  33. package/dist/llm/llm.js +38 -3
  34. package/dist/llm/llm.js.map +1 -1
  35. package/dist/log.cjs +34 -10
  36. package/dist/log.cjs.map +1 -1
  37. package/dist/log.d.cts +7 -0
  38. package/dist/log.d.ts +7 -0
  39. package/dist/log.d.ts.map +1 -1
  40. package/dist/log.js +34 -11
  41. package/dist/log.js.map +1 -1
  42. package/dist/stt/stt.cjs +18 -5
  43. package/dist/stt/stt.cjs.map +1 -1
  44. package/dist/stt/stt.d.ts.map +1 -1
  45. package/dist/stt/stt.js +18 -5
  46. package/dist/stt/stt.js.map +1 -1
  47. package/dist/telemetry/index.cjs +23 -2
  48. package/dist/telemetry/index.cjs.map +1 -1
  49. package/dist/telemetry/index.d.cts +4 -1
  50. package/dist/telemetry/index.d.ts +4 -1
  51. package/dist/telemetry/index.d.ts.map +1 -1
  52. package/dist/telemetry/index.js +27 -2
  53. package/dist/telemetry/index.js.map +1 -1
  54. package/dist/telemetry/logging.cjs +65 -0
  55. package/dist/telemetry/logging.cjs.map +1 -0
  56. package/dist/telemetry/logging.d.cts +21 -0
  57. package/dist/telemetry/logging.d.ts +21 -0
  58. package/dist/telemetry/logging.d.ts.map +1 -0
  59. package/dist/telemetry/logging.js +40 -0
  60. package/dist/telemetry/logging.js.map +1 -0
  61. package/dist/telemetry/otel_http_exporter.cjs +144 -0
  62. package/dist/telemetry/otel_http_exporter.cjs.map +1 -0
  63. package/dist/telemetry/otel_http_exporter.d.cts +62 -0
  64. package/dist/telemetry/otel_http_exporter.d.ts +62 -0
  65. package/dist/telemetry/otel_http_exporter.d.ts.map +1 -0
  66. package/dist/telemetry/otel_http_exporter.js +120 -0
  67. package/dist/telemetry/otel_http_exporter.js.map +1 -0
  68. package/dist/telemetry/pino_otel_transport.cjs +217 -0
  69. package/dist/telemetry/pino_otel_transport.cjs.map +1 -0
  70. package/dist/telemetry/pino_otel_transport.d.cts +58 -0
  71. package/dist/telemetry/pino_otel_transport.d.ts +58 -0
  72. package/dist/telemetry/pino_otel_transport.d.ts.map +1 -0
  73. package/dist/telemetry/pino_otel_transport.js +189 -0
  74. package/dist/telemetry/pino_otel_transport.js.map +1 -0
  75. package/dist/telemetry/traces.cjs +225 -16
  76. package/dist/telemetry/traces.cjs.map +1 -1
  77. package/dist/telemetry/traces.d.cts +17 -0
  78. package/dist/telemetry/traces.d.ts +17 -0
  79. package/dist/telemetry/traces.d.ts.map +1 -1
  80. package/dist/telemetry/traces.js +211 -14
  81. package/dist/telemetry/traces.js.map +1 -1
  82. package/dist/tts/tts.cjs +68 -20
  83. package/dist/tts/tts.cjs.map +1 -1
  84. package/dist/tts/tts.d.cts +2 -0
  85. package/dist/tts/tts.d.ts +2 -0
  86. package/dist/tts/tts.d.ts.map +1 -1
  87. package/dist/tts/tts.js +68 -20
  88. package/dist/tts/tts.js.map +1 -1
  89. package/dist/utils.cjs +6 -0
  90. package/dist/utils.cjs.map +1 -1
  91. package/dist/utils.d.cts +1 -0
  92. package/dist/utils.d.ts +1 -0
  93. package/dist/utils.d.ts.map +1 -1
  94. package/dist/utils.js +5 -0
  95. package/dist/utils.js.map +1 -1
  96. package/dist/voice/agent_activity.cjs +93 -7
  97. package/dist/voice/agent_activity.cjs.map +1 -1
  98. package/dist/voice/agent_activity.d.cts +3 -0
  99. package/dist/voice/agent_activity.d.ts +3 -0
  100. package/dist/voice/agent_activity.d.ts.map +1 -1
  101. package/dist/voice/agent_activity.js +93 -7
  102. package/dist/voice/agent_activity.js.map +1 -1
  103. package/dist/voice/agent_session.cjs +122 -27
  104. package/dist/voice/agent_session.cjs.map +1 -1
  105. package/dist/voice/agent_session.d.cts +15 -0
  106. package/dist/voice/agent_session.d.ts +15 -0
  107. package/dist/voice/agent_session.d.ts.map +1 -1
  108. package/dist/voice/agent_session.js +122 -27
  109. package/dist/voice/agent_session.js.map +1 -1
  110. package/dist/voice/audio_recognition.cjs +69 -22
  111. package/dist/voice/audio_recognition.cjs.map +1 -1
  112. package/dist/voice/audio_recognition.d.cts +5 -0
  113. package/dist/voice/audio_recognition.d.ts +5 -0
  114. package/dist/voice/audio_recognition.d.ts.map +1 -1
  115. package/dist/voice/audio_recognition.js +69 -22
  116. package/dist/voice/audio_recognition.js.map +1 -1
  117. package/dist/voice/generation.cjs +43 -3
  118. package/dist/voice/generation.cjs.map +1 -1
  119. package/dist/voice/generation.d.ts.map +1 -1
  120. package/dist/voice/generation.js +43 -3
  121. package/dist/voice/generation.js.map +1 -1
  122. package/dist/voice/report.cjs +3 -2
  123. package/dist/voice/report.cjs.map +1 -1
  124. package/dist/voice/report.d.cts +7 -1
  125. package/dist/voice/report.d.ts +7 -1
  126. package/dist/voice/report.d.ts.map +1 -1
  127. package/dist/voice/report.js +3 -2
  128. package/dist/voice/report.js.map +1 -1
  129. package/package.json +8 -2
  130. package/src/inference/api_protos.ts +2 -2
  131. package/src/inference/stt.ts +48 -33
  132. package/src/inference/tts.ts +4 -3
  133. package/src/ipc/job_proc_lazy_main.ts +12 -1
  134. package/src/job.ts +59 -10
  135. package/src/llm/llm.ts +48 -5
  136. package/src/log.ts +52 -15
  137. package/src/stt/stt.ts +18 -5
  138. package/src/telemetry/index.ts +22 -4
  139. package/src/telemetry/logging.ts +55 -0
  140. package/src/telemetry/otel_http_exporter.ts +191 -0
  141. package/src/telemetry/pino_otel_transport.ts +265 -0
  142. package/src/telemetry/traces.ts +320 -20
  143. package/src/tts/tts.ts +85 -24
  144. package/src/utils.ts +5 -0
  145. package/src/voice/agent_activity.ts +140 -22
  146. package/src/voice/agent_session.ts +174 -34
  147. package/src/voice/audio_recognition.ts +85 -26
  148. package/src/voice/generation.ts +59 -7
  149. package/src/voice/report.ts +10 -4
package/src/tts/tts.ts CHANGED
@@ -3,12 +3,14 @@
3
3
  // SPDX-License-Identifier: Apache-2.0
4
4
  import type { AudioFrame } from '@livekit/rtc-node';
5
5
  import type { TypedEventEmitter as TypedEmitter } from '@livekit/typed-emitter';
6
+ import type { Span } from '@opentelemetry/api';
6
7
  import { EventEmitter } from 'node:events';
7
8
  import type { ReadableStream } from 'node:stream/web';
8
9
  import { APIConnectionError, APIError } from '../_exceptions.js';
9
10
  import { log } from '../log.js';
10
11
  import type { TTSMetrics } from '../metrics/base.js';
11
12
  import { DeferredReadableStream } from '../stream/deferred_stream.js';
13
+ import { recordException, traceTypes, tracer } from '../telemetry/index.js';
12
14
  import { type APIConnectOptions, DEFAULT_API_CONNECT_OPTIONS } from '../types.js';
13
15
  import { AsyncIterableQueue, delay, mergeFrames, startSoon, toError } from '../utils.js';
14
16
 
@@ -134,6 +136,7 @@ export abstract class SynthesizeStream
134
136
  #monitorMetricsTask?: Promise<void>;
135
137
  private _connOptions: APIConnectOptions;
136
138
  protected abortController = new AbortController();
139
+ #ttsRequestSpan?: Span;
137
140
 
138
141
  private deferredInputStream: DeferredReadableStream<
139
142
  string | typeof SynthesizeStream.FLUSH_SENTINEL
@@ -160,12 +163,27 @@ export abstract class SynthesizeStream
160
163
  startSoon(() => this.mainTask().then(() => this.queue.close()));
161
164
  }
162
165
 
163
- private async mainTask() {
164
- // TODO(brian): PR3 - Add span wrapping: tracer.startActiveSpan('tts_request', ..., { endOnExit: false })
166
+ private _mainTaskImpl = async (span: Span) => {
167
+ this.#ttsRequestSpan = span;
168
+ span.setAttributes({
169
+ [traceTypes.ATTR_TTS_STREAMING]: true,
170
+ [traceTypes.ATTR_TTS_LABEL]: this.#tts.label,
171
+ });
172
+
165
173
  for (let i = 0; i < this._connOptions.maxRetry + 1; i++) {
166
174
  try {
167
- // TODO(brian): PR3 - Add span for retry attempts: tracer.startActiveSpan('tts_request_run', ...)
168
- return await this.run();
175
+ return await tracer.startActiveSpan(
176
+ async (attemptSpan) => {
177
+ attemptSpan.setAttribute(traceTypes.ATTR_RETRY_COUNT, i);
178
+ try {
179
+ return await this.run();
180
+ } catch (error) {
181
+ recordException(attemptSpan, toError(error));
182
+ throw error;
183
+ }
184
+ },
185
+ { name: 'tts_request_run' },
186
+ );
169
187
  } catch (error) {
170
188
  if (error instanceof APIError) {
171
189
  const retryInterval = this._connOptions._intervalForRetry(i);
@@ -197,7 +215,13 @@ export abstract class SynthesizeStream
197
215
  }
198
216
  }
199
217
  }
200
- }
218
+ };
219
+
220
+ private mainTask = async () =>
221
+ tracer.startActiveSpan(async (span) => this._mainTaskImpl(span), {
222
+ name: 'tts_request',
223
+ endOnExit: false,
224
+ });
201
225
 
202
226
  private emitError({ error, recoverable }: { error: Error; recoverable: boolean }) {
203
227
  this.#tts.emit('error', {
@@ -265,6 +289,9 @@ export abstract class SynthesizeStream
265
289
  label: this.#tts.label,
266
290
  streamed: false,
267
291
  };
292
+ if (this.#ttsRequestSpan) {
293
+ this.#ttsRequestSpan.setAttribute(traceTypes.ATTR_TTS_METRICS, JSON.stringify(metrics));
294
+ }
268
295
  this.#tts.emit('metrics_collected', metrics);
269
296
  }
270
297
  };
@@ -289,6 +316,11 @@ export abstract class SynthesizeStream
289
316
  if (requestId) {
290
317
  emit();
291
318
  }
319
+
320
+ if (this.#ttsRequestSpan) {
321
+ this.#ttsRequestSpan.end();
322
+ this.#ttsRequestSpan = undefined;
323
+ }
292
324
  }
293
325
 
294
326
  protected abstract run(): Promise<void>;
@@ -307,12 +339,11 @@ export abstract class SynthesizeStream
307
339
  }
308
340
  this.#metricsText += text;
309
341
 
310
- if (this.input.closed) {
311
- throw new Error('Input is closed');
312
- }
313
- if (this.closed) {
314
- throw new Error('Stream is closed');
342
+ if (this.input.closed || this.closed) {
343
+ // Stream was aborted/closed, silently skip
344
+ return;
315
345
  }
346
+
316
347
  this.input.put(text);
317
348
  }
318
349
 
@@ -322,24 +353,24 @@ export abstract class SynthesizeStream
322
353
  this.#metricsPendingTexts.push(this.#metricsText);
323
354
  this.#metricsText = '';
324
355
  }
325
- if (this.input.closed) {
326
- throw new Error('Input is closed');
327
- }
328
- if (this.closed) {
329
- throw new Error('Stream is closed');
356
+
357
+ if (this.input.closed || this.closed) {
358
+ // Stream was aborted/closed, silently skip
359
+ return;
330
360
  }
361
+
331
362
  this.input.put(SynthesizeStream.FLUSH_SENTINEL);
332
363
  }
333
364
 
334
365
  /** Mark the input as ended and forbid additional pushes */
335
366
  endInput() {
336
367
  this.flush();
337
- if (this.input.closed) {
338
- throw new Error('Input is closed');
339
- }
340
- if (this.closed) {
341
- throw new Error('Stream is closed');
368
+
369
+ if (this.input.closed || this.closed) {
370
+ // Stream was aborted/closed, silently skip
371
+ return;
342
372
  }
373
+
343
374
  this.input.close();
344
375
  }
345
376
 
@@ -378,6 +409,7 @@ export abstract class ChunkedStream implements AsyncIterableIterator<Synthesized
378
409
  abstract label: string;
379
410
  #text: string;
380
411
  #tts: TTS;
412
+ #ttsRequestSpan?: Span;
381
413
  private _connOptions: APIConnectOptions;
382
414
  private logger = log();
383
415
 
@@ -399,12 +431,27 @@ export abstract class ChunkedStream implements AsyncIterableIterator<Synthesized
399
431
  Promise.resolve().then(() => this.mainTask().then(() => this.queue.close()));
400
432
  }
401
433
 
402
- private async mainTask() {
403
- // TODO(brian): PR3 - Add span wrapping: tracer.startActiveSpan('tts_request', ..., { endOnExit: false })
434
+ private _mainTaskImpl = async (span: Span) => {
435
+ this.#ttsRequestSpan = span;
436
+ span.setAttributes({
437
+ [traceTypes.ATTR_TTS_STREAMING]: false,
438
+ [traceTypes.ATTR_TTS_LABEL]: this.#tts.label,
439
+ });
440
+
404
441
  for (let i = 0; i < this._connOptions.maxRetry + 1; i++) {
405
442
  try {
406
- // TODO(brian): PR3 - Add span for retry attempts: tracer.startActiveSpan('tts_request_run', ...)
407
- return await this.run();
443
+ return await tracer.startActiveSpan(
444
+ async (attemptSpan) => {
445
+ attemptSpan.setAttribute(traceTypes.ATTR_RETRY_COUNT, i);
446
+ try {
447
+ return await this.run();
448
+ } catch (error) {
449
+ recordException(attemptSpan, toError(error));
450
+ throw error;
451
+ }
452
+ },
453
+ { name: 'tts_request_run' },
454
+ );
408
455
  } catch (error) {
409
456
  if (error instanceof APIError) {
410
457
  const retryInterval = this._connOptions._intervalForRetry(i);
@@ -436,6 +483,13 @@ export abstract class ChunkedStream implements AsyncIterableIterator<Synthesized
436
483
  }
437
484
  }
438
485
  }
486
+ };
487
+
488
+ private async mainTask() {
489
+ return tracer.startActiveSpan(async (span) => this._mainTaskImpl(span), {
490
+ name: 'tts_request',
491
+ endOnExit: false,
492
+ });
439
493
  }
440
494
 
441
495
  private emitError({ error, recoverable }: { error: Error; recoverable: boolean }) {
@@ -483,6 +537,13 @@ export abstract class ChunkedStream implements AsyncIterableIterator<Synthesized
483
537
  label: this.#tts.label,
484
538
  streamed: false,
485
539
  };
540
+
541
+ if (this.#ttsRequestSpan) {
542
+ this.#ttsRequestSpan.setAttribute(traceTypes.ATTR_TTS_METRICS, JSON.stringify(metrics));
543
+ this.#ttsRequestSpan.end();
544
+ this.#ttsRequestSpan = undefined;
545
+ }
546
+
486
547
  this.#tts.emit('metrics_collected', metrics);
487
548
  }
488
549
 
package/src/utils.ts CHANGED
@@ -839,3 +839,8 @@ export async function waitForAbort(signal: AbortSignal) {
839
839
  signal.addEventListener('abort', handler, { once: true });
840
840
  return await abortFuture.await;
841
841
  }
842
+
843
+ export const isCloud = (url: URL) => {
844
+ const hostname = url.hostname;
845
+ return hostname.endsWith('.livekit.cloud') || hostname.endsWith('.livekit.run');
846
+ };
@@ -3,6 +3,8 @@
3
3
  // SPDX-License-Identifier: Apache-2.0
4
4
  import { Mutex } from '@livekit/mutex';
5
5
  import type { AudioFrame } from '@livekit/rtc-node';
6
+ import type { Span } from '@opentelemetry/api';
7
+ import { ROOT_CONTEXT, trace } from '@opentelemetry/api';
6
8
  import { Heap } from 'heap-js';
7
9
  import { AsyncLocalStorage } from 'node:async_hooks';
8
10
  import { ReadableStream } from 'node:stream/web';
@@ -10,6 +12,7 @@ import { type ChatContext, ChatMessage } from '../llm/chat_context.js';
10
12
  import {
11
13
  type ChatItem,
12
14
  type FunctionCall,
15
+ type FunctionCallOutput,
13
16
  type GenerationCreatedEvent,
14
17
  type InputSpeechStartedEvent,
15
18
  type InputSpeechStoppedEvent,
@@ -34,6 +37,7 @@ import type {
34
37
  } from '../metrics/base.js';
35
38
  import { DeferredReadableStream } from '../stream/deferred_stream.js';
36
39
  import { STT, type STTError, type SpeechEvent } from '../stt/stt.js';
40
+ import { traceTypes, tracer } from '../telemetry/index.js';
37
41
  import { splitWords } from '../tokenize/basic/word.js';
38
42
  import { TTS, type TTSError } from '../tts/tts.js';
39
43
  import { Future, Task, cancelAndWait, waitFor } from '../utils.js';
@@ -70,7 +74,6 @@ import {
70
74
  } from './generation.js';
71
75
  import { SpeechHandle } from './speech_handle.js';
72
76
 
73
- // equivalent to Python's contextvars
74
77
  const speechHandleStorage = new AsyncLocalStorage<SpeechHandle>();
75
78
 
76
79
  interface PreemptiveGeneration {
@@ -202,10 +205,15 @@ export class AgentActivity implements RecognitionHooks {
202
205
  }
203
206
 
204
207
  async start(): Promise<void> {
205
- // TODO(brian): PR3 - Add span: startSpan = tracer.startSpan('start_agent_activity', { attributes: { 'lk.agent_label': this.agent.label } })
206
- // TODO(brian): PR3 - Wrap prewarm calls with trace.useSpan(startSpan, endOnExit: false)
207
208
  const unlock = await this.lock.lock();
208
209
  try {
210
+ // Create start_agent_activity as a ROOT span (new trace) to match Python behavior
211
+ const startSpan = tracer.startSpan({
212
+ name: 'start_agent_activity',
213
+ attributes: { [traceTypes.ATTR_AGENT_LABEL]: this.agent.id },
214
+ context: ROOT_CONTEXT,
215
+ });
216
+
209
217
  this.agent._agentActivity = this;
210
218
 
211
219
  if (this.llm instanceof RealtimeModel) {
@@ -286,16 +294,26 @@ export class AgentActivity implements RecognitionHooks {
286
294
  turnDetectionMode: this.turnDetectionMode,
287
295
  minEndpointingDelay: this.agentSession.options.minEndpointingDelay,
288
296
  maxEndpointingDelay: this.agentSession.options.maxEndpointingDelay,
297
+ rootSpanContext: this.agentSession.rootSpanContext,
289
298
  });
290
299
  this.audioRecognition.start();
291
300
  this.started = true;
292
301
 
293
302
  this._mainTask = Task.from(({ signal }) => this.mainTask(signal));
294
- // TODO(brian): PR3 - Wrap onEnter with tracer.startActiveSpan('on_enter', { attributes: { 'lk.agent_label': this.agent.label }, context: startSpan context })
303
+
304
+ // Create on_enter as a child of start_agent_activity in the new trace
305
+ const onEnterTask = tracer.startActiveSpan(async () => this.agent.onEnter(), {
306
+ name: 'on_enter',
307
+ context: trace.setSpan(ROOT_CONTEXT, startSpan),
308
+ attributes: { [traceTypes.ATTR_AGENT_LABEL]: this.agent.id },
309
+ });
310
+
295
311
  this.createSpeechTask({
296
- task: Task.from(() => this.agent.onEnter()),
312
+ task: Task.from(() => onEnterTask),
297
313
  name: 'AgentActivity_onEnter',
298
314
  });
315
+
316
+ startSpan.end();
299
317
  } finally {
300
318
  unlock();
301
319
  }
@@ -577,7 +595,6 @@ export class AgentActivity implements RecognitionHooks {
577
595
  }
578
596
 
579
597
  if (this.draining) {
580
- // copied from python:
581
598
  // TODO(shubhra): should we "forward" this new turn to the next agent?
582
599
  this.logger.warn('skipping new realtime generation, the agent is draining');
583
600
  return;
@@ -783,7 +800,6 @@ export class AgentActivity implements RecognitionHooks {
783
800
  if (this.draining) {
784
801
  this.cancelPreemptiveGeneration();
785
802
  this.logger.warn({ user_input: info.newTranscript }, 'skipping user input, task is draining');
786
- // copied from python:
787
803
  // TODO(shubhra): should we "forward" this new turn to the next agent/activity?
788
804
  return true;
789
805
  }
@@ -1254,17 +1270,35 @@ export class AgentActivity implements RecognitionHooks {
1254
1270
  }
1255
1271
  }
1256
1272
 
1257
- // TODO(brian): PR3 - Wrap entire pipelineReplyTask() method with tracer.startActiveSpan('agent_turn')
1258
- private async pipelineReplyTask(
1259
- speechHandle: SpeechHandle,
1260
- chatCtx: ChatContext,
1261
- toolCtx: ToolContext,
1262
- modelSettings: ModelSettings,
1263
- replyAbortController: AbortController,
1264
- instructions?: string,
1265
- newMessage?: ChatMessage,
1266
- toolsMessages?: ChatItem[],
1267
- ): Promise<void> {
1273
+ private _pipelineReplyTaskImpl = async ({
1274
+ speechHandle,
1275
+ chatCtx,
1276
+ toolCtx,
1277
+ modelSettings,
1278
+ replyAbortController,
1279
+ instructions,
1280
+ newMessage,
1281
+ toolsMessages,
1282
+ span,
1283
+ }: {
1284
+ speechHandle: SpeechHandle;
1285
+ chatCtx: ChatContext;
1286
+ toolCtx: ToolContext;
1287
+ modelSettings: ModelSettings;
1288
+ replyAbortController: AbortController;
1289
+ instructions?: string;
1290
+ newMessage?: ChatMessage;
1291
+ toolsMessages?: ChatItem[];
1292
+ span: Span;
1293
+ }): Promise<void> => {
1294
+ span.setAttribute(traceTypes.ATTR_SPEECH_ID, speechHandle.id);
1295
+ if (instructions) {
1296
+ span.setAttribute(traceTypes.ATTR_INSTRUCTIONS, instructions);
1297
+ }
1298
+ if (newMessage) {
1299
+ span.setAttribute(traceTypes.ATTR_USER_INPUT, newMessage.textContent || '');
1300
+ }
1301
+
1268
1302
  speechHandleStorage.enterWith(speechHandle);
1269
1303
 
1270
1304
  const audioOutput = this.agentSession.output.audioEnabled
@@ -1406,6 +1440,8 @@ export class AgentActivity implements RecognitionHooks {
1406
1440
  msg.createdAt = replyStartedAt;
1407
1441
  }
1408
1442
  this.agent._chatCtx.insert(toolsMessages);
1443
+ // Also add to session history (matches Python agent_session.py _tool_items_added)
1444
+ this.agentSession._toolItemsAdded(toolsMessages as (FunctionCall | FunctionCallOutput)[]);
1409
1445
  }
1410
1446
 
1411
1447
  if (speechHandle.interrupted) {
@@ -1601,8 +1637,38 @@ export class AgentActivity implements RecognitionHooks {
1601
1637
  msg.createdAt = replyStartedAt;
1602
1638
  }
1603
1639
  this.agent._chatCtx.insert(toolMessages);
1640
+ this.agentSession._toolItemsAdded(toolMessages as (FunctionCall | FunctionCallOutput)[]);
1604
1641
  }
1605
- }
1642
+ };
1643
+
1644
+ private pipelineReplyTask = async (
1645
+ speechHandle: SpeechHandle,
1646
+ chatCtx: ChatContext,
1647
+ toolCtx: ToolContext,
1648
+ modelSettings: ModelSettings,
1649
+ replyAbortController: AbortController,
1650
+ instructions?: string,
1651
+ newMessage?: ChatMessage,
1652
+ toolsMessages?: ChatItem[],
1653
+ ): Promise<void> =>
1654
+ tracer.startActiveSpan(
1655
+ async (span) =>
1656
+ this._pipelineReplyTaskImpl({
1657
+ speechHandle,
1658
+ chatCtx,
1659
+ toolCtx,
1660
+ modelSettings,
1661
+ replyAbortController,
1662
+ instructions,
1663
+ newMessage,
1664
+ toolsMessages,
1665
+ span,
1666
+ }),
1667
+ {
1668
+ name: 'agent_turn',
1669
+ context: this.agentSession.rootSpanContext,
1670
+ },
1671
+ );
1606
1672
 
1607
1673
  private async realtimeGenerationTask(
1608
1674
  speechHandle: SpeechHandle,
@@ -1610,6 +1676,37 @@ export class AgentActivity implements RecognitionHooks {
1610
1676
  modelSettings: ModelSettings,
1611
1677
  replyAbortController: AbortController,
1612
1678
  ): Promise<void> {
1679
+ return tracer.startActiveSpan(
1680
+ async (span) =>
1681
+ this._realtimeGenerationTaskImpl({
1682
+ speechHandle,
1683
+ ev,
1684
+ modelSettings,
1685
+ replyAbortController,
1686
+ span,
1687
+ }),
1688
+ {
1689
+ name: 'agent_turn',
1690
+ context: this.agentSession.rootSpanContext,
1691
+ },
1692
+ );
1693
+ }
1694
+
1695
+ private async _realtimeGenerationTaskImpl({
1696
+ speechHandle,
1697
+ ev,
1698
+ modelSettings,
1699
+ replyAbortController,
1700
+ span,
1701
+ }: {
1702
+ speechHandle: SpeechHandle;
1703
+ ev: GenerationCreatedEvent;
1704
+ modelSettings: ModelSettings;
1705
+ replyAbortController: AbortController;
1706
+ span: Span;
1707
+ }): Promise<void> {
1708
+ span.setAttribute(traceTypes.ATTR_SPEECH_ID, speechHandle.id);
1709
+
1613
1710
  speechHandleStorage.enterWith(speechHandle);
1614
1711
 
1615
1712
  if (!this.realtimeSession) {
@@ -1786,6 +1883,8 @@ export class AgentActivity implements RecognitionHooks {
1786
1883
 
1787
1884
  const onToolExecutionStarted = (f: FunctionCall) => {
1788
1885
  speechHandle._itemAdded([f]);
1886
+ this.agent._chatCtx.items.push(f);
1887
+ this.agentSession._toolItemsAdded([f]);
1789
1888
  };
1790
1889
 
1791
1890
  const onToolExecutionCompleted = (out: ToolExecutionOutput) => {
@@ -1979,6 +2078,11 @@ export class AgentActivity implements RecognitionHooks {
1979
2078
  }
1980
2079
  const chatCtx = this.realtimeSession.chatCtx.copy();
1981
2080
  chatCtx.items.push(...functionToolsExecutedEvent.functionCallOutputs);
2081
+
2082
+ this.agentSession._toolItemsAdded(
2083
+ functionToolsExecutedEvent.functionCallOutputs as FunctionCallOutput[],
2084
+ );
2085
+
1982
2086
  try {
1983
2087
  await this.realtimeSession.updateChatCtx(chatCtx);
1984
2088
  } catch (error) {
@@ -2096,16 +2200,30 @@ export class AgentActivity implements RecognitionHooks {
2096
2200
  this.wakeupMainTask();
2097
2201
  }
2098
2202
 
2099
- // TODO(brian): PR3 - Wrap entire drain() method with tracer.startActiveSpan('drain_agent_activity', { attributes: { 'lk.agent_label': this.agent.label } })
2100
2203
  async drain(): Promise<void> {
2204
+ // Create drain_agent_activity as a ROOT span (new trace) to match Python behavior
2205
+ return tracer.startActiveSpan(async (span) => this._drainImpl(span), {
2206
+ name: 'drain_agent_activity',
2207
+ context: ROOT_CONTEXT,
2208
+ });
2209
+ }
2210
+
2211
+ private async _drainImpl(span: Span): Promise<void> {
2212
+ span.setAttribute(traceTypes.ATTR_AGENT_LABEL, this.agent.id);
2213
+
2101
2214
  const unlock = await this.lock.lock();
2102
2215
  try {
2103
2216
  if (this._draining) return;
2104
2217
 
2105
2218
  this.cancelPreemptiveGeneration();
2106
- // TODO(brian): PR3 - Wrap onExit with tracer.startActiveSpan('on_exit', { attributes: { 'lk.agent_label': this.agent.label } })
2219
+
2220
+ const onExitTask = tracer.startActiveSpan(async () => this.agent.onExit(), {
2221
+ name: 'on_exit',
2222
+ attributes: { [traceTypes.ATTR_AGENT_LABEL]: this.agent.id },
2223
+ });
2224
+
2107
2225
  this.createSpeechTask({
2108
- task: Task.from(() => this.agent.onExit()),
2226
+ task: Task.from(() => onExitTask),
2109
2227
  name: 'AgentActivity_onExit',
2110
2228
  });
2111
2229