import { assert, assertEquals, assertObjectMatch, assertStringIncludes } from "@std/assert"; import { Type } from "@earendil-works/pi-ai"; import { getCurrentTools } from "@earendil-works/pi-ai/utils/transcript"; import { fauxAssistantMessage, fauxToolCall } from "@earendil-works/pi-ai/providers/faux"; import { defineExtension } from "@berry/api"; import { lastToolResult, script, sectionsSeen, setup, toolsOffered, turn } from "./fixture.ts"; Deno.test("conversation tools follow configuration in both catalogs and validate the current schema", async () => { await using h = await setup(); let calls = 0; await h.berry.install(defineExtension({ name: "dynamic", initial: () => false, apply: (_, event) => event.enabled, setup(ext) { ext.configuration({ parameters: Type.Object({ enabled: Type.Boolean({ default: false }) }), values: (enabled) => ({ enabled }), events: ({ enabled }) => [{ enabled }], }); return (conv) => { conv.tool({ name: "answer", description: conv.state ? "configured answer" : "initial answer", parameters: Type.Object({ value: conv.state ? Type.Number() : Type.String() }), returns: Type.String(), expose: "both", execute: ({ value }) => { calls++; return String(value); }, text: (value) => `answer: ${value}`, }); if (conv.state) { conv.tool({ name: "private", description: "direct only", parameters: Type.Object({}), expose: "model", execute: () => "private answer", }); } }; }, })); const tid = await h.berry.create({ extensions: { dynamic: {} }, model: h.model }); const other = await h.berry.create({ extensions: { dynamic: {} }, model: h.model }); const direct = (value: string | number) => fauxAssistantMessage(fauxToolCall("answer", { value }), { stopReason: "toolUse" }); const initial = await turn(h, tid, [direct("hello")]); assert(toolsOffered(initial[0]).includes("answer")); assertStringIncludes(lastToolResult(initial[1]).text, "answer: hello"); await h.berry.configureExtension(tid, "dynamic", { enabled: true }); const configured = await turn(h, tid, [ direct("stale"), direct(42), script("return await tools.answer({ value: 43 })"), script("return await tools.private({})"), ]); assert(lastToolResult(configured[1]).isError); assertStringIncludes(lastToolResult(configured[2]).text, "answer: 42"); assertStringIncludes(lastToolResult(configured[3]).text, "43"); assert(lastToolResult(configured[4]).isError); assert(toolsOffered(configured[0]).includes("private")); const catalog = sectionsSeen(configured[0]).findLast((section) => section.codemode !== undefined)?.codemode ?? ""; assertStringIncludes(catalog, "answer: string"); assert(!catalog.includes("value:")); assert(!catalog.includes("private")); const offered = getCurrentTools(configured[0]); const answer = offered.find((tool) => tool.name === "answer")!; assertEquals(answer.description, "configured answer"); assertObjectMatch(answer.parameters, { properties: { value: { type: "number" } } }); assertEquals(calls, 3); const unchanged = await turn(h, other, [direct("still a string")]); assert(!toolsOffered(unchanged[0]).includes("private")); assertStringIncludes(lastToolResult(unchanged[1]).text, "still a string"); await h.berry.configureExtension(tid, "dynamic", { enabled: false }); const removed = await turn(h, tid, [ fauxAssistantMessage(fauxToolCall("private", {}), { stopReason: "toolUse" }), ]); assert(!toolsOffered(removed[0]).includes("private")); assert(lastToolResult(removed[1]).isError); }); Deno.test("conversation registrations replace static tools and emitted state changes the next request", async () => { await using h = await setup(); await h.berry.install(defineExtension({ name: "replacement", initial: () => false, apply: (_, event) => event, setup(ext) { ext.tool({ name: "replace", description: "static", parameters: Type.Object({}), expose: "both", execute: () => "must not execute", }); return (conv) => conv.tool({ name: "replace", description: "dynamic", parameters: Type.Object({}), expose: conv.state ? "codemode" : "both", async execute(_, ctx) { await ctx.emit(true); return "replacement"; }, }); }, })); const tid = await h.berry.create({ extensions: { replacement: {} }, model: h.model }); const direct = fauxAssistantMessage(fauxToolCall("replace", {}), { stopReason: "toolUse" }); const seen = await turn(h, tid, [direct, direct, script("return await tools.replace({})")]); assertStringIncludes(lastToolResult(seen[1]).text, "replacement"); assert(!toolsOffered(seen[1]).includes("replace")); assert(lastToolResult(seen[2]).isError); assertStringIncludes(lastToolResult(seen[3]).text, "replacement"); });