import { describe, expect, test } from "vitest"; import type { UIMessage } from "ai"; import { customWidgetAssistantInstructions, getForcedAssistantToolName, getRequiredAssistantToolNames, withAssistantToolPolicy, } from "./assistant-tool-policy"; const assistantMessage = (...parts: UIMessage["parts"]): UIMessage => ({ id: "assistant-message", role: "assistant", parts, }); describe("withAssistantToolPolicy", () => { test("tells models to call approval-gated mutations without a prose confirmation", () => { const description = withAssistantToolPolicy("Create a Homarr app.", true); expect(description).toContain("call this tool immediately"); expect(description).toContain("does not execute until the user selects Approve and run"); expect(description).toContain("Never ask for confirmation in prose"); }); test("does not alter read-only tool descriptions", () => { expect(withAssistantToolPolicy("List all Homarr apps.", false)).toBe("List all Homarr apps."); }); }); describe("customWidgetAssistantInstructions", () => { test("requires the complete skill and real preview-query data before creation", () => { expect(customWidgetAssistantInstructions).toContain("Custom Widget tools are mandatory"); expect(customWidgetAssistantInstructions).toContain("only a manifest, JSX, instructions"); expect(customWidgetAssistantInstructions).toContain("when they are preloaded below"); expect(customWidgetAssistantInstructions).toContain("do not call customWidget_getSkill or customWidget_schema"); expect(customWidgetAssistantInstructions).toContain( "already loaded their complete contents into this system prompt", ); expect(customWidgetAssistantInstructions).toContain("first call customWidget_getSkill"); expect(customWidgetAssistantInstructions).toContain("customWidget_schema"); expect(customWidgetAssistantInstructions).toContain("customWidget_getComponentCatalog"); expect(customWidgetAssistantInstructions).toContain("customWidget_getComponent"); expect(customWidgetAssistantInstructions).toContain("customWidget_getSharedProps once"); expect(customWidgetAssistantInstructions).toContain("at most eight named component documents"); expect(customWidgetAssistantInstructions).toContain("at most four after loading a complete example"); expect(customWidgetAssistantInstructions).toContain("successful preview responses as binding contracts"); expect(customWidgetAssistantInstructions).toContain("Make initial states actionable"); expect(customWidgetAssistantInstructions).toContain("never load the catalog component-by-component"); expect(customWidgetAssistantInstructions).toContain("compact rows that remain scannable in narrow tiles"); expect(customWidgetAssistantInstructions).toContain("Label standalone icons"); expect(customWidgetAssistantInstructions).toContain("customWidget_getExample"); expect(customWidgetAssistantInstructions).toContain("templateLines"); expect(customWidgetAssistantInstructions).toContain("installed `pokedex` example"); expect(customWidgetAssistantInstructions).toContain("Honor an explicit iteration count"); expect(customWidgetAssistantInstructions).toContain("customWidget_validate with the complete definition"); expect(customWidgetAssistantInstructions).toContain("customWidget_previewQuery for every query returned"); expect(customWidgetAssistantInstructions).toContain("the previous validation and preview evidence is stale"); expect(customWidgetAssistantInstructions).toContain("Never substitute a later unvalidated version"); expect(customWidgetAssistantInstructions).toContain("customWidget_createFromPreview"); expect(customWidgetAssistantInstructions).toContain("Never say the widget is created, updated, placed"); expect(customWidgetAssistantInstructions).toContain("corresponding customWidget_create, customWidget_update"); expect(customWidgetAssistantInstructions).toContain("managementPath"); }); test("routes meaningful follow-up choices through ask_user instead of prose", () => { expect(customWidgetAssistantInstructions).toContain("call ask_user with explicit choices"); expect(customWidgetAssistantInstructions).toContain("Never end a custom-widget response with a prose question"); expect(customWidgetAssistantInstructions).toContain("purely rhetorical questions do not require ask_user"); }); }); describe("getForcedAssistantToolName", () => { test("continues directly from the reviewed app form to app creation", () => { expect( getForcedAssistantToolName([ assistantMessage({ type: "dynamic-tool", toolName: "configure_app", toolCallId: "configure-1", input: { name: "YouTube" }, state: "output-available", output: { name: "YouTube", iconUrl: "/api/icons/youtube.svg", href: "https://youtube.com", }, }), ]), ).toBe("app_create"); }); test("waits for the user to finish reviewing the app form", () => { expect( getForcedAssistantToolName([ assistantMessage({ type: "dynamic-tool", toolName: "configure_app", toolCallId: "configure-1", input: { name: "YouTube" }, state: "input-available", }), ]), ).toBeUndefined(); }); test("continues directly from reviewed board settings to the settings mutation", () => { expect( getForcedAssistantToolName([ assistantMessage({ type: "dynamic-tool", toolName: "configure_board_settings", toolCallId: "settings-1", input: { boardId: "board-1", boardName: "Home", changes: { customCss: ".item {}" } }, state: "output-available", output: { id: "board-1", customCss: ".item {}" }, }), ]), ).toBe("board_savePartialBoardSettings"); }); test("does not request a mutation when board settings were left unchanged", () => { expect( getForcedAssistantToolName([ assistantMessage({ type: "dynamic-tool", toolName: "configure_board_settings", toolCallId: "settings-1", input: { boardId: "board-1", boardName: "Home", changes: {} }, state: "output-available", output: { id: "board-1", cancelled: true }, }), ]), ).toBeUndefined(); }); test("continues directly from reviewed widget settings to board placement", () => { expect( getForcedAssistantToolName([ assistantMessage({ type: "dynamic-tool", toolName: "configure_widget", toolCallId: "widget-1", input: { boardId: "board-1", boardName: "Home", kind: "notebook" }, state: "output-available", output: { boardId: "board-1", kind: "notebook", options: { content: "