@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.
- package/dist/beta/index.cjs +29 -0
- package/dist/beta/index.cjs.map +1 -0
- package/dist/beta/index.d.cts +2 -0
- package/dist/beta/index.d.ts +2 -0
- package/dist/beta/index.d.ts.map +1 -0
- package/dist/beta/index.js +7 -0
- package/dist/beta/index.js.map +1 -0
- package/dist/beta/workflows/index.cjs +29 -0
- package/dist/beta/workflows/index.cjs.map +1 -0
- package/dist/beta/workflows/index.d.cts +2 -0
- package/dist/beta/workflows/index.d.ts +2 -0
- package/dist/beta/workflows/index.d.ts.map +1 -0
- package/dist/beta/workflows/index.js +7 -0
- package/dist/beta/workflows/index.js.map +1 -0
- package/dist/beta/workflows/task_group.cjs +162 -0
- package/dist/beta/workflows/task_group.cjs.map +1 -0
- package/dist/beta/workflows/task_group.d.cts +32 -0
- package/dist/beta/workflows/task_group.d.ts +32 -0
- package/dist/beta/workflows/task_group.d.ts.map +1 -0
- package/dist/beta/workflows/task_group.js +138 -0
- package/dist/beta/workflows/task_group.js.map +1 -0
- package/dist/index.cjs +3 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/inference/api_protos.d.cts +59 -59
- package/dist/inference/api_protos.d.ts +59 -59
- package/dist/llm/chat_context.cjs +89 -1
- package/dist/llm/chat_context.cjs.map +1 -1
- package/dist/llm/chat_context.d.cts +10 -1
- package/dist/llm/chat_context.d.ts +10 -1
- package/dist/llm/chat_context.d.ts.map +1 -1
- package/dist/llm/chat_context.js +89 -1
- package/dist/llm/chat_context.js.map +1 -1
- package/dist/llm/chat_context.test.cjs +43 -0
- package/dist/llm/chat_context.test.cjs.map +1 -1
- package/dist/llm/chat_context.test.js +43 -0
- package/dist/llm/chat_context.test.js.map +1 -1
- package/dist/llm/index.cjs +2 -0
- package/dist/llm/index.cjs.map +1 -1
- package/dist/llm/index.d.cts +1 -1
- package/dist/llm/index.d.ts +1 -1
- package/dist/llm/index.d.ts.map +1 -1
- package/dist/llm/index.js +3 -1
- package/dist/llm/index.js.map +1 -1
- package/dist/llm/provider_format/index.d.cts +1 -1
- package/dist/llm/provider_format/index.d.ts +1 -1
- package/dist/llm/tool_context.cjs +7 -0
- package/dist/llm/tool_context.cjs.map +1 -1
- package/dist/llm/tool_context.d.cts +10 -2
- package/dist/llm/tool_context.d.ts +10 -2
- package/dist/llm/tool_context.d.ts.map +1 -1
- package/dist/llm/tool_context.js +6 -0
- package/dist/llm/tool_context.js.map +1 -1
- package/dist/utils.cjs +1 -0
- package/dist/utils.cjs.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +1 -0
- package/dist/utils.js.map +1 -1
- package/dist/version.cjs +1 -1
- package/dist/version.js +1 -1
- package/dist/voice/agent.cjs +9 -0
- package/dist/voice/agent.cjs.map +1 -1
- package/dist/voice/agent.d.cts +1 -0
- package/dist/voice/agent.d.ts +1 -0
- package/dist/voice/agent.d.ts.map +1 -1
- package/dist/voice/agent.js +9 -0
- package/dist/voice/agent.js.map +1 -1
- package/dist/voice/agent_activity.cjs +31 -8
- package/dist/voice/agent_activity.cjs.map +1 -1
- package/dist/voice/agent_activity.d.cts +7 -0
- package/dist/voice/agent_activity.d.ts +7 -0
- package/dist/voice/agent_activity.d.ts.map +1 -1
- package/dist/voice/agent_activity.js +31 -8
- package/dist/voice/agent_activity.js.map +1 -1
- package/dist/voice/room_io/room_io.cjs +11 -2
- package/dist/voice/room_io/room_io.cjs.map +1 -1
- package/dist/voice/room_io/room_io.d.ts.map +1 -1
- package/dist/voice/room_io/room_io.js +12 -3
- package/dist/voice/room_io/room_io.js.map +1 -1
- package/dist/voice/testing/fake_llm.cjs +127 -0
- package/dist/voice/testing/fake_llm.cjs.map +1 -0
- package/dist/voice/testing/fake_llm.d.cts +30 -0
- package/dist/voice/testing/fake_llm.d.ts +30 -0
- package/dist/voice/testing/fake_llm.d.ts.map +1 -0
- package/dist/voice/testing/fake_llm.js +103 -0
- package/dist/voice/testing/fake_llm.js.map +1 -0
- package/dist/voice/testing/index.cjs +3 -0
- package/dist/voice/testing/index.cjs.map +1 -1
- package/dist/voice/testing/index.d.cts +1 -0
- package/dist/voice/testing/index.d.ts +1 -0
- package/dist/voice/testing/index.d.ts.map +1 -1
- package/dist/voice/testing/index.js +2 -0
- package/dist/voice/testing/index.js.map +1 -1
- package/package.json +1 -1
- package/src/beta/index.ts +9 -0
- package/src/beta/workflows/index.ts +9 -0
- package/src/beta/workflows/task_group.ts +194 -0
- package/src/index.ts +2 -1
- package/src/llm/chat_context.test.ts +48 -0
- package/src/llm/chat_context.ts +123 -0
- package/src/llm/index.ts +1 -0
- package/src/llm/tool_context.ts +14 -0
- package/src/utils.ts +5 -0
- package/src/voice/agent.ts +11 -0
- package/src/voice/agent_activity.ts +44 -6
- package/src/voice/room_io/room_io.ts +14 -3
- package/src/voice/testing/fake_llm.ts +138 -0
- 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
|
@@ -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[] = [
|
package/src/llm/chat_context.ts
CHANGED
|
@@ -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
package/src/llm/tool_context.ts
CHANGED
|
@@ -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
|
};
|