@tanstack/ai
Version:
Core TanStack AI library - Open source AI SDK
234 lines (233 loc) • 6.88 kB
JavaScript
function isContentPart(part) {
return part.type === "text" || part.type === "image" || part.type === "audio" || part.type === "video" || part.type === "document";
}
function collapseContentParts(parts) {
if (parts.length === 0) return null;
const allText = parts.every((p) => p.type === "text");
if (allText) {
const joined = parts.map((p) => p.content).join("");
return joined || null;
}
return parts;
}
function getTextContent(content) {
if (content === null) return "";
if (typeof content === "string") return content;
return content.filter((part) => part.type === "text").map((part) => part.content).join("");
}
function convertMessagesToModelMessages(messages) {
const modelMessages = [];
for (const msg of messages) {
if ("parts" in msg) {
modelMessages.push(...uiMessageToModelMessages(msg));
} else {
modelMessages.push(msg);
}
}
return modelMessages;
}
function uiMessageToModelMessages(uiMessage) {
if (uiMessage.role === "system") {
return [];
}
if (uiMessage.role !== "assistant") {
return [buildUserOrToolMessage(uiMessage)];
}
return buildAssistantMessages(uiMessage);
}
function buildUserOrToolMessage(uiMessage) {
const contentParts = [];
for (const part of uiMessage.parts) {
if (isContentPart(part)) {
contentParts.push(part);
}
}
return {
role: uiMessage.role,
content: collapseContentParts(contentParts)
};
}
function createSegment() {
return { contentParts: [], toolCalls: [] };
}
function isToolCallIncluded(part) {
return part.state === "input-complete" || part.state === "approval-responded" || part.output !== void 0;
}
function buildAssistantMessages(uiMessage) {
const messageList = [];
let current = createSegment();
const emittedToolResultIds = /* @__PURE__ */ new Set();
function flushSegment() {
const content = collapseContentParts(current.contentParts);
const hasContent = content !== null;
const hasToolCalls = current.toolCalls.length > 0;
if (hasContent || hasToolCalls) {
messageList.push({
role: "assistant",
content,
...hasToolCalls && { toolCalls: current.toolCalls }
});
}
current = createSegment();
}
for (const part of uiMessage.parts) {
switch (part.type) {
case "text":
case "image":
case "audio":
case "video":
case "document":
current.contentParts.push(part);
break;
case "tool-call":
if (isToolCallIncluded(part)) {
current.toolCalls.push({
id: part.id,
type: "function",
function: {
name: part.name,
arguments: part.arguments
}
});
}
break;
case "tool-result":
flushSegment();
if ((part.state === "complete" || part.state === "error") && !emittedToolResultIds.has(part.toolCallId)) {
messageList.push({
role: "tool",
content: part.content,
toolCallId: part.toolCallId
});
emittedToolResultIds.add(part.toolCallId);
}
break;
}
}
flushSegment();
for (const part of uiMessage.parts) {
if (part.type !== "tool-call") continue;
if (part.output !== void 0 && !emittedToolResultIds.has(part.id)) {
messageList.push({
role: "tool",
content: JSON.stringify(part.output),
toolCallId: part.id
});
emittedToolResultIds.add(part.id);
}
if (part.output === void 0 && part.state === "approval-responded" && part.approval?.approved !== void 0 && !emittedToolResultIds.has(part.id)) {
const approved = part.approval.approved;
messageList.push({
role: "tool",
content: JSON.stringify({
approved,
...approved && { pendingExecution: true },
message: approved ? "User approved this action" : "User denied this action"
}),
toolCallId: part.id
});
emittedToolResultIds.add(part.id);
}
}
if (messageList.length === 0) {
messageList.push({
role: "assistant",
content: null
});
}
return messageList;
}
function modelMessageToUIMessage(modelMessage, id) {
const parts = [];
if (modelMessage.role === "tool" && modelMessage.toolCallId) {
parts.push({
type: "tool-result",
toolCallId: modelMessage.toolCallId,
content: getTextContent(modelMessage.content),
state: "complete"
});
} else if (Array.isArray(modelMessage.content)) {
for (const part of modelMessage.content) {
parts.push(part);
}
} else {
const textContent = getTextContent(modelMessage.content);
if (textContent) {
parts.push({
type: "text",
content: textContent
});
}
}
if (modelMessage.toolCalls && modelMessage.toolCalls.length > 0) {
for (const toolCall of modelMessage.toolCalls) {
parts.push({
type: "tool-call",
id: toolCall.id,
name: toolCall.function.name,
arguments: toolCall.function.arguments,
state: "input-complete"
// Model messages have complete arguments
});
}
}
return {
id: id || generateMessageId(),
role: modelMessage.role === "tool" ? "assistant" : modelMessage.role,
parts
};
}
function modelMessagesToUIMessages(modelMessages) {
const uiMessages = [];
let currentAssistantMessage = null;
for (const msg of modelMessages) {
if (msg.role === "tool") {
if (currentAssistantMessage && currentAssistantMessage.role === "assistant") {
currentAssistantMessage.parts.push({
type: "tool-result",
toolCallId: msg.toolCallId,
content: getTextContent(msg.content),
state: "complete"
});
} else {
const toolResultUIMessage = modelMessageToUIMessage(msg);
uiMessages.push(toolResultUIMessage);
}
} else {
const uiMessage = modelMessageToUIMessage(msg);
uiMessages.push(uiMessage);
if (msg.role === "assistant") {
currentAssistantMessage = uiMessage;
} else {
currentAssistantMessage = null;
}
}
}
return uiMessages;
}
function normalizeToUIMessage(message, generateId) {
if ("parts" in message) {
return {
...message,
id: message.id || generateId(),
createdAt: message.createdAt || /* @__PURE__ */ new Date()
};
} else {
return {
...modelMessageToUIMessage(message, generateId()),
createdAt: /* @__PURE__ */ new Date()
};
}
}
function generateMessageId() {
return `msg-${Date.now()}-${Math.random().toString(36).substring(7)}`;
}
export {
convertMessagesToModelMessages,
generateMessageId,
modelMessageToUIMessage,
modelMessagesToUIMessages,
normalizeToUIMessage,
uiMessageToModelMessages
};
//# sourceMappingURL=messages.js.map