/** * The Jev tool ranker: what tool_search and searchTools() get back, what Jev is sent, and that * discovery keeps working (BM25) whenever Jev cannot answer. */ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; const state = vi.hoisted(() => ({ settingsPath: "" })); vi.mock("@selesai/code", async () => ({ ...(await import("./tool-search/tool.ts")), getSettingsPath: () => state.settingsPath, })); import { TOKENIN_JEV_CLASSIFIER } from "./jev/classifier.ts"; import { JEV_ROUTING_EVENT } from "./jev/decisions.ts"; import { classifierResponse, providerTemplate } from "./jev/test-support.ts"; import { createJevToolRanker, TOOL_RANK_MAX_REQUESTS } from "./jev-tool-ranker.ts"; import { FIND_BATCH } from "./jev-ask-tool.ts"; import { Bm25Ranker, createToolSearchDocument, type ToolSearchDocument } from "./tool-search/tool.ts"; let root: string; beforeAll(() => { root = mkdtempSync(join(tmpdir(), "jev-tool-ranker-")); mkdirSync(join(root, "agent"), { recursive: true }); state.settingsPath = join(root, "agent", "settings.json"); }); afterAll(() => rmSync(root, { recursive: true, force: true })); const tool = (name: string, description: string): ToolSearchDocument => createToolSearchDocument({ name, description, parameters: {} as never }); const TOOLS = [ tool("mcp__gh__create_issue", "Open a new issue in a repository to track a defect or task."), tool("mcp__db__run_query", "Run a read-only SQL query against the analytics database."), tool("mcp__cal__add_event", "Add an event to the calendar."), ]; interface Sent { state: { request: string; tools: Record }; questions: Record; } function setup(options: { enabled?: boolean; credential?: boolean; relevant?: string[]; score?: number } = {}) { writeFileSync( state.settingsPath, JSON.stringify({ jevAdvisory: { routes: { toolSearch: { enabled: options.enabled ?? true } } } }), "utf-8", ); const relevant = new Set(options.relevant ?? []); const sent: Sent[] = []; const classify = vi.fn(async (_model: unknown, context: Sent) => { sent.push(context); const names = Object.keys(context.state.tools); const answers = Object.fromEntries( Object.keys(context.questions).map((id, index) => [ id, { noul: relevant.has(names[Number(id.slice(1))]) ? (options.score ?? 0.92) : 0.08 }, ]), ); return classifierResponse(JSON.stringify({ answers })); }); const events: Record[] = []; const ranker = createJevToolRanker({ events: { emit: (channel, data) => channel === JEV_ROUTING_EVENT && events.push(data as Record) }, }); const ctx = { modelRegistry: { getAll: () => [providerTemplate()], // Only Jev is registered: asking for any other model would find none and fall back to BM25. findOfType: (type: string, provider: string, id: string) => type === "classifier" && provider === "tokenin" && id === "jev-1.13" ? { ...TOKENIN_JEV_CLASSIFIER } : undefined, getApiKeyAndHeaders: async () => (options.credential === false ? { ok: false } : { ok: true, apiKey: "key", headers: {} }), classify, }, }; return { ranker, ctx: ctx as never, classify, sent, events }; } beforeEach(() => vi.clearAllMocks()); describe("Jev tool ranker", () => { it("finds a tool whose words the query never uses", async () => { const query = "file a bug report for the crash"; expect(new Bm25Ranker().rank(query, TOOLS, 5)).toEqual([]); const { ranker, ctx, classify, sent, events } = setup({ relevant: ["mcp__gh__create_issue"] }); const matches = await ranker.rank(query, TOOLS, 5, { ctx }); expect(matches.map((match) => match.name)).toEqual(["mcp__gh__create_issue"]); expect(classify.mock.calls[0][0]).toMatchObject({ id: "jev-1.13", api: "typesafe-system-one" }); expect(sent).toHaveLength(1); expect(sent[0].state.request).toBe(query); expect(Object.keys(sent[0].state.tools)).toEqual(TOOLS.map((document) => document.name)); expect(sent[0].state.tools["mcp__cal__add_event"]).toContain("Add an event to the calendar."); expect(events).toEqual([ expect.objectContaining({ route: "toolSearch", outcome: "jev", candidates: 3, judged: 3, matched: 1, confidence: "high" }), ]); // Telemetry is shape only. expect(JSON.stringify(events)).not.toContain("bug report"); }); it("orders matches by Jev's probability and honors the limit", async () => { const { ranker, ctx } = setup({ relevant: ["mcp__db__run_query", "mcp__cal__add_event"] }); const matches = await ranker.rank("anything", TOOLS, 1, { ctx }); expect(matches).toHaveLength(1); }); it("treats Jev as the judge: low probabilities mean no match", async () => { const { ranker, ctx } = setup({ relevant: [] }); expect(await ranker.rank("calendar event", TOOLS, 5, { ctx })).toEqual([]); }); it("ranks with BM25 and sends nothing without a session", async () => { const { ranker, classify } = setup({ relevant: ["mcp__gh__create_issue"] }); const matches = await ranker.rank("calendar event", TOOLS, 5); expect(matches.map((match) => match.name)).toEqual(["mcp__cal__add_event"]); expect(classify).not.toHaveBeenCalled(); }); it("ranks with BM25 when the route is off", async () => { const { ranker, ctx, classify } = setup({ enabled: false }); expect((await ranker.rank("calendar event", TOOLS, 5, { ctx })).map((match) => match.name)).toEqual(["mcp__cal__add_event"]); expect(classify).not.toHaveBeenCalled(); }); it("ranks with BM25 without a Token-In credential", async () => { const { ranker, ctx, classify, events } = setup({ credential: false }); expect((await ranker.rank("calendar event", TOOLS, 5, { ctx })).map((match) => match.name)).toEqual(["mcp__cal__add_event"]); expect(classify).not.toHaveBeenCalled(); expect(events).toEqual([expect.objectContaining({ route: "toolSearch", outcome: "fallback", reason: "no-credential" })]); }); it("ranks with BM25 when every request fails", async () => { const { ranker, ctx, classify, events } = setup(); classify.mockRejectedValue(new Error("down")); expect((await ranker.rank("calendar event", TOOLS, 5, { ctx })).map((match) => match.name)).toEqual(["mcp__cal__add_event"]); expect(events).toEqual([expect.objectContaining({ outcome: "fallback" })]); }); it("ranks with BM25 when the caller already aborted", async () => { const { ranker, ctx } = setup({ relevant: ["mcp__gh__create_issue"] }); const controller = new AbortController(); controller.abort(); const matches = await ranker.rank("calendar event", TOOLS, 5, { ctx, signal: controller.signal }); expect(matches.map((match) => match.name)).toEqual(["mcp__cal__add_event"]); }); it("keeps BM25 hits for tools whose batch failed", async () => { // 18 BM25 hits fill one batch of 16 and spill two into a second batch. const documents = Array.from({ length: FIND_BATCH + 2 }, (_, index) => tool(`mcp__x__gadget_${index}`, `A gadget, number ${index}.`)); const { ranker, ctx, classify } = setup({ relevant: ["mcp__x__gadget_3"] }); const answer = classify.getMockImplementation()!; classify.mockImplementation(async (model, context) => { if ("mcp__x__gadget_16" in context.state.tools) throw new Error("down"); return answer(model, context); }); const matches = await ranker.rank("gadget", documents, 30, { ctx }); // Jev's verdicts come first, then the tools Jev never scored in BM25 order; scored-and-rejected tools are gone. expect(matches.map((match) => match.name)).toEqual(["mcp__x__gadget_3", "mcp__x__gadget_16", "mcp__x__gadget_17"]); }); it("sends at most TOOL_RANK_MAX_REQUESTS requests, choosing by BM25 past the cap", async () => { const cap = TOOL_RANK_MAX_REQUESTS * FIND_BATCH; const documents = [ ...Array.from({ length: cap + 50 }, (_, index) => tool(`mcp__bulk__tool_${index}`, `Generic helper ${index}.`)), tool("mcp__late__quokka", "Pet the quokka."), ]; const { ranker, ctx, classify, sent } = setup({ relevant: ["mcp__late__quokka"] }); const matches = await ranker.rank("quokka", documents, 3, { ctx }); expect(classify.mock.calls.length).toBeLessThanOrEqual(TOOL_RANK_MAX_REQUESTS); const judged = sent.flatMap((request) => Object.keys(request.state.tools)); expect(judged).toHaveLength(cap); expect(judged).toContain("mcp__late__quokka"); expect(matches.map((match) => match.name)).toEqual(["mcp__late__quokka"]); }); });