MagicCode/packages/core/src/agent.test.ts

104 lines
3.3 KiB
TypeScript
Raw Normal View History

2026-10-11 00:11:20 +00:00
import { describe, expect, it } from "vitest";
import { createSession, runTurn } from "./agent";
import type { AgentUI, LLMProvider, PermissionPolicy, StreamEvent, Tool } from "./types";
const silentUI: AgentUI = {
onThinking() {},
onText() {},
onTextEnd() {},
onToolCall() {},
onToolResult() {},
confirm: async () => true,
};
/** Provider factice : 1er appel → demande d'outil, 2e appel → réponse texte. */
function fakeProvider(toolName = "echo"): LLMProvider {
let call = 0;
return {
id: "fake",
model: "fake",
capabilities: { tools: true },
async *chat(): AsyncIterable<StreamEvent> {
call++;
if (call === 1) {
yield { type: "tool_use", id: "t1", name: toolName, input: { value: "salut" } };
yield { type: "done", stopReason: "tool_use" };
} else {
yield { type: "text_delta", text: "terminé" };
yield { type: "done", stopReason: "end_turn" };
}
},
};
}
describe("runTurn", () => {
it("exécute l'outil demandé puis renvoie la réponse finale", async () => {
const echo: Tool = {
spec: { name: "echo", description: "echo", inputSchema: { type: "object" } },
permission: "auto",
execute: async (input) => String(input.value),
};
const session = createSession({
provider: fakeProvider(),
init: { systemPrompt: "test", tools: [echo] },
ui: silentUI,
cwd: process.cwd(),
});
await runTurn(session, "bonjour");
// user, assistant(tool_use), user(tool_result), assistant(texte)
expect(session.messages).toHaveLength(4);
expect(session.messages[2]?.content[0]).toMatchObject({
type: "tool_result",
content: "salut",
});
});
});
describe("politique de permissions", () => {
const makeTool = (ran: string[]): Tool => ({
spec: { name: "danger", description: "d", inputSchema: { type: "object" } },
permission: "ask",
execute: async () => {
ran.push("x");
return "ok";
},
});
const policy = (decision: "allow" | "ask" | "deny"): PermissionPolicy => ({
check: () => ({ decision, reason: "règle de test" }),
});
async function run(decision: "allow" | "ask" | "deny", answer: boolean) {
const ran: string[] = [];
const confirms: string[] = [];
const session = createSession({
provider: fakeProvider("danger"),
init: { systemPrompt: "t", tools: [makeTool(ran)], permissions: policy(decision) },
ui: { ...silentUI, confirm: async (m) => (confirms.push(m), answer) },
cwd: process.cwd(),
});
await runTurn(session, "go");
return { ran, confirms, session };
}
it("allow : exécute sans confirmation", async () => {
const { ran, confirms } = await run("allow", false);
expect(ran).toHaveLength(1);
expect(confirms).toHaveLength(0);
});
it("deny : refuse sans demander", async () => {
const { ran, confirms, session } = await run("deny", true);
expect(ran).toHaveLength(0);
expect(confirms).toHaveLength(0);
expect(session.messages[2]?.content[0]).toMatchObject({ type: "tool_result", isError: true });
});
it("ask : demande confirmation même pour un outil automatique", async () => {
const { ran, confirms } = await run("ask", true);
expect(confirms).toHaveLength(1);
expect(ran).toHaveLength(1);
});
});