@livekit/agents 1.0.47 → 1.0.48

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 (112) hide show
  1. package/dist/beta/index.cjs +29 -0
  2. package/dist/beta/index.cjs.map +1 -0
  3. package/dist/beta/index.d.cts +2 -0
  4. package/dist/beta/index.d.ts +2 -0
  5. package/dist/beta/index.d.ts.map +1 -0
  6. package/dist/beta/index.js +7 -0
  7. package/dist/beta/index.js.map +1 -0
  8. package/dist/beta/workflows/index.cjs +29 -0
  9. package/dist/beta/workflows/index.cjs.map +1 -0
  10. package/dist/beta/workflows/index.d.cts +2 -0
  11. package/dist/beta/workflows/index.d.ts +2 -0
  12. package/dist/beta/workflows/index.d.ts.map +1 -0
  13. package/dist/beta/workflows/index.js +7 -0
  14. package/dist/beta/workflows/index.js.map +1 -0
  15. package/dist/beta/workflows/task_group.cjs +162 -0
  16. package/dist/beta/workflows/task_group.cjs.map +1 -0
  17. package/dist/beta/workflows/task_group.d.cts +32 -0
  18. package/dist/beta/workflows/task_group.d.ts +32 -0
  19. package/dist/beta/workflows/task_group.d.ts.map +1 -0
  20. package/dist/beta/workflows/task_group.js +138 -0
  21. package/dist/beta/workflows/task_group.js.map +1 -0
  22. package/dist/index.cjs +3 -0
  23. package/dist/index.cjs.map +1 -1
  24. package/dist/index.d.cts +2 -1
  25. package/dist/index.d.ts +2 -1
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +2 -0
  28. package/dist/index.js.map +1 -1
  29. package/dist/inference/api_protos.d.cts +59 -59
  30. package/dist/inference/api_protos.d.ts +59 -59
  31. package/dist/llm/chat_context.cjs +89 -1
  32. package/dist/llm/chat_context.cjs.map +1 -1
  33. package/dist/llm/chat_context.d.cts +10 -1
  34. package/dist/llm/chat_context.d.ts +10 -1
  35. package/dist/llm/chat_context.d.ts.map +1 -1
  36. package/dist/llm/chat_context.js +89 -1
  37. package/dist/llm/chat_context.js.map +1 -1
  38. package/dist/llm/chat_context.test.cjs +43 -0
  39. package/dist/llm/chat_context.test.cjs.map +1 -1
  40. package/dist/llm/chat_context.test.js +43 -0
  41. package/dist/llm/chat_context.test.js.map +1 -1
  42. package/dist/llm/index.cjs +2 -0
  43. package/dist/llm/index.cjs.map +1 -1
  44. package/dist/llm/index.d.cts +1 -1
  45. package/dist/llm/index.d.ts +1 -1
  46. package/dist/llm/index.d.ts.map +1 -1
  47. package/dist/llm/index.js +3 -1
  48. package/dist/llm/index.js.map +1 -1
  49. package/dist/llm/provider_format/index.d.cts +1 -1
  50. package/dist/llm/provider_format/index.d.ts +1 -1
  51. package/dist/llm/tool_context.cjs +7 -0
  52. package/dist/llm/tool_context.cjs.map +1 -1
  53. package/dist/llm/tool_context.d.cts +10 -2
  54. package/dist/llm/tool_context.d.ts +10 -2
  55. package/dist/llm/tool_context.d.ts.map +1 -1
  56. package/dist/llm/tool_context.js +6 -0
  57. package/dist/llm/tool_context.js.map +1 -1
  58. package/dist/utils.cjs +1 -0
  59. package/dist/utils.cjs.map +1 -1
  60. package/dist/utils.d.ts.map +1 -1
  61. package/dist/utils.js +1 -0
  62. package/dist/utils.js.map +1 -1
  63. package/dist/version.cjs +1 -1
  64. package/dist/version.js +1 -1
  65. package/dist/voice/agent.cjs +9 -0
  66. package/dist/voice/agent.cjs.map +1 -1
  67. package/dist/voice/agent.d.cts +1 -0
  68. package/dist/voice/agent.d.ts +1 -0
  69. package/dist/voice/agent.d.ts.map +1 -1
  70. package/dist/voice/agent.js +9 -0
  71. package/dist/voice/agent.js.map +1 -1
  72. package/dist/voice/agent_activity.cjs +31 -8
  73. package/dist/voice/agent_activity.cjs.map +1 -1
  74. package/dist/voice/agent_activity.d.cts +7 -0
  75. package/dist/voice/agent_activity.d.ts +7 -0
  76. package/dist/voice/agent_activity.d.ts.map +1 -1
  77. package/dist/voice/agent_activity.js +31 -8
  78. package/dist/voice/agent_activity.js.map +1 -1
  79. package/dist/voice/room_io/room_io.cjs +11 -2
  80. package/dist/voice/room_io/room_io.cjs.map +1 -1
  81. package/dist/voice/room_io/room_io.d.ts.map +1 -1
  82. package/dist/voice/room_io/room_io.js +12 -3
  83. package/dist/voice/room_io/room_io.js.map +1 -1
  84. package/dist/voice/testing/fake_llm.cjs +127 -0
  85. package/dist/voice/testing/fake_llm.cjs.map +1 -0
  86. package/dist/voice/testing/fake_llm.d.cts +30 -0
  87. package/dist/voice/testing/fake_llm.d.ts +30 -0
  88. package/dist/voice/testing/fake_llm.d.ts.map +1 -0
  89. package/dist/voice/testing/fake_llm.js +103 -0
  90. package/dist/voice/testing/fake_llm.js.map +1 -0
  91. package/dist/voice/testing/index.cjs +3 -0
  92. package/dist/voice/testing/index.cjs.map +1 -1
  93. package/dist/voice/testing/index.d.cts +1 -0
  94. package/dist/voice/testing/index.d.ts +1 -0
  95. package/dist/voice/testing/index.d.ts.map +1 -1
  96. package/dist/voice/testing/index.js +2 -0
  97. package/dist/voice/testing/index.js.map +1 -1
  98. package/package.json +1 -1
  99. package/src/beta/index.ts +9 -0
  100. package/src/beta/workflows/index.ts +9 -0
  101. package/src/beta/workflows/task_group.ts +194 -0
  102. package/src/index.ts +2 -1
  103. package/src/llm/chat_context.test.ts +48 -0
  104. package/src/llm/chat_context.ts +123 -0
  105. package/src/llm/index.ts +1 -0
  106. package/src/llm/tool_context.ts +14 -0
  107. package/src/utils.ts +5 -0
  108. package/src/voice/agent.ts +11 -0
  109. package/src/voice/agent_activity.ts +44 -6
  110. package/src/voice/room_io/room_io.ts +14 -3
  111. package/src/voice/testing/fake_llm.ts +138 -0
  112. package/src/voice/testing/index.ts +2 -0
