Parent directory

classify.test.ts

2550 bytes
 1import { describe, expect, test } from "bun:test";
 2import { createClassifierReviewer, type PiClassifierCaller } from "../../src/core/classify";
 3import { createPolicyReviewer } from "../../src/core/review";
 4import type { ClassifierReviewerConfig } from "../../src/core/types";
 5
 6const config: ClassifierReviewerConfig = {
 7  kind: "classifier",
 8  provider: "openrouter",
 9  model: "typesafe/jev-1.13",
10  askThreshold: 0.2,
11};
12
13function caller(probabilities: Record<string, number> = {}): PiClassifierCaller {
14  return async (_, input) => ({
15    provider: config.provider,
16    model: config.model,
17    api: "typesafe-system-one",
18    timestamp: Date.now(),
19    stopReason: "stop",
20    answers: Object.fromEntries(
21      Object.keys(input.questions).map((id) => [id, { type: "bool", probability: probabilities[id] ?? 0.01 }]),
22    ),
23  });
24}
25
26const request = { toolName: "bash", input: { command: "example" } };
27
28describe("classifier decisions", () => {
29  test("allows when all risks are below the threshold", async () => {
30    const result = await createClassifierReviewer(config, caller()).evaluate(request, {});
31    expect(result.decision.decision).toBe("allow");
32  });
33
34  test.each(["privilege", "destruction", "secrets", "files", "remote", "git", "home_search"])(
35    "asks for %s at the threshold",
36    async (id) => {
37      const result = await createClassifierReviewer(config, caller({ [id]: config.askThreshold })).evaluate(
38        request,
39        {},
40      );
41      expect(result.decision.decision).toBe("ask");
42    },
43  );
44
45  test.each([NaN, Infinity, -0.1, 1.1])("asks for invalid probability %s", async (probability) => {
46    const result = await createClassifierReviewer(config, caller({ privilege: probability })).evaluate(request, {});
47    expect(result.decision.decision).toBe("ask");
48  });
49
50  test("asks when answers are missing", async () => {
51    const result = await createClassifierReviewer(config, async (...args) => ({
52      ...(await caller()(...args)),
53      answers: {},
54    })).evaluate(request, {});
55    expect(result.decision.decision).toBe("ask");
56  });
57
58  test("asks on classifier failure", async () => {
59    const result = await createClassifierReviewer(config, async () => {
60      throw new Error("Unavailable");
61    }).evaluate(request, {});
62    expect(result.decision.decision).toBe("ask");
63  });
64
65  test("asks when the classifier adapter is absent", async () => {
66    const result = await createPolicyReviewer(config, async () => "").evaluate(request, {});
67    expect(result.decision.decision).toBe("ask");
68  });
69});