forked from CopilotKit/CopilotKit
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrun-loop.test.ts
More file actions
93 lines (83 loc) · 3 KB
/
Copy pathrun-loop.test.ts
File metadata and controls
93 lines (83 loc) · 3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import { describe, it, expect, vi } from "vitest";
import { z } from "zod";
import { runAgentLoop } from "./run-loop.js";
import { makeFakeRunRenderer } from "./testing/fake-adapter.js";
import { FakeAgent } from "./testing/fake-agent.js";
import type { BotTool, AgentToolDescriptor, ContextEntry } from "./tools.js";
import type { AgentSubscriber } from "@ag-ui/client";
import type { CapturedInterrupt } from "./platform-adapter.js";
const toolDescriptors: AgentToolDescriptor[] = [];
const context: ContextEntry[] = [];
describe("runAgentLoop", () => {
it("executes a frontend tool call and pushes the result, then terminates", async () => {
const renderer = makeFakeRunRenderer();
const recorded: Array<{ msg: string }> = [];
const echo: BotTool = {
name: "echo",
description: "echo back",
parameters: z.object({ msg: z.string() }),
handler: (args) => {
recorded.push(args as { msg: string });
return { ok: true };
},
};
const tools = new Map<string, BotTool>([["echo", echo]]);
// Step 1: agent emits an `echo` tool call, then finishes.
// Step 2: agent just finishes (no further tool calls) -> loop terminates.
const agent = new FakeAgent([
(sub: AgentSubscriber) => {
sub.onToolCallEndEvent?.({
event: { toolCallId: "t1" },
toolCallName: "echo",
toolCallArgs: { msg: "hi" },
} as never);
sub.onRunFinishedEvent?.({ event: {} } as never);
},
(sub: AgentSubscriber) => {
sub.onRunFinishedEvent?.({ event: {} } as never);
},
]);
await runAgentLoop({
agent,
renderer,
tools,
toolDescriptors,
context,
makeToolCtx: () => ({ thread: {} as never, platform: "fake" }),
});
expect(recorded).toEqual([{ msg: "hi" }]);
expect(agent.runAgentCalls).toBe(2);
const toolResult = agent.messages.find((m) => m.role === "tool");
expect(toolResult).toBeDefined();
expect((toolResult as { toolCallId?: string }).toolCallId).toBe("t1");
});
it("posts the picker via handleInterrupt and returns without running tools", async () => {
const renderer = makeFakeRunRenderer();
const tools = new Map<string, BotTool>();
const handleInterrupt = vi.fn<(i: CapturedInterrupt) => void>();
const agent = new FakeAgent([
(sub: AgentSubscriber) => {
sub.onCustomEvent?.({
event: { name: "on_interrupt", value: { q: 1 } },
} as never);
sub.onRunFinishedEvent?.({ event: {} } as never);
},
]);
await runAgentLoop({
agent,
renderer,
tools,
toolDescriptors,
context,
makeToolCtx: () => ({ thread: {} as never, platform: "fake" }),
handleInterrupt,
});
expect(handleInterrupt).toHaveBeenCalledTimes(1);
expect(handleInterrupt).toHaveBeenCalledWith({
eventName: "on_interrupt",
value: { q: 1 },
});
expect(agent.runAgentCalls).toBe(1);
expect(agent.messages.some((m) => m.role === "tool")).toBe(false);
});
});