spb/khaelor Public
KHAELOR — a terminal-native autonomous engineering agent powered by Anthropic.
TypeScript 82.9%
HTML 14.9%
CSS 1.1%
JavaScript 0.7%
1/**2 * KHAELOR3 * File: tests/tools/registry.test.ts4 * Description: Unit tests for the ToolRegistry — registration, schema export, and input validation.5 *6 * Author: Simon-Pierre Boucher7 * Contact: contact@spboucher.ai8 */910import { describe, expect, it } from "vitest";11import {12 ToolRegistry,13 createDefaultToolRegistry,14 createReadTool,15 parseToolInput,16} from "../../src/tools/index.js";1718describe("ToolRegistry", () => {19 it("registers, lists, and gets tools", () => {20 const registry = new ToolRegistry();21 registry.register(createReadTool());22 expect(registry.list().map((t) => t.name)).toEqual(["read"]);23 expect(registry.get("read")?.name).toBe("read");24 expect(registry.get("missing")).toBeUndefined();25 });2627 it("rejects duplicate registration", () => {28 const registry = new ToolRegistry();29 registry.register(createReadTool());30 expect(() => registry.register(createReadTool())).toThrowError(/already registered/);31 });3233 it("registers all seven default tools", () => {34 const registry = createDefaultToolRegistry();35 expect(registry.list().map((t) => t.name)).toEqual([36 "read",37 "write",38 "edit",39 "grep",40 "glob",41 "bash",42 "process",43 ]);44 });4546 it("exports valid Anthropic tool params with at most 5 parameters each", () => {47 const params = createDefaultToolRegistry().toAnthropicTools();48 expect(params).toHaveLength(7);49 for (const param of params) {50 expect(typeof param.name).toBe("string");51 expect(param.description.length).toBeGreaterThan(50);52 expect(param.input_schema.type).toBe("object");53 const propNames = Object.keys(param.input_schema.properties);54 expect(propNames.length).toBeGreaterThan(0);55 expect(propNames.length).toBeLessThanOrEqual(5);56 for (const required of param.input_schema.required) {57 expect(propNames).toContain(required);58 }59 for (const prop of Object.values(param.input_schema.properties)) {60 expect(["string", "integer", "boolean"]).toContain(prop.type);61 expect(prop.description.length).toBeGreaterThan(0);62 }63 // Round-trips as plain JSON (what the Anthropic SDK serializes).64 expect(JSON.parse(JSON.stringify(param))).toEqual(param);65 }66 });6768 it("snake_case parameter naming throughout", () => {69 for (const param of createDefaultToolRegistry().toAnthropicTools()) {70 for (const name of Object.keys(param.input_schema.properties)) {71 expect(name).toMatch(/^[a-z][a-z0-9_]*$/);72 }73 }74 });75});7677describe("parseToolInput", () => {78 const read = createReadTool();7980 it("accepts valid input", () => {81 const parsed = parseToolInput(read, { file_path: "a.ts", offset: 2 });82 expect(parsed.ok).toBe(true);83 });8485 it("produces repair prose for a missing required parameter", () => {86 const parsed = parseToolInput(read, { offset: 2 });87 expect(parsed.ok).toBe(false);88 if (!parsed.ok) {89 expect(parsed.error).toBe(90 'Invalid input for tool "read": parameter "file_path" is required. Please rewrite the input so it satisfies the expected schema.',91 );92 }93 });9495 it("rejects wrong types with repair prose", () => {96 const parsed = parseToolInput(read, { file_path: "a.ts", offset: "two" });97 expect(parsed.ok).toBe(false);98 if (!parsed.ok) expect(parsed.error).toContain('parameter "offset" must be an integer');99 });100101 it("rejects non-object input", () => {102 const parsed = parseToolInput(read, "just a string");103 expect(parsed.ok).toBe(false);104 if (!parsed.ok) expect(parsed.error).toContain("must be a JSON object");105 });106107 it("enforces enums (process action)", () => {108 const registry = createDefaultToolRegistry();109 const processTool = registry.get("process");110 expect(processTool).toBeDefined();111 const parsed = parseToolInput(processTool!, { action: "restart" });112 expect(parsed.ok).toBe(false);113 if (!parsed.ok) {114 expect(parsed.error).toContain('parameter "action" must be one of "start", "list", "read", "write", "stop"');115 }116 });117});118