@convex-dev/agent
Version:
A agent component for Convex.
146 lines • 5.99 kB
JavaScript
import { deserializeMessage, toUIFilePart } from "../mapping.js";
export function toUIMessages(messages) {
const uiMessages = [];
let assistantMessage;
for (const message of messages) {
const coreMessage = message.message && deserializeMessage(message.message);
const text = message.text ?? "";
const content = coreMessage?.content;
const nonStringContent = content && typeof content !== "string" ? content : [];
if (!coreMessage)
continue;
const common = {
id: message.id ?? message._id,
createdAt: new Date(message._creationTime),
order: message.order,
stepOrder: message.stepOrder,
status: message.streaming ? "streaming" : message.status,
key: `${message.threadId}-${message.order}-${message.stepOrder}`,
};
if (coreMessage.role === "system") {
uiMessages.push({
...common,
role: "system",
content: text,
parts: [{ type: "text", text }],
});
}
else if (coreMessage.role === "user") {
const parts = [];
if (text) {
parts.push({ type: "text", text });
}
nonStringContent.forEach((contentPart) => {
switch (contentPart.type) {
case "file":
case "image":
parts.push(toUIFilePart(contentPart));
break;
}
});
uiMessages.push({
...common,
role: "user",
content: message.text ?? "",
parts,
});
}
else {
if (coreMessage.role === "tool" && !assistantMessage) {
console.warn("Tool message without preceding assistant message.. skipping", message);
continue;
}
if (!assistantMessage) {
assistantMessage = {
...common,
role: "assistant",
content: "",
parts: [],
};
uiMessages.push(assistantMessage);
}
else {
assistantMessage.status = message.streaming
? "streaming"
: message.status;
}
// update it to the last message's id
assistantMessage.id = message.id ?? message._id;
if (message.text) {
assistantMessage.parts.push({
type: "text",
text: message.text,
});
assistantMessage.content += message.text;
}
if (message.reasoning) {
assistantMessage.parts.push({
type: "reasoning",
reasoning: message.reasoning,
details: message.reasoningDetails ?? [],
});
}
for (const source of message.sources ?? []) {
assistantMessage.parts.push({
type: "source",
source,
});
}
for (const contentPart of nonStringContent) {
switch (contentPart.type) {
case "file":
case "image":
assistantMessage.parts.push(toUIFilePart(contentPart));
break;
case "tool-call":
assistantMessage.parts.push({
type: "step-start",
});
assistantMessage.parts.push({
type: "tool-invocation",
toolInvocation: {
state: "call",
step: assistantMessage.parts.filter((part) => part.type === "tool-invocation").length,
toolCallId: contentPart.toolCallId,
toolName: contentPart.toolName,
args: contentPart.args,
},
});
break;
case "tool-result": {
const call = assistantMessage.parts.find((part) => part.type === "tool-invocation" &&
part.toolInvocation.toolCallId === contentPart.toolCallId);
const toolInvocation = {
state: "result",
toolCallId: contentPart.toolCallId,
toolName: contentPart.toolName,
args: call?.toolInvocation.args,
result: contentPart.result,
step: call?.toolInvocation.step ??
assistantMessage.parts.filter((part) => part.type === "tool-invocation").length,
};
if (call) {
call.toolInvocation = toolInvocation;
}
else {
console.warn("Tool result without preceding tool call.. adding anyways", contentPart);
assistantMessage.parts.push({
type: "tool-invocation",
toolInvocation,
});
}
break;
}
}
}
}
if (!message.tool &&
assistantMessage &&
assistantMessage.parts.length > 0) {
// Reset it so the next set of tool calls will create a new assistant message
assistantMessage = undefined;
}
}
return uiMessages;
}
//# sourceMappingURL=toUIMessages.js.map