@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
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/voice/testing/fake_llm.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { ChatContext } from '../../llm/chat_context.js';\nimport { FunctionCall } from '../../llm/chat_context.js';\nimport { LLMStream as BaseLLMStream, LLM, type LLMStream } from '../../llm/llm.js';\nimport type { ToolChoice, ToolContext } from '../../llm/tool_context.js';\nimport { type APIConnectOptions, DEFAULT_API_CONNECT_OPTIONS } from '../../types.js';\nimport { delay } from '../../utils.js';\n\nexport interface FakeLLMResponse {\n input: string;\n type?: 'llm';\n content?: string;\n ttft?: number;\n duration?: number;\n toolCalls?: Array<{ name: string; args: Record<string, unknown> }>;\n}\n\nexport class FakeLLM extends LLM {\n private readonly responseMap = new Map<string, FakeLLMResponse>();\n\n constructor(responses: FakeLLMResponse[] = []) {\n super();\n for (const response of responses) {\n this.responseMap.set(response.input, {\n type: 'llm',\n ttft: 0,\n duration: 0,\n ...response,\n });\n }\n }\n\n label(): string {\n return 'fake-llm';\n }\n\n chat({\n chatCtx,\n toolCtx,\n connOptions = DEFAULT_API_CONNECT_OPTIONS,\n }: {\n chatCtx: ChatContext;\n toolCtx?: ToolContext;\n connOptions?: APIConnectOptions;\n parallelToolCalls?: boolean;\n toolChoice?: ToolChoice;\n extraKwargs?: Record<string, unknown>;\n }): LLMStream {\n return new FakeLLMStream(this, {\n chatCtx,\n toolCtx,\n connOptions,\n });\n }\n\n lookup(input: string): FakeLLMResponse | undefined {\n return this.responseMap.get(input);\n }\n}\n\nclass FakeLLMStream extends BaseLLMStream {\n private readonly fake: FakeLLM;\n\n constructor(\n fake: FakeLLM,\n params: { chatCtx: ChatContext; toolCtx?: ToolContext; connOptions: APIConnectOptions },\n ) {\n super(fake, params);\n this.fake = fake;\n }\n\n protected async run(): Promise<void> {\n const input = this.getInputText();\n const decision = this.fake.lookup(input);\n if (!decision) {\n return;\n }\n\n const startedAt = Date.now();\n if ((decision.ttft ?? 0) > 0) {\n await delay(decision.ttft!);\n }\n\n const content = decision.content ?? '';\n const chunkSize = 3;\n for (let i = 0; i < content.length; i += chunkSize) {\n this.queue.put({\n id: 'fake',\n delta: { role: 'assistant', content: content.slice(i, i + chunkSize) },\n });\n }\n\n if (decision.toolCalls && decision.toolCalls.length > 0) {\n const calls = decision.toolCalls.map((tc, index) =>\n FunctionCall.create({\n callId: `fake_call_${index}`,\n name: tc.name,\n args: JSON.stringify(tc.args),\n }),\n );\n this.queue.put({\n id: 'fake',\n delta: { role: 'assistant', toolCalls: calls },\n });\n }\n\n const elapsed = Date.now() - startedAt;\n const waitMs = Math.max(0, (decision.duration ?? 0) - elapsed);\n if (waitMs > 0) {\n await delay(waitMs);\n }\n }\n\n private getInputText(): string {\n const items = this.chatCtx.items;\n if (items.length === 0) {\n throw new Error('No input text found');\n }\n\n for (const item of items) {\n if (item.type === 'message' && item.role === 'system') {\n const text = item.textContent ?? '';\n const lines = text.split('\\n');\n const tail = lines[lines.length - 1] ?? '';\n if (lines.length > 1 && tail.startsWith('instructions:')) {\n return tail;\n }\n }\n }\n\n const last = items[items.length - 1]!;\n if (last.type === 'message' && last.role === 'user') return last.textContent ?? '';\n if (last.type === 'function_call_output') return last.output;\n throw new Error('No input text found');\n }\n}\n"],"mappings":"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","names":[]}
@@ -22,6 +22,7 @@ __export(testing_exports, {
22
22
  AssertionError: () => import_run_result.AssertionError,
23
23
  EventAssert: () => import_run_result.EventAssert,
24
24
  EventRangeAssert: () => import_run_result.EventRangeAssert,
25
+ FakeLLM: () => import_fake_llm.FakeLLM,
25
26
  FunctionCallAssert: () => import_run_result.FunctionCallAssert,
26
27
  FunctionCallOutputAssert: () => import_run_result.FunctionCallOutputAssert,
27
28
  MessageAssert: () => import_run_result.MessageAssert,
@@ -35,12 +36,14 @@ __export(testing_exports, {
35
36
  module.exports = __toCommonJS(testing_exports);
36
37
  var import_run_result = require("./run_result.cjs");
37
38
  var import_types = require("./types.cjs");
39
+ var import_fake_llm = require("./fake_llm.cjs");
38
40
  // Annotate the CommonJS export names for ESM import in node:
39
41
  0 && (module.exports = {
40
42
  AgentHandoffAssert,
41
43
  AssertionError,
42
44
  EventAssert,
43
45
  EventRangeAssert,
46
+ FakeLLM,
44
47
  FunctionCallAssert,
45
48
  FunctionCallOutputAssert,
46
49
  MessageAssert,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/voice/testing/index.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Testing utilities for agent evaluation.\n *\n * @example\n * ```typescript\n * import { AgentSession, Agent, voice } from '@livekit/agents';\n *\n * const session = new AgentSession({ llm });\n * await session.start(agent);\n *\n * const result = await session.run({ userInput: 'Hello' });\n * result.expect.nextEvent().isMessage({ role: 'assistant' });\n * result.expect.noMoreEvents();\n * ```\n *\n * @packageDocumentation\n */\n\nexport {\n AgentHandoffAssert,\n AssertionError,\n EventAssert,\n EventRangeAssert,\n FunctionCallAssert,\n FunctionCallOutputAssert,\n MessageAssert,\n RunAssert,\n RunResult,\n} from './run_result.js';\n\nexport {\n isAgentHandoffEvent,\n isChatMessageEvent,\n isFunctionCallEvent,\n isFunctionCallOutputEvent,\n type AgentHandoffAssertOptions,\n type AgentHandoffEvent,\n type ChatMessageEvent,\n type EventType,\n type FunctionCallAssertOptions,\n type FunctionCallEvent,\n type FunctionCallOutputAssertOptions,\n type FunctionCallOutputEvent,\n type MessageAssertOptions,\n type RunEvent,\n} from './types.js';\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAsBA,wBAUO;AAEP,mBAeO;","names":[]}
1
+ {"version":3,"sources":["../../../src/voice/testing/index.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Testing utilities for agent evaluation.\n *\n * @example\n * ```typescript\n * import { AgentSession, Agent, voice } from '@livekit/agents';\n *\n * const session = new AgentSession({ llm });\n * await session.start(agent);\n *\n * const result = await session.run({ userInput: 'Hello' });\n * result.expect.nextEvent().isMessage({ role: 'assistant' });\n * result.expect.noMoreEvents();\n * ```\n *\n * @packageDocumentation\n */\n\nexport {\n AgentHandoffAssert,\n AssertionError,\n EventAssert,\n EventRangeAssert,\n FunctionCallAssert,\n FunctionCallOutputAssert,\n MessageAssert,\n RunAssert,\n RunResult,\n} from './run_result.js';\n\nexport {\n isAgentHandoffEvent,\n isChatMessageEvent,\n isFunctionCallEvent,\n isFunctionCallOutputEvent,\n type AgentHandoffAssertOptions,\n type AgentHandoffEvent,\n type ChatMessageEvent,\n type EventType,\n type FunctionCallAssertOptions,\n type FunctionCallEvent,\n type FunctionCallOutputAssertOptions,\n type FunctionCallOutputEvent,\n type MessageAssertOptions,\n type RunEvent,\n} from './types.js';\n\nexport { FakeLLM, type FakeLLMResponse } from './fake_llm.js';\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAsBA,wBAUO;AAEP,mBAeO;AAEP,sBAA8C;","names":[]}
@@ -17,4 +17,5 @@
17
17
  */
18
18
  export { AgentHandoffAssert, AssertionError, EventAssert, EventRangeAssert, FunctionCallAssert, FunctionCallOutputAssert, MessageAssert, RunAssert, RunResult, } from './run_result.js';
19
19
  export { isAgentHandoffEvent, isChatMessageEvent, isFunctionCallEvent, isFunctionCallOutputEvent, type AgentHandoffAssertOptions, type AgentHandoffEvent, type ChatMessageEvent, type EventType, type FunctionCallAssertOptions, type FunctionCallEvent, type FunctionCallOutputAssertOptions, type FunctionCallOutputEvent, type MessageAssertOptions, type RunEvent, } from './types.js';
20
+ export { FakeLLM, type FakeLLMResponse } from './fake_llm.js';
20
21
  //# sourceMappingURL=index.d.ts.map
@@ -17,4 +17,5 @@
17
17
  */
18
18
  export { AgentHandoffAssert, AssertionError, EventAssert, EventRangeAssert, FunctionCallAssert, FunctionCallOutputAssert, MessageAssert, RunAssert, RunResult, } from './run_result.js';
19
19
  export { isAgentHandoffEvent, isChatMessageEvent, isFunctionCallEvent, isFunctionCallOutputEvent, type AgentHandoffAssertOptions, type AgentHandoffEvent, type ChatMessageEvent, type EventType, type FunctionCallAssertOptions, type FunctionCallEvent, type FunctionCallOutputAssertOptions, type FunctionCallOutputEvent, type MessageAssertOptions, type RunEvent, } from './types.js';
20
+ export { FakeLLM, type FakeLLMResponse } from './fake_llm.js';
20
21
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/voice/testing/index.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EACL,kBAAkB,EAClB,cAAc,EACd,WAAW,EACX,gBAAgB,EAChB,kBAAkB,EAClB,wBAAwB,EACxB,aAAa,EACb,SAAS,EACT,SAAS,GACV,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,mBAAmB,EACnB,yBAAyB,EACzB,KAAK,yBAAyB,EAC9B,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,SAAS,EACd,KAAK,yBAAyB,EAC9B,KAAK,iBAAiB,EACtB,KAAK,+BAA+B,EACpC,KAAK,uBAAuB,EAC5B,KAAK,oBAAoB,EACzB,KAAK,QAAQ,GACd,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/voice/testing/index.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EACL,kBAAkB,EAClB,cAAc,EACd,WAAW,EACX,gBAAgB,EAChB,kBAAkB,EAClB,wBAAwB,EACxB,aAAa,EACb,SAAS,EACT,SAAS,GACV,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,mBAAmB,EACnB,yBAAyB,EACzB,KAAK,yBAAyB,EAC9B,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,SAAS,EACd,KAAK,yBAAyB,EAC9B,KAAK,iBAAiB,EACtB,KAAK,+BAA+B,EACpC,KAAK,uBAAuB,EAC5B,KAAK,oBAAoB,EACzB,KAAK,QAAQ,GACd,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC"}
@@ -15,11 +15,13 @@ import {
15
15
  isFunctionCallEvent,
16
16
  isFunctionCallOutputEvent
17
17
  } from "./types.js";
18
+ import { FakeLLM } from "./fake_llm.js";
18
19
  export {
19
20
  AgentHandoffAssert,
20
21
  AssertionError,
21
22
  EventAssert,
22
23
  EventRangeAssert,
24
+ FakeLLM,
23
25
  FunctionCallAssert,
24
26
  FunctionCallOutputAssert,
25
27
  MessageAssert,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/voice/testing/index.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Testing utilities for agent evaluation.\n *\n * @example\n * ```typescript\n * import { AgentSession, Agent, voice } from '@livekit/agents';\n *\n * const session = new AgentSession({ llm });\n * await session.start(agent);\n *\n * const result = await session.run({ userInput: 'Hello' });\n * result.expect.nextEvent().isMessage({ role: 'assistant' });\n * result.expect.noMoreEvents();\n * ```\n *\n * @packageDocumentation\n */\n\nexport {\n AgentHandoffAssert,\n AssertionError,\n EventAssert,\n EventRangeAssert,\n FunctionCallAssert,\n FunctionCallOutputAssert,\n MessageAssert,\n RunAssert,\n RunResult,\n} from './run_result.js';\n\nexport {\n isAgentHandoffEvent,\n isChatMessageEvent,\n isFunctionCallEvent,\n isFunctionCallOutputEvent,\n type AgentHandoffAssertOptions,\n type AgentHandoffEvent,\n type ChatMessageEvent,\n type EventType,\n type FunctionCallAssertOptions,\n type FunctionCallEvent,\n type FunctionCallOutputAssertOptions,\n type FunctionCallOutputEvent,\n type MessageAssertOptions,\n type RunEvent,\n} from './types.js';\n"],"mappings":"AAsBA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAWK;","names":[]}
1
+ {"version":3,"sources":["../../../src/voice/testing/index.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Testing utilities for agent evaluation.\n *\n * @example\n * ```typescript\n * import { AgentSession, Agent, voice } from '@livekit/agents';\n *\n * const session = new AgentSession({ llm });\n * await session.start(agent);\n *\n * const result = await session.run({ userInput: 'Hello' });\n * result.expect.nextEvent().isMessage({ role: 'assistant' });\n * result.expect.noMoreEvents();\n * ```\n *\n * @packageDocumentation\n */\n\nexport {\n AgentHandoffAssert,\n AssertionError,\n EventAssert,\n EventRangeAssert,\n FunctionCallAssert,\n FunctionCallOutputAssert,\n MessageAssert,\n RunAssert,\n RunResult,\n} from './run_result.js';\n\nexport {\n isAgentHandoffEvent,\n isChatMessageEvent,\n isFunctionCallEvent,\n isFunctionCallOutputEvent,\n type AgentHandoffAssertOptions,\n type AgentHandoffEvent,\n type ChatMessageEvent,\n type EventType,\n type FunctionCallAssertOptions,\n type FunctionCallEvent,\n type FunctionCallOutputAssertOptions,\n type FunctionCallOutputEvent,\n type MessageAssertOptions,\n type RunEvent,\n} from './types.js';\n\nexport { FakeLLM, type FakeLLMResponse } from './fake_llm.js';\n"],"mappings":"AAsBA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAWK;AAEP,SAAS,eAAqC;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livekit/agents",
3
- "version": "1.0.47",
3
+ "version": "1.0.48",
4
4
  "description": "LiveKit Agents - Node.js",
5
5
  "main": "dist/index.js",
6
6
  "require": "dist/index.cjs",
@@ -0,0 +1,9 @@
1
+ // SPDX-FileCopyrightText: 2026 LiveKit, Inc.
2
+ //
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ export {
5
+ TaskGroup,
6
+ type TaskCompletedEvent,
7
+ type TaskGroupOptions,
8
+ type TaskGroupResult,
9
+ } from './workflows/index.js';
@@ -0,0 +1,9 @@
1
+ // SPDX-FileCopyrightText: 2026 LiveKit, Inc.
2
+ //
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ export {
5
+ TaskGroup,
6
+ type TaskCompletedEvent,
7
+ type TaskGroupOptions,
8
+ type TaskGroupResult,
9
+ } from './task_group.js';
@@ -0,0 +1,194 @@
1
+ // SPDX-FileCopyrightText: 2026 LiveKit, Inc.
2
+ //
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ import { z } from 'zod';
5
+ import type { ChatContext } from '../../llm/chat_context.js';
6
+ import { LLM, ToolError, ToolFlag, tool } from '../../llm/index.js';
7
+ import { AgentTask } from '../../voice/agent.js';
8
+
9
+ interface FactoryInfo {
10
+ taskFactory: () => AgentTask;
11
+ id: string;
12
+ description: string;
13
+ }
14
+
15
+ export interface TaskGroupResult {
16
+ taskResults: Record<string, unknown>;
17
+ }
18
+
19
+ export interface TaskCompletedEvent {
20
+ agentTask: AgentTask;
21
+ taskId: string;
22
+ result: unknown;
23
+ }
24
+
25
+ class OutOfScopeError extends ToolError {
26
+ readonly targetTaskIds: string[];
27
+
28
+ constructor(targetTaskIds: string[]) {
29
+ super('out_of_scope');
30
+ this.targetTaskIds = targetTaskIds;
31
+ }
32
+ }
33
+
34
+ export interface TaskGroupOptions {
35
+ summarizeChatCtx?: boolean;
36
+ returnExceptions?: boolean;
37
+ chatCtx?: ChatContext;
38
+ onTaskCompleted?: (event: TaskCompletedEvent) => Promise<void>;
39
+ }
40
+
41
+ export class TaskGroup extends AgentTask<TaskGroupResult> {
42
+ private _summarizeChatCtx: boolean;
43
+ private _returnExceptions: boolean;
44
+ private _visitedTasks = new Set<string>();
45
+ private _registeredFactories = new Map<string, FactoryInfo>();
46
+ private _taskCompletedCallback?: (event: TaskCompletedEvent) => Promise<void>;
47
+ private _currentTask?: AgentTask;
48
+
49
+ constructor(options: TaskGroupOptions = {}) {
50
+ const { summarizeChatCtx = true, returnExceptions = false, chatCtx, onTaskCompleted } = options;
51
+
52
+ super({ instructions: '*empty*', chatCtx });
53
+
54
+ this._summarizeChatCtx = summarizeChatCtx;
55
+ this._returnExceptions = returnExceptions;
56
+ this._taskCompletedCallback = onTaskCompleted;
57
+ }
58
+
59
+ add(task: () => AgentTask, { id, description }: { id: string; description: string }): this {
60
+ this._registeredFactories.set(id, { taskFactory: task, id, description });
61
+ return this;
62
+ }
63
+
64
+ async onEnter(): Promise<void> {
65
+ const taskStack = [...this._registeredFactories.keys()];
66
+ const taskResults: Record<string, unknown> = {};
67
+
68
+ while (taskStack.length > 0) {
69
+ const taskId = taskStack.shift()!;
70
+ const factoryInfo = this._registeredFactories.get(taskId)!;
71
+
72
+ this._currentTask = factoryInfo.taskFactory();
73
+
74
+ const sharedChatCtx = this._chatCtx.copy();
75
+ await this._currentTask.updateChatCtx(sharedChatCtx);
76
+
77
+ const outOfScopeTool = this.buildOutOfScopeTool(taskId);
78
+ if (outOfScopeTool) {
79
+ await this._currentTask.updateTools({
80
+ ...this._currentTask.toolCtx,
81
+ out_of_scope: outOfScopeTool,
82
+ });
83
+ }
84
+
85
+ try {
86
+ this._visitedTasks.add(taskId);
87
+ const res = await this._currentTask.run();
88
+ taskResults[taskId] = res;
89
+
90
+ if (this._taskCompletedCallback) {
91
+ await this._taskCompletedCallback({
92
+ agentTask: this._currentTask,
93
+ taskId,
94
+ result: res,
95
+ });
96
+ }
97
+ } catch (e) {
98
+ if (e instanceof OutOfScopeError) {
99
+ taskStack.unshift(taskId);
100
+ for (let i = e.targetTaskIds.length - 1; i >= 0; i--) {
101
+ taskStack.unshift(e.targetTaskIds[i]!);
102
+ }
103
+ continue;
104
+ }
105
+
106
+ if (this._returnExceptions) {
107
+ taskResults[taskId] = e;
108
+ continue;
109
+ } else {
110
+ this.complete(e instanceof Error ? e : new Error(String(e)));
111
+ return;
112
+ }
113
+ }
114
+ }
115
+
116
+ try {
117
+ if (this._summarizeChatCtx) {
118
+ const sessionLlm = this.session.llm;
119
+ if (!(sessionLlm instanceof LLM)) {
120
+ throw new Error('summarizeChatCtx requires a standard LLM on the session');
121
+ }
122
+
123
+ // TODO(parity): Add excludeConfigUpdate when AgentConfigUpdate is ported
124
+ const ctxToSummarize = this._chatCtx.copy({
125
+ excludeInstructions: true,
126
+ excludeHandoff: true,
127
+ excludeEmptyMessage: true,
128
+ excludeFunctionCall: true,
129
+ });
130
+
131
+ const summarizedChatCtx = await ctxToSummarize._summarize(sessionLlm, {
132
+ keepLastTurns: 0,
133
+ });
134
+ await this.updateChatCtx(summarizedChatCtx);
135
+ }
136
+ } catch (e) {
137
+ this.complete(new Error(`failed to summarize the chat_ctx: ${e}`));
138
+ return;
139
+ }
140
+
141
+ this.complete({ taskResults });
142
+ }
143
+
144
+ private buildOutOfScopeTool(activeTaskId: string) {
145
+ if (this._visitedTasks.size === 0) {
146
+ return undefined;
147
+ }
148
+
149
+ const regressionTaskIds = new Set(this._visitedTasks);
150
+ regressionTaskIds.delete(activeTaskId);
151
+
152
+ if (regressionTaskIds.size === 0) {
153
+ return undefined;
154
+ }
155
+
156
+ const taskRepr: Record<string, string> = {};
157
+ for (const [id, info] of this._registeredFactories) {
158
+ if (regressionTaskIds.has(id)) {
159
+ taskRepr[id] = info.description;
160
+ }
161
+ }
162
+
163
+ const taskIdValues = [...regressionTaskIds] as [string, ...string[]];
164
+
165
+ const description =
166
+ 'Call to regress to other tasks according to what the user requested to modify, return the corresponding task ids. ' +
167
+ 'For example, if the user wants to change their email and there is a task with id "email_task" with a description of "Collect the user\'s email", return the id ("get_email_task"). ' +
168
+ 'If the user requests to regress to multiple tasks, such as changing their phone number and email, return both task ids in the order they were requested. ' +
169
+ `The following are the IDs and their corresponding task description. ${JSON.stringify(taskRepr)}`;
170
+
171
+ const currentTask = this._currentTask;
172
+ const registeredFactories = this._registeredFactories;
173
+ const visitedTasks = this._visitedTasks;
174
+
175
+ return tool({
176
+ description,
177
+ flags: ToolFlag.IGNORE_ON_ENTER,
178
+ parameters: z.object({
179
+ task_ids: z.array(z.enum(taskIdValues)).describe('The IDs of the tasks requested'),
180
+ }),
181
+ execute: async ({ task_ids }: { task_ids: string[] }) => {
182
+ for (const tid of task_ids) {
183
+ if (!registeredFactories.has(tid) || !visitedTasks.has(tid)) {
184
+ throw new ToolError(`Unable to regress, invalid task id ${tid}`);
185
+ }
186
+ }
187
+
188
+ if (currentTask && !currentTask.done) {
189
+ currentTask.complete(new OutOfScopeError(task_ids));
190
+ }
191
+ },
192
+ });
193
+ }
194
+ }
package/src/index.ts CHANGED
@@ -9,6 +9,7 @@
9
9
  * @see {@link https://docs.livekit.io/agents/overview | LiveKit Agents documentation}
10
10
  * @packageDocumentation
11
11
  */
12
+ import * as beta from './beta/index.js';
12
13
  import * as cli from './cli.js';
13
14
  import * as inference from './inference/index.js';
14
15
  import * as ipc from './ipc/index.js';
@@ -37,4 +38,4 @@ export * from './version.js';
37
38
  export { createTimedString, isTimedString, type TimedString } from './voice/io.js';
38
39
  export * from './worker.js';
39
40
 
40
- export { cli, inference, ipc, llm, metrics, stream, stt, telemetry, tokenize, tts, voice };
41
+ export { beta, cli, inference, ipc, llm, metrics, stream, stt, telemetry, tokenize, tts, voice };
@@ -2,6 +2,8 @@
2
2
  //
3
3
  // SPDX-License-Identifier: Apache-2.0
4
4
  import { describe, expect, it } from 'vitest';
5
+ import { initializeLogger } from '../log.js';
6
+ import { FakeLLM } from '../voice/testing/fake_llm.js';
5
7
  import {
6
8
  type AudioContent,
7
9
  ChatContext,
@@ -13,6 +15,8 @@ import {
13
15
  ReadonlyChatContext,
14
16
  } from './chat_context.js';
15
17
 
18
+ initializeLogger({ pretty: false, level: 'error' });
19
+
16
20
  describe('ChatContext.toJSON', () => {
17
21
  it('should match snapshot for empty context', () => {
18
22
  const context = new ChatContext();
@@ -283,6 +287,50 @@ describe('ChatContext.toJSON', () => {
283
287
  });
284
288
  });
285
289
 
290
+ describe('ChatContext._summarize', () => {
291
+ it('keeps chronological timestamps with summary + tail', async () => {
292
+ const ctx = new ChatContext();
293
+ ctx.addMessage({ role: 'system', content: 'System prompt', createdAt: 0 });
294
+ ctx.addMessage({ role: 'user', content: 'hello', createdAt: 1000 });
295
+ ctx.addMessage({ role: 'assistant', content: 'hi there', createdAt: 2000 });
296
+ ctx.insert(
297
+ new FunctionCallOutput({
298
+ callId: 'call_1',
299
+ name: 'lookup',
300
+ output: '{"ok":true}',
301
+ isError: false,
302
+ createdAt: 3500,
303
+ }),
304
+ );
305
+ ctx.addMessage({ role: 'user', content: 'my color is blue', createdAt: 3000 });
306
+ ctx.addMessage({ role: 'assistant', content: 'noted', createdAt: 4000 });
307
+
308
+ const fake = new FakeLLM([
309
+ {
310
+ input: 'Conversation to summarize:\n\nuser: hello\nassistant: hi there',
311
+ content: 'condensed head',
312
+ },
313
+ ]);
314
+
315
+ await ctx._summarize(fake, { keepLastTurns: 1 });
316
+
317
+ const summary = ctx.items.find(
318
+ (item) =>
319
+ item.type === 'message' && item.role === 'assistant' && item.extra?.is_summary === true,
320
+ );
321
+ expect(summary).toBeDefined();
322
+ if (!summary || summary.type !== 'message') {
323
+ throw new Error('summary message is missing');
324
+ }
325
+
326
+ expect(summary.createdAt).toBeCloseTo(2999.999, 6);
327
+
328
+ const createdAts = ctx.items.map((item) => item.createdAt);
329
+ const sorted = [...createdAts].sort((a, b) => a - b);
330
+ expect(createdAts).toEqual(sorted);
331
+ });
332
+ });
333
+
286
334
  describe('ReadonlyChatContext with immutable array', () => {
287
335
  it('should have readonly property set to true', () => {
288
336
  const items: ChatItem[] = [
@@ -3,6 +3,7 @@
3
3
  // SPDX-License-Identifier: Apache-2.0
4
4
  import type { AudioFrame, VideoFrame } from '@livekit/rtc-node';
5
5
  import { createImmutableArray, shortuuid } from '../utils.js';
6
+ import type { LLM } from './llm.js';
6
7
  import { type ProviderFormat, toChatCtx } from './provider_format/index.js';
7
8
  import type { JSONObject, JSONValue, ToolContext } from './tool_context.js';
8
9
 
@@ -95,12 +96,15 @@ export class ChatMessage {
95
96
 
96
97
  createdAt: number;
97
98
 
99
+ extra: Record<string, unknown>;
100
+
98
101
  constructor(params: {
99
102
  role: ChatRole;
100
103
  content: ChatContent[] | string;
101
104
  id?: string;
102
105
  interrupted?: boolean;
103
106
  createdAt?: number;
107
+ extra?: Record<string, unknown>;
104
108
  }) {
105
109
  const {
106
110
  role,
@@ -108,12 +112,14 @@ export class ChatMessage {
108
112
  id = shortuuid('item_'),
109
113
  interrupted = false,
110
114
  createdAt = Date.now(),
115
+ extra = {},
111
116
  } = params;
112
117
  this.id = id;
113
118
  this.role = role;
114
119
  this.content = Array.isArray(content) ? content : [content];
115
120
  this.interrupted = interrupted;
116
121
  this.createdAt = createdAt;
122
+ this.extra = extra;
117
123
  }
118
124
 
119
125
  static create(params: {
@@ -122,6 +128,7 @@ export class ChatMessage {
122
128
  id?: string;
123
129
  interrupted?: boolean;
124
130
  createdAt?: number;
131
+ extra?: Record<string, unknown>;
125
132
  }) {
126
133
  return new ChatMessage(params);
127
134
  }
@@ -401,6 +408,7 @@ export class AgentHandoffItem {
401
408
  }
402
409
  }
403
410
 
411
+ // TODO(parity): Add AgentConfigUpdate type to ChatItem union
404
412
  export type ChatItem = ChatMessage | FunctionCall | FunctionCallOutput | AgentHandoffItem;
405
413
 
406
414
  export class ChatContext {
@@ -431,6 +439,7 @@ export class ChatContext {
431
439
  id?: string;
432
440
  interrupted?: boolean;
433
441
  createdAt?: number;
442
+ extra?: Record<string, unknown>;
434
443
  }): ChatMessage {
435
444
  const msg = new ChatMessage(params);
436
445
  if (params.createdAt !== undefined) {
@@ -463,11 +472,13 @@ export class ChatContext {
463
472
  return idx !== -1 ? idx : undefined;
464
473
  }
465
474
 
475
+ // TODO(parity): Add excludeConfigUpdate option when AgentConfigUpdate is ported
466
476
  copy(
467
477
  options: {
468
478
  excludeFunctionCall?: boolean;
469
479
  excludeInstructions?: boolean;
470
480
  excludeEmptyMessage?: boolean;
481
+ excludeHandoff?: boolean;
471
482
  toolCtx?: ToolContext<any>; // eslint-disable-line @typescript-eslint/no-explicit-any
472
483
  } = {},
473
484
  ): ChatContext {
@@ -475,6 +486,7 @@ export class ChatContext {
475
486
  excludeFunctionCall = false,
476
487
  excludeInstructions = false,
477
488
  excludeEmptyMessage = false,
489
+ excludeHandoff = false,
478
490
  toolCtx,
479
491
  } = options;
480
492
  const items: ChatItem[] = [];
@@ -500,6 +512,10 @@ export class ChatContext {
500
512
  continue;
501
513
  }
502
514
 
515
+ if (excludeHandoff && item.type === 'agent_handoff') {
516
+ continue;
517
+ }
518
+
503
519
  if (toolCtx !== undefined && isToolCallOrOutput(item) && toolCtx[item.name] === undefined) {
504
520
  continue;
505
521
  }
@@ -510,6 +526,7 @@ export class ChatContext {
510
526
  return new ChatContext(items);
511
527
  }
512
528
 
529
+ // TODO(parity): Add excludeConfigUpdate option when AgentConfigUpdate is ported
513
530
  merge(
514
531
  other: ChatContext,
515
532
  options: {
@@ -762,6 +779,112 @@ export class ChatContext {
762
779
  return true;
763
780
  }
764
781
 
782
+ async _summarize(llm: LLM, options: { keepLastTurns?: number } = {}): Promise<ChatContext> {
783
+ const { keepLastTurns = 2 } = options;
784
+
785
+ const toSummarize: ChatMessage[] = [];
786
+ for (const item of this._items) {
787
+ if (item.type !== 'message') continue;
788
+ if (item.role !== 'user' && item.role !== 'assistant') continue;
789
+ if (item.extra?.is_summary === true) continue;
790
+
791
+ const text = (item.textContent ?? '').trim();
792
+ if (text) {
793
+ toSummarize.push(item);
794
+ }
795
+ }
796
+
797
+ if (toSummarize.length === 0) {
798
+ return this;
799
+ }
800
+
801
+ const tailN = Math.max(0, Math.min(toSummarize.length, keepLastTurns * 2));
802
+ let head: ChatMessage[];
803
+ let tail: ChatMessage[];
804
+ if (tailN === 0) {
805
+ head = toSummarize;
806
+ tail = [];
807
+ } else {
808
+ head = toSummarize.slice(0, -tailN);
809
+ tail = toSummarize.slice(-tailN);
810
+ }
811
+
812
+ if (head.length === 0) {
813
+ return this;
814
+ }
815
+
816
+ const sourceText = head
817
+ .map((m) => `${m.role}: ${(m.textContent ?? '').trim()}`)
818
+ .join('\n')
819
+ .trim();
820
+
821
+ if (!sourceText) {
822
+ return this;
823
+ }
824
+
825
+ // TODO: refactor this into LLMStream.collect API.
826
+ const promptCtx = new ChatContext();
827
+ promptCtx.addMessage({
828
+ role: 'system',
829
+ content:
830
+ 'Compress older chat history into a short, faithful summary.\n' +
831
+ 'Focus on user goals, constraints, decisions, key facts/preferences/entities, and pending tasks.\n' +
832
+ 'Exclude chit-chat and greetings. Be concise.',
833
+ });
834
+ promptCtx.addMessage({
835
+ role: 'user',
836
+ content: `Conversation to summarize:\n\n${sourceText}`,
837
+ });
838
+
839
+ const chunks: string[] = [];
840
+ for await (const chunk of llm.chat({ chatCtx: promptCtx })) {
841
+ if (chunk.delta?.content) {
842
+ chunks.push(chunk.delta.content);
843
+ }
844
+ }
845
+
846
+ const summary = chunks.join('').trim();
847
+ if (!summary) {
848
+ return this;
849
+ }
850
+
851
+ const tailStartTs = tail.length > 0 ? tail[0]!.createdAt : Infinity;
852
+
853
+ const preserved: ChatItem[] = [];
854
+ for (const it of this._items) {
855
+ if (
856
+ (it.type === 'function_call' || it.type === 'function_call_output') &&
857
+ it.createdAt < tailStartTs
858
+ ) {
859
+ continue;
860
+ }
861
+
862
+ if (it.type === 'message' && (it.role === 'user' || it.role === 'assistant')) {
863
+ continue;
864
+ }
865
+
866
+ preserved.push(it);
867
+ }
868
+
869
+ this._items = preserved;
870
+
871
+ const createdAtHint =
872
+ tail.length > 0 ? tail[0]!.createdAt - 1e-3 : head[head.length - 1]!.createdAt + 1e-3;
873
+
874
+ this.addMessage({
875
+ role: 'assistant',
876
+ content: `[history summary]\n${summary}`,
877
+ createdAt: createdAtHint,
878
+ extra: { is_summary: true },
879
+ });
880
+
881
+ for (const msg of tail) {
882
+ this.insert(msg);
883
+ }
884
+
885
+ return this;
886
+ }
887
+
765
888
  /**
766
889
  * Indicates whether the context is read-only
767
890
  */
package/src/llm/index.ts CHANGED
@@ -6,6 +6,7 @@ export {
6
6
  isFunctionTool,
7
7
  tool,
8
8
  ToolError,
9
+ ToolFlag,
9
10
  type AgentHandoff,
10
11
  type FunctionTool,
11
12
  type ProviderDefinedTool,
@@ -80,6 +80,13 @@ export class ToolError extends Error {
80
80
  }
81
81
  }
82
82
 
83
+ export const ToolFlag = {
84
+ NONE: 0,
85
+ IGNORE_ON_ENTER: 1 << 0,
86
+ } as const;
87
+
88
+ export type ToolFlag = (typeof ToolFlag)[keyof typeof ToolFlag];
89
+
83
90
  export interface AgentHandoff {
84
91
  /**
85
92
  * The agent to handoff to.
@@ -178,6 +185,8 @@ export interface FunctionTool<
178
185
  */
179
186
  execute: ToolExecuteFunction<Parameters, UserData, Result>;
180
187
 
188
+ flags: number;
189
+
181
190
  [FUNCTION_TOOL_SYMBOL]: true;
182
191
  }
183
192
 
@@ -242,10 +251,12 @@ export function tool<
242
251
  description,
243
252
  parameters,
244
253
  execute,
254
+ flags,
245
255
  }: {
246
256
  description: string;
247
257
  parameters: Schema;
248
258
  execute: ToolExecuteFunction<InferToolInput<Schema>, UserData, Result>;
259
+ flags?: number;
249
260
  }): FunctionTool<InferToolInput<Schema>, UserData, Result>;
250
261
 
251
262
  /**
@@ -254,10 +265,12 @@ export function tool<
254
265
  export function tool<UserData = UnknownUserData, Result = unknown>({
255
266
  description,
256
267
  execute,
268
+ flags,
257
269
  }: {
258
270
  description: string;
259
271
  parameters?: never;
260
272
  execute: ToolExecuteFunction<Record<string, never>, UserData, Result>;
273
+ flags?: number;
261
274
  }): FunctionTool<Record<string, never>, UserData, Result>;
262
275
 
263
276
  /**
@@ -295,6 +308,7 @@ export function tool(tool: any): any {
295
308
  description: tool.description,
296
309
  parameters,
297
310
  execute: tool.execute,
311
+ flags: tool.flags ?? ToolFlag.NONE,
298
312
  [TOOL_SYMBOL]: true,
299
313
  [FUNCTION_TOOL_SYMBOL]: true,
300
314
  };