@convex-dev/agent
Version:
A agent component for Convex.
296 lines (285 loc) • 7.69 kB
text/typescript
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
});