// Run with `bun test`. import assert from "node:assert/strict"; import { test } from "node:test"; import { array, boolean, integer, object, objectSchema, oneOf, parseArgs, string, ToolError } from "./params"; const props = { owner: string("Owner.", { min: 1, max: 39 }), limit: integer("Limit.", { min: 1, max: 50 }).default(20), state: oneOf("State.", ["open", "closed"] as const).optional(), files: array("Files.", object("A file.", { path: string("Path."), binary: boolean("Binary.").default(false) })).default([]), }; test("parses arguments, filling in defaults", () => { assert.deepEqual(parseArgs(props, { owner: "joni", files: [{ path: "a" }] }), { owner: "joni", limit: 20, state: undefined, files: [{ path: "a", binary: false }], }); // Agents send null for "not given", and numbers as strings. assert.deepEqual(parseArgs(props, { owner: "joni", limit: "5", state: null }), { owner: "joni", limit: 5, state: undefined, files: [] }); }); test("refuses bad arguments with messages an agent can act on", () => { const fails = (args: unknown, message: RegExp) => assert.throws(() => parseArgs(props, args), (e) => e instanceof ToolError && message.test(e.message)); fails({}, /owner is required/); fails({ owner: "" }, /owner can't be empty/); fails({ owner: "a", limit: 99 }, /limit must be between 1 and 50/); fails({ owner: "a", state: "merged" }, /state must be one of open, closed/); fails({ owner: "a", file: [] }, /Unknown argument file/); fails({ owner: "a", files: [{ path: 1 }] }, /files\[0\]\.path must be a string/); }); test("describes itself as JSON Schema", () => { const schema = objectSchema(props); assert.deepEqual(schema.required, ["owner"]); assert.equal(schema.additionalProperties, false); assert.deepEqual((schema.properties as Record).limit.default, 20); });