Laimiao agent v2 service: agent orchestration, sessions, billing, gateway client, skills catalog, auth, config, plus tests.
95 lines
3.8 KiB
TypeScript
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 });
|
|
});
|
|
});
|