UNPKG

@convex-dev/agent

Version:

A agent component for Convex.

296 lines (285 loc) 7.69 kB
import { describe, it, expect } from "vitest"; import { toUIMessages } from "./toUIMessages.js"; import type { MessageDoc } from "../client/index.js"; // Helper to create a base message doc function baseMessageDoc(overrides: Partial<MessageDoc> = {}): MessageDoc { return { _id: "msg1", _creationTime: Date.now(), order: 1, stepOrder: 0, status: "success", threadId: "thread1", tool: false, ...overrides, }; } describe("toUIMessages", () => { it("handles user message", () => { const messages = [ baseMessageDoc({ message: { role: "user", content: "Hello!", }, text: "Hello!", }), ]; const uiMessages = toUIMessages(messages); expect(uiMessages).toHaveLength(1); expect(uiMessages[0].role).toBe("user"); expect(uiMessages[0].content).toBe("Hello!"); expect(uiMessages[0].parts[0]).toEqual({ type: "text", text: "Hello!" }); }); it("handles assistant message", () => { const messages = [ baseMessageDoc({ message: { role: "assistant", content: "Hi, how can I help?", }, text: "Hi, how can I help?", }), ]; const uiMessages = toUIMessages(messages); expect(uiMessages).toHaveLength(1); expect(uiMessages[0].role).toBe("assistant"); expect(uiMessages[0].content).toBe("Hi, how can I help?"); expect(uiMessages[0].parts[0]).toEqual({ type: "text", text: "Hi, how can I help?", }); }); it("handles multiple messages", () => { const messages = [ baseMessageDoc({ message: { role: "user", content: "Hello!", }, text: "Hello!", }), baseMessageDoc({ message: { role: "assistant", content: [ { type: "reasoning", text: "I'm thinking...", }, { type: "redacted-reasoning", data: "asdfasdfasdf", }, { type: "text", text: "I'm thinking...", }, { type: "file", mimeType: "text/plain", data: "asdfasdfasdf", }, { type: "tool-call", toolName: "myTool", toolCallId: "call1", args: "", }, ], }, tool: true, reasoning: "I'm thinking...", text: "I'm thinking...", }), baseMessageDoc({ message: { role: "tool", content: [ { type: "tool-result", toolCallId: "call1", toolName: "myTool", result: "42", }, ], }, text: "42", tool: true, }), ]; const uiMessages = toUIMessages(messages); expect(uiMessages).toHaveLength(2); expect(uiMessages[0].role).toBe("user"); expect(uiMessages[0].parts.filter((p) => p.type === "text")).toHaveLength( 1 ); expect(uiMessages[1].role).toBe("assistant"); expect( uiMessages[1].parts.filter((p) => p.type === "tool-invocation") ).toHaveLength(1); expect( uiMessages[1].parts.filter((p) => p.type === "tool-invocation")[0] .toolInvocation ).toEqual({ toolName: "myTool", toolCallId: "call1", args: "", state: "result", result: "42", step: 0, }); }); it("handles multiple text and reasoning parts", () => { const messages = [ baseMessageDoc({ message: { role: "assistant", content: [ { type: "reasoning", text: "I'm thinking...", }, { type: "text", text: "Here's one idea.", }, { type: "reasoning", text: "I'm thinking...", }, { type: "text", text: "Here's another idea.", }, ], }, reasoning: "I'm thinking...I'm thinking...", text: "Here's one idea. Here's another idea.", }), ]; const uiMessages = toUIMessages(messages); expect(uiMessages).toHaveLength(1); expect(uiMessages[0].role).toBe("assistant"); expect(uiMessages[0].content).toBe("Here's one idea. Here's another idea."); expect( uiMessages[0].parts.filter((p) => p.type === "reasoning") ).toHaveLength(1); expect(uiMessages[0].parts.filter((p) => p.type === "text")).toHaveLength( 1 ); expect(uiMessages[0].parts.filter((p) => p.type === "text")[0].text).toBe( "Here's one idea. Here's another idea." ); }); it("handles system message", () => { const messages = [ baseMessageDoc({ message: { role: "system", content: "System message here", }, text: "System message here", }), ]; const uiMessages = toUIMessages(messages); expect(uiMessages).toHaveLength(1); expect(uiMessages[0].role).toBe("system"); expect(uiMessages[0].content).toBe("System message here"); expect(uiMessages[0].parts[0]).toEqual({ type: "text", text: "System message here", }); }); it("handles tool call", () => { const messages = [ baseMessageDoc({ message: { role: "assistant", content: [ { type: "tool-call", toolName: "myTool", toolCallId: "call1", args: "", }, ], }, text: "", }), ]; const uiMessages = toUIMessages(messages); expect(uiMessages).toHaveLength(1); expect(uiMessages[0].role).toBe("assistant"); expect( uiMessages[0].parts.filter((p) => p.type === "tool-invocation") ).toHaveLength(1); expect( uiMessages[0].parts.filter((p) => p.type === "tool-invocation")[0] .toolInvocation ).toEqual({ toolName: "myTool", toolCallId: "call1", args: "", state: "call", step: 0, }); }); it("handles tool result", () => { const messages = [ baseMessageDoc({ tool: true, message: { role: "assistant", content: [ { type: "tool-call", toolName: "myTool", toolCallId: "call1", args: "", }, ], }, text: "", }), baseMessageDoc({ message: { role: "tool", content: [ { type: "tool-result", toolCallId: "call1", toolName: "myTool", result: "42", }, ], }, text: "", }), ]; const uiMessages = toUIMessages(messages); expect(uiMessages).toHaveLength(1); expect(uiMessages[0].role).toBe("assistant"); // Should have a tool-invocation part expect(uiMessages[0].parts.some((p) => p.type === "tool-invocation")).toBe( true ); }); it("does not duplicate text content", () => { const messages = [ baseMessageDoc({ message: { role: "assistant", content: "Hello!", }, text: "Hello!", }), ]; const uiMessages = toUIMessages(messages); // There should only be one text part const textParts = uiMessages[0].parts.filter((p) => p.type === "text"); expect(textParts).toHaveLength(1); expect(textParts[0].text).toBe("Hello!"); }); // Add more tests for array content, tool calls, etc. as needed });