UNPKG

@convex-dev/agent

Version:

A agent component for Convex.

274 lines (251 loc) 7.44 kB
import { describe, it, expect } from "vitest"; import { combineUIMessages } from "./UIMessages.js"; describe("combineUIMessages", () => { it("combines messages spanning two pages correctly", () => { const message1 = { id: "msg1", key: "msg1-key", _creationTime: Date.now(), order: 1, stepOrder: 1, status: "success" as const, role: "assistant" as const, text: "", parts: [ { type: "dynamic-tool" as const, state: "input-available" as const, toolCallId: "call_123", toolName: "calculator", input: { operation: "add", a: 2, b: 3 }, }, ], }; const message2 = { id: "msg2", key: "msg2-key", _creationTime: Date.now() + 1, order: 1, stepOrder: 2, status: "success" as const, role: "assistant" as const, text: "The result is 5.", parts: [ { type: "tool-calculator" as const, state: "output-available" as const, toolCallId: "call_123", input: { operation: "add", a: 2, b: 3 }, output: { result: 5 }, }, { type: "text" as const, text: "The result is 5.", state: "done" as const, }, ], }; const combined = combineUIMessages([message1, message2]); expect(combined).toHaveLength(1); expect(combined[0].role).toBe("assistant"); expect(combined[0].text).toBe("The result is 5."); expect(combined[0].parts).toHaveLength(2); const toolPart = combined[0].parts.find( (p) => p.type === "tool-calculator", ); expect(toolPart).toMatchObject({ type: "tool-calculator", state: "output-available", toolCallId: "call_123", input: { operation: "add", a: 2, b: 3 }, output: { result: 5 }, }); const textPart = combined[0].parts.find((p) => p.type === "text"); expect(textPart).toMatchObject({ type: "text", text: "The result is 5.", state: "done", }); }); it("preserves separate messages with different roles", () => { const userMessage = { id: "user1", key: "user1-key", _creationTime: Date.now(), order: 1, stepOrder: 0, status: "success" as const, role: "user" as const, text: "Calculate 2 + 3", parts: [{ type: "text" as const, text: "Calculate 2 + 3" }], }; const assistantMessage = { id: "assistant1", key: "assistant1-key", _creationTime: Date.now() + 1, order: 2, stepOrder: 0, status: "success" as const, role: "assistant" as const, text: "The result is 5.", parts: [ { type: "text" as const, text: "The result is 5.", state: "done" as const, }, ], }; const combined = combineUIMessages([userMessage, assistantMessage]); expect(combined).toHaveLength(2); expect(combined[0]).toEqual(userMessage); expect(combined[1]).toEqual(assistantMessage); }); it("combines multiple tool calls across pages", () => { const message1 = { id: "msg1", key: "msg1-key", _creationTime: Date.now(), order: 1, stepOrder: 1, status: "success" as const, role: "assistant" as const, text: "", parts: [ { type: "dynamic-tool" as const, state: "input-available" as const, toolCallId: "call_1", toolName: "calculator", input: { operation: "add", a: 2, b: 3 }, }, { type: "dynamic-tool" as const, state: "input-available" as const, toolCallId: "call_2", toolName: "formatter", input: { text: "result" }, }, ], }; const message2 = { id: "msg2", key: "msg2-key", _creationTime: Date.now() + 1, order: 1, stepOrder: 2, status: "success" as const, role: "assistant" as const, text: "The formatted result is: 5", parts: [ { type: "tool-calculator" as const, state: "output-available" as const, toolCallId: "call_1", input: { operation: "add", a: 2, b: 3 }, output: { result: 5 }, }, { type: "tool-formatter" as const, state: "output-available" as const, toolCallId: "call_2", input: { text: "result" }, output: { formatted: "The formatted result is: 5" }, }, { type: "text" as const, text: "The formatted result is: 5", state: "done" as const, }, ], }; const combined = combineUIMessages([message1, message2]); expect(combined).toHaveLength(1); expect(combined[0].role).toBe("assistant"); expect(combined[0].text).toBe("The formatted result is: 5"); expect(combined[0].parts).toHaveLength(3); const calculatorPart = combined[0].parts.find( (p) => p.type === "tool-calculator" && "toolCallId" in p && p.toolCallId === "call_1", ); expect(calculatorPart).toMatchObject({ type: "tool-calculator", state: "output-available", toolCallId: "call_1", input: { operation: "add", a: 2, b: 3 }, output: { result: 5 }, }); const formatterPart = combined[0].parts.find( (p) => p.type === "tool-formatter" && "toolCallId" in p && p.toolCallId === "call_2", ); expect(formatterPart).toMatchObject({ type: "tool-formatter", state: "output-available", toolCallId: "call_2", input: { text: "result" }, output: { formatted: "The formatted result is: 5" }, }); }); it("handles tool call without corresponding output", () => { const message1 = { id: "msg1", key: "msg1-key", _creationTime: Date.now(), order: 1, stepOrder: 1, status: "success" as const, role: "assistant" as const, text: "", parts: [ { type: "dynamic-tool" as const, state: "input-available" as const, toolCallId: "call_orphan", toolName: "calculator", input: { operation: "add", a: 2, b: 3 }, }, ], }; const message2 = { id: "msg2", key: "msg2-key", _creationTime: Date.now() + 1, order: 1, stepOrder: 2, status: "success" as const, role: "assistant" as const, text: "Still processing...", parts: [ { type: "text" as const, text: "Still processing...", state: "done" as const, }, ], }; const combined = combineUIMessages([message1, message2]); expect(combined).toHaveLength(1); expect(combined[0].role).toBe("assistant"); expect(combined[0].text).toBe("Still processing..."); expect(combined[0].parts).toHaveLength(2); const toolPart = combined[0].parts.find((p) => p.type === "dynamic-tool"); expect(toolPart).toMatchObject({ type: "dynamic-tool", state: "input-available", toolCallId: "call_orphan", toolName: "calculator", input: { operation: "add", a: 2, b: 3 }, }); const textPart = combined[0].parts.find((p) => p.type === "text"); expect(textPart).toMatchObject({ type: "text", text: "Still processing...", state: "done", }); }); });