Files
light 6ec9778d45 chore: initial commit
Laimiao agent v2 service: agent orchestration, sessions, billing,
gateway client, skills catalog, auth, config, plus tests.
2026-11-07 10:40:07 +08:00

95 lines
3.8 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { ToolConfirmationManager, type ToolConfirmationRequest } from "../src/agent/confirmations.js";
function request(manager: ToolConfirmationManager, toolCallId = "call-1", signal?: AbortSignal) {
let emitted: ToolConfirmationRequest | undefined;
const promise = manager.request(
{
sessionId: "s1",
toolCallId,
toolName: "generate_image",
args: { prompt: "poster" },
},
(value) => {
emitted = value;
},
signal,
);
return { promise, get emitted() { return emitted; } };
}
describe("ToolConfirmationManager", () => {
it("waits for approval and resolves the tool call", async () => {
const manager = new ToolConfirmationManager(1_000);
const pending = request(manager);
expect(pending.emitted?.toolCallId).toBe("call-1");
expect(pending.emitted?.expiresAt).toBeGreaterThan(Date.now());
expect(manager.resolve("s1", "call-1", true)).toBe(true);
await expect(pending.promise).resolves.toEqual({ approved: true, reason: undefined });
expect(manager.count()).toBe(0);
});
it("carries confirm type/payload and resolves edited content", async () => {
const manager = new ToolConfirmationManager(1_000);
let emitted: ToolConfirmationRequest | undefined;
const promise = manager.request(
{
sessionId: "s1",
toolCallId: "call-content",
toolName: "confirm_travel_content",
confirmType: "travel_content_confirm",
payload: { summary: "线路:测试" },
args: {},
},
(value) => {
emitted = value;
},
);
expect(emitted?.confirmType).toBe("travel_content_confirm");
expect(emitted?.payload).toEqual({ summary: "线路:测试" });
expect(manager.resolve("s1", "call-content", { approved: true, content: "用户修改后的内容" })).toBe(true);
await expect(promise).resolves.toEqual({ approved: true, content: "用户修改后的内容" });
});
it("returns a rejection reason", async () => {
const manager = new ToolConfirmationManager(1_000);
const pending = request(manager);
manager.resolve("s1", "call-1", false, "不要生成");
await expect(pending.promise).resolves.toEqual({ approved: false, reason: "不要生成" });
});
it("times out and does not leave stale entries", async () => {
const manager = new ToolConfirmationManager(10);
const pending = request(manager);
await expect(pending.promise).resolves.toMatchObject({ approved: false, reason: "等待用户确认超时" });
expect(manager.count()).toBe(0);
});
it("cancels when the run is aborted", async () => {
const manager = new ToolConfirmationManager(1_000);
const controller = new AbortController();
const pending = request(manager, "call-abort", controller.signal);
controller.abort();
await expect(pending.promise).resolves.toEqual({ approved: false, reason: "请求已中止" });
});
it("clears all pending confirmations for a session", async () => {
const manager = new ToolConfirmationManager(1_000);
const first = request(manager, "call-1");
const second = request(manager, "call-2");
manager.clearSession("s1");
await expect(first.promise).resolves.toMatchObject({ approved: false, reason: "会话已结束" });
await expect(second.promise).resolves.toMatchObject({ approved: false, reason: "会话已结束" });
});
it("rejects duplicate pending confirmations for the same tool call", async () => {
const manager = new ToolConfirmationManager(1_000);
const first = request(manager);
const duplicate = request(manager);
await expect(duplicate.promise).resolves.toMatchObject({ approved: false });
manager.resolve("s1", "call-1", true);
await expect(first.promise).resolves.toEqual({ approved: true, reason: undefined });
});
});