UNPKG

@mastra/core

Version:

The core foundation of the Mastra framework, providing essential components and interfaces for building AI-powered applications.

1,158 lines (1,153 loc) • 43.7 kB
'use strict'; var chunkEQ7QKYYD_cjs = require('./chunk-EQ7QKYYD.cjs'); var chunk5K6WYRP2_cjs = require('./chunk-5K6WYRP2.cjs'); var crypto = require('crypto'); var ai = require('ai'); var providerUtils = require('@ai-sdk/provider-utils'); var zod = require('zod'); // src/agent/message-list/prompt/attachments-to-parts.ts function attachmentsToParts(attachments) { const parts = []; for (const attachment of attachments) { let url; try { url = new URL(attachment.url); } catch { throw new Error(`Invalid URL: ${attachment.url}`); } switch (url.protocol) { case "http:": case "https:": { if (attachment.contentType?.startsWith("image/")) { parts.push({ type: "image", image: url.toString(), mimeType: attachment.contentType }); } else { if (!attachment.contentType) { throw new Error("If the attachment is not an image, it must specify a content type"); } parts.push({ type: "file", data: url.toString(), mimeType: attachment.contentType }); } break; } case "data:": { if (attachment.contentType?.startsWith("image/")) { parts.push({ type: "image", image: attachment.url, mimeType: attachment.contentType }); } else if (attachment.contentType?.startsWith("text/")) { parts.push({ type: "file", data: attachment.url, mimeType: attachment.contentType }); } else { if (!attachment.contentType) { throw new Error("If the attachment is not an image or text, it must specify a content type"); } parts.push({ type: "file", data: attachment.url, mimeType: attachment.contentType }); } break; } default: { throw new Error(`Unsupported URL protocol: ${url.protocol}`); } } } return parts; } // src/agent/message-list/prompt/convert-to-mastra-v1.ts var makePushOrCombine = (v1Messages) => (msg) => { const previousMessage = v1Messages.at(-1); if (msg.role === previousMessage?.role && Array.isArray(previousMessage.content) && Array.isArray(msg.content) && // we were creating new messages for tool calls before and not appending to the assistant message // so don't append here so everything works as before (msg.role !== `assistant` || msg.role === `assistant` && msg.content.at(-1)?.type !== `tool-call`)) { for (const part of msg.content) { previousMessage.content.push(part); } } else { v1Messages.push(msg); } }; function convertToV1Messages(messages) { const v1Messages = []; const pushOrCombine = makePushOrCombine(v1Messages); for (let i = 0; i < messages.length; i++) { const message = messages[i]; const isLastMessage = i === messages.length - 1; if (!message?.content) continue; const { content, experimental_attachments: inputAttachments = [], parts: inputParts } = message.content; const { role } = message; const fields = { id: message.id, createdAt: message.createdAt, resourceId: message.resourceId, threadId: message.threadId }; const experimental_attachments = [...inputAttachments]; const parts = []; for (const part of inputParts) { if (part.type === "file") { experimental_attachments.push({ url: part.data, contentType: part.mimeType }); } else { parts.push(part); } } switch (role) { case "user": { if (parts == null) { const userContent = experimental_attachments ? [{ type: "text", text: content || "" }, ...attachmentsToParts(experimental_attachments)] : { type: "text", text: content || "" }; pushOrCombine({ role: "user", ...fields, type: "text", // @ts-ignore content: userContent }); } else { const textParts = message.content.parts.filter((part) => part.type === "text").map((part) => ({ type: "text", text: part.text })); const userContent = experimental_attachments ? [...textParts, ...attachmentsToParts(experimental_attachments)] : textParts; pushOrCombine({ role: "user", ...fields, type: "text", content: Array.isArray(userContent) && userContent.length === 1 && userContent[0]?.type === `text` && typeof content !== `undefined` ? content : userContent }); } break; } case "assistant": { if (message.content.parts != null) { let processBlock2 = function() { const content2 = []; for (const part of block) { switch (part.type) { case "file": case "text": { content2.push(part); break; } case "reasoning": { for (const detail of part.details) { switch (detail.type) { case "text": content2.push({ type: "reasoning", text: detail.text, signature: detail.signature }); break; case "redacted": content2.push({ type: "redacted-reasoning", data: detail.data }); break; } } break; } case "tool-invocation": if (part.toolInvocation.toolName !== "updateWorkingMemory") { content2.push({ type: "tool-call", toolCallId: part.toolInvocation.toolCallId, toolName: part.toolInvocation.toolName, args: part.toolInvocation.args }); } break; } } pushOrCombine({ role: "assistant", ...fields, type: content2.some((c) => c.type === `tool-call`) ? "tool-call" : "text", // content: content, content: typeof content2 !== `string` && Array.isArray(content2) && content2.length === 1 && content2[0]?.type === `text` ? message?.content?.content || content2 : content2 }); const stepInvocations = block.filter((part) => `type` in part && part.type === "tool-invocation").map((part) => part.toolInvocation).filter((ti) => ti.toolName !== "updateWorkingMemory"); const invocationsWithResults = stepInvocations.filter((ti) => ti.state === "result" && "result" in ti); if (invocationsWithResults.length > 0) { pushOrCombine({ role: "tool", ...fields, type: "tool-result", content: invocationsWithResults.map((toolInvocation) => { const { toolCallId, toolName, result } = toolInvocation; return { type: "tool-result", toolCallId, toolName, result }; }) }); } block = []; blockHasToolInvocations = false; currentStep++; }; let currentStep = 0; let blockHasToolInvocations = false; let block = []; for (const part of message.content.parts) { switch (part.type) { case "text": { if (blockHasToolInvocations) { processBlock2(); } block.push(part); break; } case "file": case "reasoning": { block.push(part); break; } case "tool-invocation": { if ((part.toolInvocation.step ?? 0) !== currentStep) { processBlock2(); } block.push(part); blockHasToolInvocations = true; break; } } } processBlock2(); const toolInvocations2 = message.content.toolInvocations; if (toolInvocations2 && toolInvocations2.length > 0) { const processedToolCallIds = /* @__PURE__ */ new Set(); for (const part of message.content.parts) { if (part.type === "tool-invocation" && part.toolInvocation.toolCallId) { processedToolCallIds.add(part.toolInvocation.toolCallId); } } const unprocessedToolInvocations = toolInvocations2.filter( (ti) => !processedToolCallIds.has(ti.toolCallId) && ti.toolName !== "updateWorkingMemory" ); if (unprocessedToolInvocations.length > 0) { const invocationsByStep = /* @__PURE__ */ new Map(); for (const inv of unprocessedToolInvocations) { const step = inv.step ?? 0; if (!invocationsByStep.has(step)) { invocationsByStep.set(step, []); } invocationsByStep.get(step).push(inv); } const sortedSteps = Array.from(invocationsByStep.keys()).sort((a, b) => a - b); for (const step of sortedSteps) { const stepInvocations = invocationsByStep.get(step); pushOrCombine({ role: "assistant", ...fields, type: "tool-call", content: [ ...stepInvocations.map(({ toolCallId, toolName, args }) => ({ type: "tool-call", toolCallId, toolName, args })) ] }); const invocationsWithResults = stepInvocations.filter((ti) => ti.state === "result" && "result" in ti); if (invocationsWithResults.length > 0) { pushOrCombine({ role: "tool", ...fields, type: "tool-result", content: invocationsWithResults.map((toolInvocation) => { const { toolCallId, toolName, result } = toolInvocation; return { type: "tool-result", toolCallId, toolName, result }; }) }); } } } } break; } const toolInvocations = message.content.toolInvocations; if (toolInvocations == null || toolInvocations.length === 0) { pushOrCombine({ role: "assistant", ...fields, content: content || "", type: "text" }); break; } const maxStep = toolInvocations.reduce((max, toolInvocation) => { return Math.max(max, toolInvocation.step ?? 0); }, 0); for (let i2 = 0; i2 <= maxStep; i2++) { const stepInvocations = toolInvocations.filter( (toolInvocation) => (toolInvocation.step ?? 0) === i2 && toolInvocation.toolName !== "updateWorkingMemory" ); if (stepInvocations.length === 0) { continue; } pushOrCombine({ role: "assistant", ...fields, type: "tool-call", content: [ ...isLastMessage && content && i2 === 0 ? [{ type: "text", text: content }] : [], ...stepInvocations.map(({ toolCallId, toolName, args }) => ({ type: "tool-call", toolCallId, toolName, args })) ] }); const invocationsWithResults = stepInvocations.filter((ti) => ti.state === "result" && "result" in ti); if (invocationsWithResults.length > 0) { pushOrCombine({ role: "tool", ...fields, type: "tool-result", content: invocationsWithResults.map((toolInvocation) => { const { toolCallId, toolName, result } = toolInvocation; return { type: "tool-result", toolCallId, toolName, result }; }) }); } } if (content && !isLastMessage) { pushOrCombine({ role: "assistant", ...fields, type: "text", content: content || "" }); } break; } } } return v1Messages; } zod.z.union([ zod.z.string(), zod.z.instanceof(Uint8Array), zod.z.instanceof(ArrayBuffer), zod.z.custom( // Buffer might not be available in some environments such as CloudFlare: (value) => globalThis.Buffer?.isBuffer(value) ?? false, { message: "Must be a Buffer" } ) ]); function convertDataContentToBase64String(content) { if (typeof content === "string") { return content; } if (content instanceof ArrayBuffer) { return providerUtils.convertUint8ArrayToBase64(new Uint8Array(content)); } return providerUtils.convertUint8ArrayToBase64(content); } // src/agent/message-list/index.ts var MessageList = class _MessageList { messages = []; // passed in by dev in input or context systemMessages = []; // passed in by us for a specific purpose, eg memory system message taggedSystemMessages = {}; memoryInfo = null; // used to filter this.messages by how it was added: input/response/memory memoryMessages = /* @__PURE__ */ new Set(); newUserMessages = /* @__PURE__ */ new Set(); newResponseMessages = /* @__PURE__ */ new Set(); userContextMessages = /* @__PURE__ */ new Set(); generateMessageId; _agentNetworkAppend = false; constructor({ threadId, resourceId, generateMessageId, // @ts-ignore Flag for agent network messages _agentNetworkAppend } = {}) { if (threadId) { this.memoryInfo = { threadId, resourceId }; this.generateMessageId = generateMessageId; } this._agentNetworkAppend = _agentNetworkAppend || false; } add(messages, messageSource) { if (!messages) return this; for (const message of Array.isArray(messages) ? messages : [messages]) { this.addOne( typeof message === `string` ? { role: "user", content: message } : message, messageSource ); } return this; } getLatestUserContent() { const currentUserMessages = this.all.core().filter((m) => m.role === "user"); const content = currentUserMessages.at(-1)?.content; if (!content) return null; return _MessageList.coreContentToString(content); } get get() { return { all: this.all, remembered: this.remembered, input: this.input, response: this.response }; } get clear() { return { input: { v2: () => { const userMessages = Array.from(this.newUserMessages); this.messages = this.messages.filter((m) => !this.newUserMessages.has(m)); this.newUserMessages.clear(); return userMessages; } } }; } all = { v2: () => this.messages, v1: () => convertToV1Messages(this.messages), ui: () => this.messages.map(_MessageList.toUIMessage), core: () => this.convertToCoreMessages(this.all.ui()), prompt: () => { const coreMessages = this.all.core(); const messages = [...this.systemMessages, ...Object.values(this.taggedSystemMessages).flat(), ...coreMessages]; const needsDefaultUserMessage = !messages.length || messages[0]?.role === "assistant"; if (needsDefaultUserMessage) { const defaultMessage = { role: "user", content: "." }; messages.unshift(defaultMessage); } return messages; } }; remembered = { v2: () => this.messages.filter((m) => this.memoryMessages.has(m)), v1: () => convertToV1Messages(this.remembered.v2()), ui: () => this.remembered.v2().map(_MessageList.toUIMessage), core: () => this.convertToCoreMessages(this.remembered.ui()) }; input = { v2: () => this.messages.filter((m) => this.newUserMessages.has(m)), v1: () => convertToV1Messages(this.input.v2()), ui: () => this.input.v2().map(_MessageList.toUIMessage), core: () => this.convertToCoreMessages(this.input.ui()) }; response = { v2: () => this.messages.filter((m) => this.newResponseMessages.has(m)) }; drainUnsavedMessages() { const messages = this.messages.filter((m) => this.newUserMessages.has(m) || this.newResponseMessages.has(m)); this.newUserMessages.clear(); this.newResponseMessages.clear(); return messages; } getEarliestUnsavedMessageTimestamp() { const unsavedMessages = this.messages.filter((m) => this.newUserMessages.has(m) || this.newResponseMessages.has(m)); if (unsavedMessages.length === 0) return void 0; return Math.min(...unsavedMessages.map((m) => new Date(m.createdAt).getTime())); } getSystemMessages(tag) { if (tag) { return this.taggedSystemMessages[tag] || []; } return this.systemMessages; } addSystem(messages, tag) { if (!messages) return this; for (const message of Array.isArray(messages) ? messages : [messages]) { this.addOneSystem(message, tag); } return this; } convertToCoreMessages(messages) { return ai.convertToCoreMessages(this.sanitizeUIMessages(messages)); } sanitizeUIMessages(messages) { const msgs = messages.map((m) => { if (m.parts.length === 0) return false; const safeParts = m.parts.filter( (p) => p.type !== `tool-invocation` || // calls and partial-calls should be updated to be results at this point // if they haven't we can't send them back to the llm and need to remove them. p.toolInvocation.state !== `call` && p.toolInvocation.state !== `partial-call` ); if (!safeParts.length) return false; const sanitized = { ...m, parts: safeParts }; if (`toolInvocations` in m && m.toolInvocations) { sanitized.toolInvocations = m.toolInvocations.filter((t) => t.state === `result`); } return sanitized; }).filter((m) => Boolean(m)); return msgs; } addOneSystem(message, tag) { if (typeof message === `string`) message = { role: "system", content: message }; if (tag && !this.isDuplicateSystem(message, tag)) { this.taggedSystemMessages[tag] ||= []; this.taggedSystemMessages[tag].push(message); } else if (!this.isDuplicateSystem(message)) { this.systemMessages.push(message); } } isDuplicateSystem(message, tag) { if (tag) { if (!this.taggedSystemMessages[tag]) return false; return this.taggedSystemMessages[tag].some( (m) => _MessageList.cacheKeyFromContent(m.content) === _MessageList.cacheKeyFromContent(message.content) ); } return this.systemMessages.some( (m) => _MessageList.cacheKeyFromContent(m.content) === _MessageList.cacheKeyFromContent(message.content) ); } static toUIMessage(m) { const experimentalAttachments = m.content.experimental_attachments ? [...m.content.experimental_attachments] : []; const contentString = typeof m.content.content === `string` && m.content.content !== "" ? m.content.content : m.content.parts.reduce((prev, part) => { if (part.type === `text`) { return part.text; } return prev; }, ""); const parts = []; if (m.content.parts.length) { for (const part of m.content.parts) { if (part.type === `file`) { experimentalAttachments.push({ contentType: part.mimeType, url: part.data }); } else if (part.type === "tool-invocation" && (part.toolInvocation.state === "call" || part.toolInvocation.state === "partial-call")) { continue; } else { parts.push(part); } } } if (parts.length === 0 && experimentalAttachments.length > 0) { parts.push({ type: "text", text: "" }); } if (m.role === `user`) { const uiMessage2 = { id: m.id, role: m.role, content: m.content.content || contentString, createdAt: m.createdAt, parts, experimental_attachments: experimentalAttachments }; if (m.content.metadata) { uiMessage2.metadata = m.content.metadata; } return uiMessage2; } else if (m.role === `assistant`) { const uiMessage2 = { id: m.id, role: m.role, content: m.content.content || contentString, createdAt: m.createdAt, parts, reasoning: void 0, toolInvocations: `toolInvocations` in m.content ? m.content.toolInvocations?.filter((t) => t.state === "result") : void 0 }; if (m.content.metadata) { uiMessage2.metadata = m.content.metadata; } return uiMessage2; } const uiMessage = { id: m.id, role: m.role, content: m.content.content || contentString, createdAt: m.createdAt, parts, experimental_attachments: experimentalAttachments }; if (m.content.metadata) { uiMessage.metadata = m.content.metadata; } return uiMessage; } getMessageById(id) { return this.messages.find((m) => m.id === id); } shouldReplaceMessage(message) { if (!this.messages.length) return { exists: false }; if (!(`id` in message) || !message?.id) { return { exists: false }; } const existingMessage = this.getMessageById(message.id); if (!existingMessage) return { exists: false }; return { exists: true, shouldReplace: !_MessageList.messagesAreEqual(existingMessage, message), id: existingMessage.id }; } addOne(message, messageSource) { if ((!(`content` in message) || !message.content && // allow empty strings typeof message.content !== "string") && (!(`parts` in message) || !message.parts)) { throw new chunk5K6WYRP2_cjs.MastraError({ id: "INVALID_MESSAGE_CONTENT", domain: "AGENT" /* AGENT */, category: "USER" /* USER */, text: `Message with role "${message.role}" must have either a 'content' property (string or array) or a 'parts' property (array) that is not empty, null, or undefined. Received message: ${JSON.stringify(message, null, 2)}`, details: { role: message.role, messageSource, hasContent: "content" in message, hasParts: "parts" in message } }); } if (message.role === `system` && _MessageList.isVercelCoreMessage(message)) return this.addSystem(message); if (message.role === `system`) { throw new chunk5K6WYRP2_cjs.MastraError({ id: "INVALID_SYSTEM_MESSAGE_FORMAT", domain: "AGENT" /* AGENT */, category: "USER" /* USER */, text: `Invalid system message format. System messages must be CoreMessage format with 'role' and 'content' properties. The content should be a string or valid content array.`, details: { messageSource, receivedMessage: JSON.stringify(message, null, 2) } }); } const messageV2 = this.inputToMastraMessageV2(message, messageSource); const { exists, shouldReplace, id } = this.shouldReplaceMessage(messageV2); const latestMessage = this.messages.at(-1); if (messageSource === `memory`) { for (const existingMessage of this.messages) { if (_MessageList.messagesAreEqual(existingMessage, messageV2)) { return; } } } const shouldAppendToLastAssistantMessage = latestMessage?.role === "assistant" && messageV2.role === "assistant" && latestMessage.threadId === messageV2.threadId && // If the message is from memory, don't append to the last assistant message messageSource !== "memory"; const appendNetworkMessage = this._agentNetworkAppend && latestMessage && !this.memoryMessages.has(latestMessage) || !this._agentNetworkAppend; if (shouldAppendToLastAssistantMessage && appendNetworkMessage) { latestMessage.createdAt = messageV2.createdAt || latestMessage.createdAt; const toolResultAnchorMap = /* @__PURE__ */ new Map(); const partsToAdd = /* @__PURE__ */ new Map(); for (const [index, part] of messageV2.content.parts.entries()) { if (part.type === "tool-invocation") { const existingCallPart = [...latestMessage.content.parts].reverse().find((p) => p.type === "tool-invocation" && p.toolInvocation.toolCallId === part.toolInvocation.toolCallId); const existingCallToolInvocation = !!existingCallPart && existingCallPart.type === "tool-invocation"; if (existingCallToolInvocation) { if (part.toolInvocation.state === "result") { existingCallPart.toolInvocation = { ...existingCallPart.toolInvocation, step: part.toolInvocation.step, state: "result", result: part.toolInvocation.result, args: { ...existingCallPart.toolInvocation.args, ...part.toolInvocation.args } }; if (!latestMessage.content.toolInvocations) { latestMessage.content.toolInvocations = []; } const toolInvocationIndex = latestMessage.content.toolInvocations.findIndex( (t) => t.toolCallId === existingCallPart.toolInvocation.toolCallId ); if (toolInvocationIndex === -1) { latestMessage.content.toolInvocations.push(existingCallPart.toolInvocation); } else { latestMessage.content.toolInvocations[toolInvocationIndex] = existingCallPart.toolInvocation; } } const existingIndex = latestMessage.content.parts.findIndex((p) => p === existingCallPart); toolResultAnchorMap.set(index, existingIndex); } else { partsToAdd.set(index, part); } } else { partsToAdd.set(index, part); } } this.addPartsToLatestMessage({ latestMessage, messageV2, anchorMap: toolResultAnchorMap, partsToAdd }); if (latestMessage.createdAt.getTime() < messageV2.createdAt.getTime()) { latestMessage.createdAt = messageV2.createdAt; } if (!latestMessage.content.content && messageV2.content.content) { latestMessage.content.content = messageV2.content.content; } if (latestMessage.content.content && messageV2.content.content && latestMessage.content.content !== messageV2.content.content) { latestMessage.content.content = messageV2.content.content; } this.pushMessageToSource(latestMessage, messageSource); } else { let existingIndex = -1; if (shouldReplace) { existingIndex = this.messages.findIndex((m) => m.id === id); } const existingMessage = existingIndex !== -1 && this.messages[existingIndex]; if (shouldReplace && existingMessage) { this.messages[existingIndex] = messageV2; } else if (!exists) { this.messages.push(messageV2); } this.pushMessageToSource(messageV2, messageSource); } this.messages.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime()); return this; } pushMessageToSource(messageV2, messageSource) { if (messageSource === `memory`) { this.memoryMessages.add(messageV2); } else if (messageSource === `response`) { this.newResponseMessages.add(messageV2); } else if (messageSource === `user`) { this.newUserMessages.add(messageV2); } else if (messageSource === `context`) { this.userContextMessages.add(messageV2); } else { throw new Error(`Missing message source for message ${messageV2}`); } } /** * Pushes a new message part to the latest message. * @param latestMessage - The latest message to push the part to. * @param newMessage - The new message to push the part from. * @param part - The part to push. * @param insertAt - The index at which to insert the part. Optional. */ pushNewMessagePart({ latestMessage, newMessage, part, insertAt // optional }) { const partKey = _MessageList.cacheKeyFromParts([part]); const latestPartCount = latestMessage.content.parts.filter( (p) => _MessageList.cacheKeyFromParts([p]) === partKey ).length; const newPartCount = newMessage.content.parts.filter((p) => _MessageList.cacheKeyFromParts([p]) === partKey).length; if (latestPartCount < newPartCount) { if (typeof insertAt === "number") { latestMessage.content.parts.splice(insertAt, 0, part); } else { latestMessage.content.parts.push(part); } } } /** * Upserts parts of messageV2 into latestMessage based on the anchorMap. * This is used when appending a message to the last assistant message to ensure that parts are inserted in the correct order. * @param latestMessage - The latest message to upsert parts into. * @param messageV2 - The message to upsert parts from. * @param anchorMap - The anchor map to use for upserting parts. */ addPartsToLatestMessage({ latestMessage, messageV2, anchorMap, partsToAdd }) { for (let i = 0; i < messageV2.content.parts.length; ++i) { const part = messageV2.content.parts[i]; if (!part) continue; const key = _MessageList.cacheKeyFromParts([part]); const partToAdd = partsToAdd.get(i); if (!key || !partToAdd) continue; if (anchorMap.size > 0) { if (anchorMap.has(i)) continue; const leftAnchorV2 = [...anchorMap.keys()].filter((idx) => idx < i).pop() ?? -1; const rightAnchorV2 = [...anchorMap.keys()].find((idx) => idx > i) ?? -1; const leftAnchorLatest = leftAnchorV2 !== -1 ? anchorMap.get(leftAnchorV2) : 0; const offset = leftAnchorV2 === -1 ? i : i - leftAnchorV2; const insertAt = leftAnchorLatest + offset; const rightAnchorLatest = rightAnchorV2 !== -1 ? anchorMap.get(rightAnchorV2) : latestMessage.content.parts.length; if (insertAt >= 0 && insertAt <= rightAnchorLatest && !latestMessage.content.parts.slice(insertAt, rightAnchorLatest).some((p) => _MessageList.cacheKeyFromParts([p]) === _MessageList.cacheKeyFromParts([part]))) { this.pushNewMessagePart({ latestMessage, newMessage: messageV2, part, insertAt }); for (const [v2Idx, latestIdx] of anchorMap.entries()) { if (latestIdx >= insertAt) { anchorMap.set(v2Idx, latestIdx + 1); } } } } else { this.pushNewMessagePart({ latestMessage, newMessage: messageV2, part }); } } } inputToMastraMessageV2(message, messageSource) { if ( // we can't throw if the threadId doesn't match and this message came from memory // this is because per-user semantic recall can retrieve messages from other threads messageSource !== `memory` && `threadId` in message && message.threadId && this.memoryInfo && message.threadId !== this.memoryInfo.threadId ) { throw new Error( `Received input message with wrong threadId. Input ${message.threadId}, expected ${this.memoryInfo.threadId}` ); } if (`resourceId` in message && message.resourceId && this.memoryInfo?.resourceId && message.resourceId !== this.memoryInfo.resourceId) { throw new Error( `Received input message with wrong resourceId. Input ${message.resourceId}, expected ${this.memoryInfo.resourceId}` ); } if (_MessageList.isMastraMessageV1(message)) { return this.mastraMessageV1ToMastraMessageV2(message, messageSource); } if (_MessageList.isMastraMessageV2(message)) { return this.hydrateMastraMessageV2Fields(message); } if (_MessageList.isVercelCoreMessage(message)) { return this.vercelCoreMessageToMastraMessageV2(message, messageSource); } if (_MessageList.isVercelUIMessage(message)) { return this.vercelUIMessageToMastraMessageV2(message, messageSource); } throw new Error(`Found unhandled message ${JSON.stringify(message)}`); } lastCreatedAt; // this makes sure messages added in order will always have a date atleast 1ms apart. generateCreatedAt(messageSource, start) { start = start instanceof Date ? start : start ? new Date(start) : void 0; if (start && !this.lastCreatedAt) { this.lastCreatedAt = start.getTime(); return start; } if (start && messageSource === `memory`) { return start; } const now = /* @__PURE__ */ new Date(); const nowTime = start?.getTime() || now.getTime(); const lastTime = this.messages.reduce((p, m) => { if (m.createdAt.getTime() > p) return m.createdAt.getTime(); return p; }, this.lastCreatedAt || 0); if (nowTime <= lastTime) { const newDate = new Date(lastTime + 1); this.lastCreatedAt = newDate.getTime(); return newDate; } this.lastCreatedAt = nowTime; return now; } newMessageId() { if (this.generateMessageId) { return this.generateMessageId(); } return crypto.randomUUID(); } mastraMessageV1ToMastraMessageV2(message, messageSource) { const coreV2 = this.vercelCoreMessageToMastraMessageV2( { content: message.content, role: message.role }, messageSource ); return { id: message.id, role: coreV2.role, createdAt: this.generateCreatedAt(messageSource, message.createdAt), threadId: message.threadId, resourceId: message.resourceId, content: coreV2.content }; } hydrateMastraMessageV2Fields(message) { if (!(message.createdAt instanceof Date)) message.createdAt = new Date(message.createdAt); return message; } vercelUIMessageToMastraMessageV2(message, messageSource) { const content = { format: 2, parts: message.parts }; if (message.toolInvocations) content.toolInvocations = message.toolInvocations; if (message.reasoning) content.reasoning = message.reasoning; if (message.annotations) content.annotations = message.annotations; if (message.experimental_attachments) { content.experimental_attachments = message.experimental_attachments; } if ("metadata" in message && message.metadata !== null && message.metadata !== void 0) { content.metadata = message.metadata; } return { id: message.id || this.newMessageId(), role: _MessageList.getRole(message), createdAt: this.generateCreatedAt(messageSource, message.createdAt), threadId: this.memoryInfo?.threadId, resourceId: this.memoryInfo?.resourceId, content }; } vercelCoreMessageToMastraMessageV2(coreMessage, messageSource) { const id = `id` in coreMessage ? coreMessage.id : this.newMessageId(); const parts = []; const experimentalAttachments = []; const toolInvocations = []; if (typeof coreMessage.content === "string") { parts.push({ type: "step-start" }); parts.push({ type: "text", text: coreMessage.content }); } else if (Array.isArray(coreMessage.content)) { for (const part of coreMessage.content) { switch (part.type) { case "text": parts.push({ type: "text", text: part.text }); break; case "tool-call": parts.push({ type: "tool-invocation", toolInvocation: { state: "call", toolCallId: part.toolCallId, toolName: part.toolName, args: part.args } }); break; case "tool-result": const invocation = { state: "result", toolCallId: part.toolCallId, toolName: part.toolName, result: part.result ?? "", // undefined will cause AI SDK to throw an error, but for client side tool calls this really could be undefined args: {} // when we combine this invocation onto the existing tool-call part it will have args already }; parts.push({ type: "tool-invocation", toolInvocation: invocation }); toolInvocations.push(invocation); break; case "reasoning": parts.push({ type: "reasoning", reasoning: "", // leave this blank so we aren't double storing it in the db along with details details: [{ type: "text", text: part.text, signature: part.signature }] }); break; case "redacted-reasoning": parts.push({ type: "reasoning", reasoning: "", // No text reasoning for redacted parts details: [{ type: "redacted", data: part.data }] }); break; case "image": parts.push({ type: "file", data: part.image.toString(), mimeType: part.mimeType }); break; case "file": if (part.data instanceof URL) { parts.push({ type: "file", data: part.data.toString(), mimeType: part.mimeType }); } else { try { parts.push({ type: "file", mimeType: part.mimeType, data: convertDataContentToBase64String(part.data) }); } catch (error) { console.error(`Failed to convert binary data to base64 in CoreMessage file part: ${error}`, error); } } break; } } } const content = { format: 2, parts }; if (toolInvocations.length) content.toolInvocations = toolInvocations; if (typeof coreMessage.content === `string`) content.content = coreMessage.content; if (experimentalAttachments.length) content.experimental_attachments = experimentalAttachments; return { id, role: _MessageList.getRole(coreMessage), createdAt: this.generateCreatedAt(messageSource), threadId: this.memoryInfo?.threadId, resourceId: this.memoryInfo?.resourceId, content }; } static isVercelUIMessage(msg) { return !_MessageList.isMastraMessage(msg) && chunkEQ7QKYYD_cjs.isUiMessage(msg); } static isVercelCoreMessage(msg) { return !_MessageList.isMastraMessage(msg) && chunkEQ7QKYYD_cjs.isCoreMessage(msg); } static isMastraMessage(msg) { return _MessageList.isMastraMessageV2(msg) || _MessageList.isMastraMessageV1(msg); } static isMastraMessageV1(msg) { return !_MessageList.isMastraMessageV2(msg) && (`threadId` in msg || `resourceId` in msg); } static isMastraMessageV2(msg) { return Boolean( msg.content && !Array.isArray(msg.content) && typeof msg.content !== `string` && // any newly saved Mastra message v2 shape will have content: { format: 2 } `format` in msg.content && msg.content.format === 2 ); } static getRole(message) { if (message.role === `assistant` || message.role === `tool`) return `assistant`; if (message.role === `user`) return `user`; throw new Error( `BUG: add handling for message role ${message.role} in message ${JSON.stringify(message, null, 2)}` ); } static cacheKeyFromParts(parts) { let key = ``; for (const part of parts) { key += part.type; if (part.type === `text`) { key += `${part.text.length}${part.text}`; } if (part.type === `tool-invocation`) { key += part.toolInvocation.toolCallId; key += part.toolInvocation.state; } if (part.type === `reasoning`) { key += part.reasoning.length; key += part.details.reduce((prev, current) => { if (current.type === `text`) { return prev + current.text.length + (current.signature?.length || 0); } return prev; }, 0); } if (part.type === `file`) { key += part.data.length; key += part.mimeType; } } return key; } static coreContentToString(content) { if (typeof content === `string`) return content; return content.reduce((p, c) => { if (c.type === `text`) { p += c.text; } return p; }, ""); } static cacheKeyFromContent(content) { if (typeof content === `string`) return content; let key = ``; for (const part of content) { key += part.type; if (part.type === `text`) { key += part.text.length; } if (part.type === `reasoning`) { key += part.text.length; } if (part.type === `tool-call`) { key += part.toolCallId; key += part.toolName; } if (part.type === `tool-result`) { key += part.toolCallId; key += part.toolName; } if (part.type === `file`) { key += part.filename; key += part.mimeType; } if (part.type === `image`) { key += part.image instanceof URL ? part.image.toString() : part.image.toString().length; key += part.mimeType; } if (part.type === `redacted-reasoning`) { key += part.data.length; } } return key; } static messagesAreEqual(one, two) { const oneUI = _MessageList.isVercelUIMessage(one) && one; const twoUI = _MessageList.isVercelUIMessage(two) && two; if (oneUI && !twoUI) return false; if (oneUI && twoUI) { return _MessageList.cacheKeyFromParts(one.parts) === _MessageList.cacheKeyFromParts(two.parts); } const oneCM = _MessageList.isVercelCoreMessage(one) && one; const twoCM = _MessageList.isVercelCoreMessage(two) && two; if (oneCM && !twoCM) return false; if (oneCM && twoCM) { return _MessageList.cacheKeyFromContent(oneCM.content) === _MessageList.cacheKeyFromContent(twoCM.content); } const oneMM1 = _MessageList.isMastraMessageV1(one) && one; const twoMM1 = _MessageList.isMastraMessageV1(two) && two; if (oneMM1 && !twoMM1) return false; if (oneMM1 && twoMM1) { return oneMM1.id === twoMM1.id && _MessageList.cacheKeyFromContent(oneMM1.content) === _MessageList.cacheKeyFromContent(twoMM1.content); } const oneMM2 = _MessageList.isMastraMessageV2(one) && one; const twoMM2 = _MessageList.isMastraMessageV2(two) && two; if (oneMM2 && !twoMM2) return false; if (oneMM2 && twoMM2) { return oneMM2.id === twoMM2.id && _MessageList.cacheKeyFromParts(oneMM2.content.parts) === _MessageList.cacheKeyFromParts(twoMM2.content.parts); } return true; } }; exports.MessageList = MessageList; //# sourceMappingURL=chunk-4YC6KUX4.cjs.map //# sourceMappingURL=chunk-4YC6KUX4.cjs.map