package/src/utils.ts CHANGED
@@ -173,6 +173,11 @@ export class Future<T = void> {
173
173
  this.#rejected = true;
174
174
  this.#error = error;
175
175
  this.#rejectPromise(error);
176
+ // Python calls Future.exception() right after set_exception() to silence
177
+ // "exception was never retrieved" warnings. In JS, consume the rejection
178
+ // immediately so Node does not emit unhandled-rejection noise before a
179
+ // later await/catch observes it.
180
+ void this.#await.catch(() => undefined);
176
181
  }
177
182
  }
178
183
 
@@ -302,6 +302,17 @@ export class Agent<UserData = any> {
302
302
  this._agentActivity.updateChatCtx(chatCtx);
303
303
  }
304
304
 
305
+ // TODO(parity): Add when AgentConfigUpdate is ported to ChatContext.
306
+ async updateTools(tools: ToolContext): Promise<void> {
307
+ if (!this._agentActivity) {
308
+ this._tools = { ...tools };
309
+ this._chatCtx = this._chatCtx.copy({ toolCtx: this._tools });
310
+ return;
311
+ }
312
+
313
+ await this._agentActivity.updateTools(tools);
314
+ }
315
+
305
316
  static default = {
306
317
  async sttNode(
307
318
  agent: Agent,
@@ -23,6 +23,7 @@ import {
23
23
  type RealtimeSession,
24
24
  type ToolChoice,
25
25
  type ToolContext,
26
+ ToolFlag,
26
27
  } from '../llm/index.js';
27
28
  import type { LLMError } from '../llm/llm.js';
28
29
  import { isSameToolChoice, isSameToolContext } from '../llm/tool_context.js';
@@ -83,6 +84,12 @@ import { SpeechHandle } from './speech_handle.js';
83
84
  import { setParticipantSpanAttributes } from './utils.js';
84
85
 
85
86
  export const agentActivityStorage = new AsyncLocalStorage<AgentActivity>();
87
+ export const onEnterStorage = new AsyncLocalStorage<OnEnterData>();
88
+
89
+ interface OnEnterData {
90
+ session: AgentSession;
91
+ agent: Agent;
92
+ }
86
93
 
87
94
  interface PreemptiveGeneration {
88
95
  speechHandle: SpeechHandle;
@@ -312,6 +319,8 @@ export class AgentActivity implements RecognitionHooks {
312
319
  }
313
320
  }
314
321
 
322
+ // TODO(parity): Record initial AgentConfigUpdate in chat context
323
+
315
324
  // metrics and error handling
316
325
  if (this.llm instanceof LLM) {
317
326
  this.llm.on('metrics_collected', this.onMetricsCollected);
@@ -354,11 +363,13 @@ export class AgentActivity implements RecognitionHooks {
354
363
  if (runOnEnter) {
355
364
  this._onEnterTask = this.createSpeechTask({
356
365
  taskFn: () =>
357
- tracer.startActiveSpan(async () => this.agent.onEnter(), {
358
- name: 'on_enter',
359
- context: trace.setSpan(ROOT_CONTEXT, startSpan),
360
- attributes: { [traceTypes.ATTR_AGENT_LABEL]: this.agent.id },
361
- }),
366
+ onEnterStorage.run({ session: this.agentSession, agent: this.agent }, () =>
367
+ tracer.startActiveSpan(async () => this.agent.onEnter(), {
368
+ name: 'on_enter',
369
+ context: trace.setSpan(ROOT_CONTEXT, startSpan),
370
+ attributes: { [traceTypes.ATTR_AGENT_LABEL]: this.agent.id },
371
+ }),
372
+ ),
362
373
  inlineTask: true,
363
374
  name: 'AgentActivity_onEnter',
364
375
  });
@@ -446,6 +457,20 @@ export class AgentActivity implements RecognitionHooks {
446
457
  }
447
458
  }
448
459
 
460
+ // TODO: Add when AgentConfigUpdate is ported to ChatContext.
461
+ async updateTools(tools: ToolContext): Promise<void> {
462
+ this.agent._tools = { ...tools };
463
+
464
+ if (this.realtimeSession) {
465
+ await this.realtimeSession.updateTools(tools);
466
+ }
467
+
468
+ if (this.llm instanceof LLM) {
469
+ // for realtime LLM, we assume the server will remove unvalid tool messages
470
+ await this.updateChatCtx(this.agent._chatCtx.copy({ toolCtx: tools }));
471
+ }
472
+ }
473
+
449
474
  updateOptions({ toolChoice }: { toolChoice?: ToolChoice | null }): void {
450
475
  if (toolChoice !== undefined) {
451
476
  this.toolChoice = toolChoice;
@@ -1129,12 +1154,25 @@ export class AgentActivity implements RecognitionHooks {
1129
1154
  instructions = `${this.agent.instructions}\n${instructions}`;
1130
1155
  }
1131
1156
 
1157
+ // Filter out tools with IGNORE_ON_ENTER flag when generateReply is called inside onEnter
1158
+ const onEnterData = onEnterStorage.getStore();
1159
+ const shouldFilterTools =
1160
+ onEnterData?.agent === this.agent && onEnterData?.session === this.agentSession;
1161
+
1162
+ const tools = shouldFilterTools
1163
+ ? Object.fromEntries(
1164
+ Object.entries(this.agent.toolCtx).filter(
1165
+ ([, fnTool]) => !(fnTool.flags & ToolFlag.IGNORE_ON_ENTER),
1166
+ ),
1167
+ )
1168
+ : this.agent.toolCtx;
1169
+
1132
1170
  const task = this.createSpeechTask({
1133
1171
  taskFn: (abortController: AbortController) =>
1134
1172
  this.pipelineReplyTask(
1135
1173
  handle,
1136
1174
  chatCtx ?? this.agent.chatCtx,
1137
- this.agent.toolCtx,
1175
+ tools,
1138
1176
  {
1139
1177
  toolChoice: toOaiToolChoice(toolChoice !== undefined ? toolChoice : this.toolChoice),
1140
1178
  },
@@ -21,7 +21,7 @@ import type { WritableStreamDefaultWriter } from 'node:stream/web';
21
21
  import { ATTRIBUTE_PUBLISH_ON_BEHALF, TOPIC_CHAT } from '../../constants.js';
22
22
  import { log } from '../../log.js';
23
23
  import { IdentityTransform } from '../../stream/identity_transform.js';
24
- import { Future, Task } from '../../utils.js';
24
+ import { Future, Task, waitForAbort } from '../../utils.js';
25
25
  import { type AgentSession } from '../agent_session.js';
26
26
  import {
27
27
  AgentSessionEventTypes,
@@ -177,7 +177,10 @@ export class RoomIO {
177
177
  : this.inputOptions.participantIdentity ?? null;
178
178
  }
179
179
  private async init(signal: AbortSignal): Promise<void> {
180
- await this.roomConnectedFuture.await;
180
+ await Promise.race([this.roomConnectedFuture.await, waitForAbort(signal)]);
181
+ if (signal.aborted) {
182
+ return;
183
+ }
181
184
 
182
185
  for (const participant of this.room.remoteParticipants.values()) {
183
186
  this.onParticipantConnected(participant);
@@ -186,7 +189,15 @@ export class RoomIO {
186
189
  return;
187
190
  }
188
191
 
189
- const participant = await this.participantAvailableFuture.await;
192
+ const participant = await Promise.race([
193
+ this.participantAvailableFuture.await,
194
+ waitForAbort(signal),
195
+ ]);
196
+
197
+ if (!participant) {
198
+ return;
199
+ }
200
+
190
201
  this.setParticipant(participant.identity);
191
202
 
192
203
  // init agent outputs
@@ -0,0 +1,138 @@
1
+ // SPDX-FileCopyrightText: 2026 LiveKit, Inc.
2
+ //
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ import type { ChatContext } from '../../llm/chat_context.js';
5
+ import { FunctionCall } from '../../llm/chat_context.js';
6
+ import { LLMStream as BaseLLMStream, LLM, type LLMStream } from '../../llm/llm.js';
7
+ import type { ToolChoice, ToolContext } from '../../llm/tool_context.js';
8
+ import { type APIConnectOptions, DEFAULT_API_CONNECT_OPTIONS } from '../../types.js';
9
+ import { delay } from '../../utils.js';
10
+
11
+ export interface FakeLLMResponse {
12
+ input: string;
13
+ type?: 'llm';
14
+ content?: string;
15
+ ttft?: number;
16
+ duration?: number;
17
+ toolCalls?: Array<{ name: string; args: Record<string, unknown> }>;
18
+ }
19
+
20
+ export class FakeLLM extends LLM {
21
+ private readonly responseMap = new Map<string, FakeLLMResponse>();
22
+
23
+ constructor(responses: FakeLLMResponse[] = []) {
24
+ super();
25
+ for (const response of responses) {
26
+ this.responseMap.set(response.input, {
27
+ type: 'llm',
28
+ ttft: 0,
29
+ duration: 0,
30
+ ...response,
31
+ });
32
+ }
33
+ }
34
+
35
+ label(): string {
36
+ return 'fake-llm';
37
+ }
38
+
39
+ chat({
40
+ chatCtx,
41
+ toolCtx,
42
+ connOptions = DEFAULT_API_CONNECT_OPTIONS,
43
+ }: {
44
+ chatCtx: ChatContext;
45
+ toolCtx?: ToolContext;
46
+ connOptions?: APIConnectOptions;
47
+ parallelToolCalls?: boolean;
48
+ toolChoice?: ToolChoice;
49
+ extraKwargs?: Record<string, unknown>;
50
+ }): LLMStream {
51
+ return new FakeLLMStream(this, {
52
+ chatCtx,
53
+ toolCtx,
54
+ connOptions,
55
+ });
56
+ }
57
+
58
+ lookup(input: string): FakeLLMResponse | undefined {
59
+ return this.responseMap.get(input);
60
+ }
61
+ }
62
+
63
+ class FakeLLMStream extends BaseLLMStream {
64
+ private readonly fake: FakeLLM;
65
+
66
+ constructor(
67
+ fake: FakeLLM,
68
+ params: { chatCtx: ChatContext; toolCtx?: ToolContext; connOptions: APIConnectOptions },
69
+ ) {
70
+ super(fake, params);
71
+ this.fake = fake;
72
+ }
73
+
74
+ protected async run(): Promise<void> {
75
+ const input = this.getInputText();
76
+ const decision = this.fake.lookup(input);
77
+ if (!decision) {
78
+ return;
79
+ }
80
+
81
+ const startedAt = Date.now();
82
+ if ((decision.ttft ?? 0) > 0) {
83
+ await delay(decision.ttft!);
84
+ }
85
+
86
+ const content = decision.content ?? '';
87
+ const chunkSize = 3;
88
+ for (let i = 0; i < content.length; i += chunkSize) {
89
+ this.queue.put({
90
+ id: 'fake',
91
+ delta: { role: 'assistant', content: content.slice(i, i + chunkSize) },
92
+ });
93
+ }
94
+
95
+ if (decision.toolCalls && decision.toolCalls.length > 0) {
96
+ const calls = decision.toolCalls.map((tc, index) =>
97
+ FunctionCall.create({
98
+ callId: `fake_call_${index}`,
99
+ name: tc.name,
100
+ args: JSON.stringify(tc.args),
101
+ }),
102
+ );
103
+ this.queue.put({
104
+ id: 'fake',
105
+ delta: { role: 'assistant', toolCalls: calls },
106
+ });
107
+ }
108
+
109
+ const elapsed = Date.now() - startedAt;
110
+ const waitMs = Math.max(0, (decision.duration ?? 0) - elapsed);
111
+ if (waitMs > 0) {
112
+ await delay(waitMs);
113
+ }
114
+ }
115
+
116
+ private getInputText(): string {
117
+ const items = this.chatCtx.items;
118
+ if (items.length === 0) {
119
+ throw new Error('No input text found');
120
+ }
121
+
122
+ for (const item of items) {
123
+ if (item.type === 'message' && item.role === 'system') {
124
+ const text = item.textContent ?? '';
125
+ const lines = text.split('\n');
126
+ const tail = lines[lines.length - 1] ?? '';
127
+ if (lines.length > 1 && tail.startsWith('instructions:')) {
128
+ return tail;
129
+ }
130
+ }
131
+ }
132
+
133
+ const last = items[items.length - 1]!;
134
+ if (last.type === 'message' && last.role === 'user') return last.textContent ?? '';
135
+ if (last.type === 'function_call_output') return last.output;
136
+ throw new Error('No input text found');
137
+ }
138
+ }
@@ -48,3 +48,5 @@ export {
48
48
  type MessageAssertOptions,
49
49
  type RunEvent,
50
50
  } from './types.js';
51
+
52
+ export { FakeLLM, type FakeLLMResponse } from './fake_llm.js';