Files
homarr/apps/nextjs/src/app/api/assistant/chat/assistant-message-conversion.spec.ts
T
Thomas 5e23659d99 feat(assistant): optimize MCP and custom widget authoring (#6743)
* feat(assistant): optimize MCP and custom widget authoring

* fix(assistant): isolate server-safe JSX analyzer

* refactor(assistant): generate MCP tool staging

* fix(assistant): preserve widget placement follow-up

* fix(ci): allow fast gate to finish

* fix(custom-widgets): address review findings

* fix(custom-widgets): split preview evidence module

* style: format assistant custom widget files

* fix(custom-widgets): sync formatted skill schema
2026-08-30 23:43:29 +02:00

243 lines
7.8 KiB
TypeScript

import { describe, expect, test } from "vitest";
import type { ModelMessage, UIMessage } from "ai";
import { compactAssistantStepMessages, convertAssistantMessagesToModelMessages } from "./assistant-message-conversion";
describe("convertAssistantMessagesToModelMessages", () => {
test("drops an interrupted tool call while preserving completed tool results", async () => {
const messages: UIMessage[] = [
{
id: "user-1",
role: "user",
parts: [{ type: "text", text: "Find Homarr icons." }],
},
{
id: "assistant-1",
role: "assistant",
parts: [
{
type: "tool-icon_findIcons",
toolCallId: "cancelled-call",
state: "input-available",
input: { searchText: "homarr" },
},
{
type: "tool-board_getBoardSettings",
toolCallId: "completed-call",
state: "output-available",
input: { id: "board-1" },
output: { id: "board-1", name: "Home" },
},
],
},
{
id: "user-2",
role: "user",
parts: [{ type: "text", text: "Continue." }],
},
];
const result = await convertAssistantMessagesToModelMessages(messages);
const serialized = JSON.stringify(result);
expect(serialized).not.toContain("cancelled-call");
expect(serialized).toContain("completed-call");
expect(serialized).toContain('"name":"Home"');
expect(result.at(-1)).toEqual({ role: "user", content: [{ type: "text", text: "Continue." }] });
});
test("compacts oversized custom widget preview query results from conversation history", async () => {
const oversizedData = Array.from({ length: 1_000 }, (_, index) => ({
id: index,
title: `Result ${index}`,
description: "x".repeat(100),
}));
const messages: UIMessage[] = [
{
id: "assistant-1",
role: "assistant",
parts: [
{
type: "dynamic-tool",
toolName: "customWidget_previewQuery",
toolCallId: "preview-query-1",
state: "output-available",
input: { sessionId: "preview-1", requestId: "results", params: {} },
output: { ok: true, status: 200, data: oversizedData },
},
],
},
{
id: "user-1",
role: "user",
parts: [{ type: "text", text: "Continue building the widget." }],
},
];
const result = await convertAssistantMessagesToModelMessages(messages);
const serialized = JSON.stringify(result);
expect(serialized).toContain('"truncated":true');
expect(serialized).toContain('"status":200');
expect(serialized).toContain("Continue building the widget.");
expect(serialized.length).toBeLessThan(10_000);
});
test("drops reloadable Custom Widget documentation from later requests", async () => {
const messages: UIMessage[] = [
{
id: "assistant-1",
role: "assistant",
parts: [
{
type: "dynamic-tool",
toolName: "customWidget_schema",
toolCallId: "schema-1",
state: "output-available",
input: {},
output: { title: "Large live schema" },
},
{
type: "dynamic-tool",
toolName: "customWidget_previewCreate",
toolCallId: "preview-1",
state: "output-available",
input: { definition: { name: "Seerr research" } },
output: { previewSession: { id: "preview-1" } },
},
],
},
{
id: "user-1",
role: "user",
parts: [{ type: "text", text: "Test the preview queries." }],
},
];
const serialized = JSON.stringify(await convertAssistantMessagesToModelMessages(messages));
expect(serialized).not.toContain("Large live schema");
expect(serialized).toContain("customWidget_previewCreate");
expect(serialized).toContain("Test the preview queries.");
});
test("prunes obsolete authoring calls inside a long-running tool loop", async () => {
const messages: UIMessage[] = [
{
id: "user-1",
role: "user",
parts: [{ type: "text", text: "Create two advanced Seerr widgets." }],
},
{
id: "assistant-1",
role: "assistant",
parts: [
{
type: "dynamic-tool",
toolName: "customWidget_getComponent",
toolCallId: "old-component",
state: "output-available",
input: { name: "Select" },
output: { name: "Select", props: "old component documentation".repeat(1_000) },
},
],
},
{
id: "assistant-2",
role: "assistant",
parts: [
{
type: "dynamic-tool",
toolName: "customWidget_validateTemplate",
toolCallId: "old-validation",
state: "output-available",
input: { template: "<Stack>old template</Stack>".repeat(1_000) },
output: { valid: true },
},
],
},
{
id: "assistant-3",
role: "assistant",
parts: [
{
type: "dynamic-tool",
toolName: "customWidget_previewCreate",
toolCallId: "current-preview",
state: "output-available",
input: { definition: { name: "Seerr research", template: "<Stack>current</Stack>" } },
output: { previewSession: { id: "preview-1" }, queries: [{ requestId: "search" }] },
},
],
},
];
const converted = await convertAssistantMessagesToModelMessages(messages);
const compacted = compactAssistantStepMessages(converted, 1);
const serialized = JSON.stringify(compacted);
expect(serialized).not.toContain("old-component");
expect(serialized).not.toContain("old-validation");
expect(serialized).toContain("current-preview");
expect(serialized).toContain("preview-1");
});
test("drops duplicate Custom Widget calls when a provider ignores sequential tool settings", () => {
const messages: ModelMessage[] = [
{
role: "assistant",
content: [
{
type: "tool-call",
toolCallId: "first-preview",
toolName: "customWidget_previewCreate",
input: { definition: { name: "First" } },
},
{
type: "tool-call",
toolCallId: "duplicate-preview",
toolName: "customWidget_previewCreate",
input: { definition: { name: "Duplicate", template: "x".repeat(20_000) } },
},
{
type: "tool-call",
toolCallId: "regular-tool",
toolName: "homarr_enableToolGroups",
input: { groups: ["board"] },
},
],
},
{
role: "tool",
content: [
{
type: "tool-result",
toolCallId: "first-preview",
toolName: "customWidget_previewCreate",
output: { type: "json", value: { previewSession: { id: "preview-1" } } },
},
{
type: "tool-result",
toolCallId: "duplicate-preview",
toolName: "customWidget_previewCreate",
output: { type: "json", value: { error: "Sequential call rejected" } },
},
{
type: "tool-result",
toolCallId: "regular-tool",
toolName: "homarr_enableToolGroups",
output: { type: "json", value: { enabledGroups: ["board"] } },
},
],
},
];
const serialized = JSON.stringify(compactAssistantStepMessages(messages));
expect(serialized).toContain("first-preview");
expect(serialized).not.toContain("regular-tool");
expect(serialized).not.toContain("duplicate-preview");
expect(serialized.length).toBeLessThan(2_000);
});
});