UNPKG

@mastra/core

Version:

Mastra is a framework for building AI-powered applications and agents with a modern TypeScript stack.

1,377 lines (1,365 loc) • 122 kB
'use strict'; var chunkIAJHRFO4_cjs = require('./chunk-IAJHRFO4.cjs'); var chunk5NTO7S5I_cjs = require('./chunk-5NTO7S5I.cjs'); var providerUtilsV5 = require('@ai-sdk/provider-utils-v5'); var crypto = require('crypto'); var AIV4 = require('ai'); var AIV5 = require('ai-v5'); var providerUtils = require('@ai-sdk/provider-utils'); var zod = require('zod'); function _interopNamespace(e) { if (e && e.__esModule) return e; var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n.default = e; return Object.freeze(n); } var AIV4__namespace = /*#__PURE__*/_interopNamespace(AIV4); var AIV5__namespace = /*#__PURE__*/_interopNamespace(AIV5); var DefaultGeneratedFile = class { base64Data; uint8ArrayData; mediaType; constructor({ data, mediaType }) { const isUint8Array = data instanceof Uint8Array; this.base64Data = isUint8Array ? void 0 : data; this.uint8ArrayData = isUint8Array ? data : void 0; this.mediaType = mediaType; } // lazy conversion with caching to avoid unnecessary conversion overhead: get base64() { if (this.base64Data == null) { this.base64Data = providerUtilsV5.convertUint8ArrayToBase64(this.uint8ArrayData); } return this.base64Data; } // lazy conversion with caching to avoid unnecessary conversion overhead: get uint8Array() { if (this.uint8ArrayData == null) { this.uint8ArrayData = providerUtilsV5.convertBase64ToUint8Array(this.base64Data); } return this.uint8ArrayData; } }; var DefaultGeneratedFileWithType = class extends DefaultGeneratedFile { type = "file"; constructor(options) { super(options); } }; // src/stream/aisdk/v5/compat/content.ts function splitDataUrl(dataUrl) { try { const [header, base64Content] = dataUrl.split(","); return { mediaType: header?.split(";")[0]?.split(":")[1], base64Content }; } catch { return { mediaType: void 0, base64Content: void 0 }; } } function convertToDataContent(content) { if (content instanceof Uint8Array) { return { data: content, mediaType: void 0 }; } if (content instanceof ArrayBuffer) { return { data: new Uint8Array(content), mediaType: void 0 }; } if (typeof content === "string") { try { content = new URL(content); } catch { } } if (content instanceof URL && content.protocol === "data:") { const { mediaType: dataUrlMediaType, base64Content } = splitDataUrl(content.toString()); if (dataUrlMediaType == null || base64Content == null) { throw new chunk5NTO7S5I_cjs.MastraError({ id: "INVALID_DATA_URL_FORMAT", text: `Invalid data URL format in content ${content.toString()}`, domain: "LLM" /* LLM */, category: "USER" /* USER */ }); } return { data: base64Content, mediaType: dataUrlMediaType }; } return { data: content, mediaType: void 0 }; } var imageMediaTypeSignatures = [ { mediaType: "image/gif", bytesPrefix: [71, 73, 70], base64Prefix: "R0lG" }, { mediaType: "image/png", bytesPrefix: [137, 80, 78, 71], base64Prefix: "iVBORw" }, { mediaType: "image/jpeg", bytesPrefix: [255, 216], base64Prefix: "/9j/" }, { mediaType: "image/webp", bytesPrefix: [82, 73, 70, 70], base64Prefix: "UklGRg" }, { mediaType: "image/bmp", bytesPrefix: [66, 77], base64Prefix: "Qk" }, { mediaType: "image/tiff", bytesPrefix: [73, 73, 42, 0], base64Prefix: "SUkqAA" }, { mediaType: "image/tiff", bytesPrefix: [77, 77, 0, 42], base64Prefix: "TU0AKg" }, { mediaType: "image/avif", bytesPrefix: [0, 0, 0, 32, 102, 116, 121, 112, 97, 118, 105, 102], base64Prefix: "AAAAIGZ0eXBhdmlm" }, { mediaType: "image/heic", bytesPrefix: [0, 0, 0, 32, 102, 116, 121, 112, 104, 101, 105, 99], base64Prefix: "AAAAIGZ0eXBoZWlj" } ]; var stripID3 = (data) => { const bytes = typeof data === "string" ? providerUtilsV5.convertBase64ToUint8Array(data) : data; const id3Size = ( // @ts-ignore (bytes[6] & 127) << 21 | // @ts-ignore (bytes[7] & 127) << 14 | // @ts-ignore (bytes[8] & 127) << 7 | // @ts-ignore bytes[9] & 127 ); return bytes.slice(id3Size + 10); }; function stripID3TagsIfPresent(data) { const hasId3 = typeof data === "string" && data.startsWith("SUQz") || typeof data !== "string" && data.length > 10 && data[0] === 73 && // 'I' data[1] === 68 && // 'D' data[2] === 51; return hasId3 ? stripID3(data) : data; } function detectMediaType({ data, signatures }) { const processedData = stripID3TagsIfPresent(data); for (const signature of signatures) { if (typeof processedData === "string" ? processedData.startsWith(signature.base64Prefix) : processedData.length >= signature.bytesPrefix.length && signature.bytesPrefix.every((byte, index) => processedData[index] === byte)) { return signature.mediaType; } } return void 0; } // src/agent/message-list/prompt/convert-file.ts function convertImageFilePart(part, downloadedAssets) { let originalData; const type = part.type; switch (type) { case "image": originalData = part.image; break; case "file": originalData = part.data; break; default: throw new Error(`Unsupported part type: ${type}`); } const { data: convertedData, mediaType: convertedMediaType } = convertToDataContent(originalData); let mediaType = convertedMediaType ?? part.mediaType; let data = convertedData; if (data instanceof URL && downloadedAssets) { const downloadedFile = downloadedAssets[data.toString()]; if (downloadedFile) { data = downloadedFile.data; mediaType ??= downloadedFile.mediaType; } } switch (type) { case "image": { if (data instanceof Uint8Array || typeof data === "string") { mediaType = detectMediaType({ data, signatures: imageMediaTypeSignatures }) ?? mediaType; } return { type: "file", mediaType: mediaType ?? "image/*", // any image filename: void 0, data, providerOptions: part.providerOptions }; } case "file": { if (mediaType == null) { throw new Error(`Media type is missing for file part`); } return { type: "file", mediaType, filename: part.filename, data, providerOptions: part.providerOptions }; } } } 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/prompt/image-utils.ts function parseDataUri(dataUri) { if (!dataUri.startsWith("data:")) { return { isDataUri: false, base64Content: dataUri }; } const base64Index = dataUri.indexOf(","); if (base64Index === -1) { return { isDataUri: true, base64Content: dataUri }; } const header = dataUri.substring(5, base64Index); const base64Content = dataUri.substring(base64Index + 1); const semicolonIndex = header.indexOf(";"); const mimeType = semicolonIndex !== -1 ? header.substring(0, semicolonIndex) : header; return { isDataUri: true, mimeType: mimeType || void 0, base64Content }; } function createDataUri(base64Content, mimeType = "application/octet-stream") { if (base64Content.startsWith("data:")) { return base64Content; } return `data:${mimeType};base64,${base64Content}`; } function imageContentToString(image, fallbackMimeType) { if (typeof image === "string") { return image; } if (image instanceof URL) { return image.toString(); } if (image instanceof Uint8Array || image instanceof ArrayBuffer || globalThis.Buffer && Buffer.isBuffer(image)) { const base64 = convertDataContentToBase64String(image); if (fallbackMimeType && !base64.startsWith("data:")) { return `data:${fallbackMimeType};base64,${base64}`; } return base64; } return String(image); } function imageContentToDataUri(image, mimeType = "image/png") { const imageStr = imageContentToString(image, mimeType); if (imageStr.startsWith("data:")) { return imageStr; } if (imageStr.startsWith("http://") || imageStr.startsWith("https://")) { return imageStr; } return `data:${mimeType};base64,${imageStr}`; } function getImageCacheKey(image) { if (image instanceof URL) { return image.toString(); } if (typeof image === "string") { return image.length; } if (image instanceof Uint8Array) { return image.byteLength; } if (image instanceof ArrayBuffer) { return image.byteLength; } return image; } function isValidUrl(str) { try { new URL(str); return true; } catch { if (str.startsWith("//")) { try { new URL(`https:${str}`); return true; } catch { return false; } } return false; } } function categorizeFileData(data, fallbackMimeType) { const parsed = parseDataUri(data); const mimeType = parsed.isDataUri && parsed.mimeType ? parsed.mimeType : fallbackMimeType; if (parsed.isDataUri) { return { type: "dataUri", mimeType, data }; } if (isValidUrl(data)) { return { type: "url", mimeType, data }; } return { type: "raw", mimeType, data }; } // src/agent/message-list/prompt/attachments-to-parts.ts function attachmentsToParts(attachments) { const parts = []; for (const attachment of attachments) { const categorized = categorizeFileData(attachment.url, attachment.contentType); let urlString = attachment.url; if (categorized.type === "raw") { urlString = createDataUri(attachment.url, attachment.contentType || "application/octet-stream"); } let url; try { url = new URL(urlString); } 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: urlString, mimeType: attachment.contentType }); } else if (attachment.contentType?.startsWith("text/")) { parts.push({ type: "file", data: urlString, 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: urlString, 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) => { const idUsageCount = /* @__PURE__ */ new Map(); const SPLIT_SUFFIX_PATTERN = /__split-\d+$/; return (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 { let baseId = msg.id; const hasSplitSuffix = SPLIT_SUFFIX_PATTERN.test(baseId); if (hasSplitSuffix) { v1Messages.push(msg); return; } const currentCount = idUsageCount.get(baseId) || 0; if (currentCount > 0) { msg.id = `${baseId}__split-${currentCount}`; } idUsageCount.set(baseId, currentCount + 1); 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: typeof content2 !== `string` && Array.isArray(content2) && content2.length === 1 && content2[0]?.type === `text` ? content2[0].text : 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": { const hasNonToolContent = block.some( (p) => p.type === "text" || p.type === "file" || p.type === "reasoning" ); if (hasNonToolContent || (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; } var downloadFromUrl = async ({ url, downloadRetries }) => { const urlText = url.toString(); try { const response = await chunkIAJHRFO4_cjs.fetchWithRetry( urlText, { method: "GET" }, downloadRetries ); if (!response.ok) { throw new chunk5NTO7S5I_cjs.MastraError({ id: "DOWNLOAD_ASSETS_FAILED", text: "Failed to download asset", domain: "LLM" /* LLM */, category: "USER" /* USER */ }); } return { data: new Uint8Array(await response.arrayBuffer()), mediaType: response.headers.get("content-type") ?? void 0 }; } catch (error) { throw new chunk5NTO7S5I_cjs.MastraError( { id: "DOWNLOAD_ASSETS_FAILED", text: "Failed to download asset", domain: "LLM" /* LLM */, category: "USER" /* USER */ }, error ); } }; async function downloadAssetsFromMessages({ messages, downloadConcurrency = 10, downloadRetries = 3, supportedUrls }) { const pMap = (await import('p-map')).default; const filesToDownload = messages.filter((message) => message.role === "user").map((message) => message.content).filter((content) => Array.isArray(content)).flat().filter((part) => part.type === "image" || part.type === "file").map((part) => { const mediaType = part.mediaType ?? (part.type === "image" ? "image/*" : void 0); let data = part.type === "image" ? part.image : part.data; if (typeof data === "string") { try { data = new URL(data); } catch { } } return { mediaType, data }; }).filter((part) => part.data instanceof URL).map((part) => { return { url: part.data, isUrlSupportedByModel: part.mediaType != null && providerUtilsV5.isUrlSupported({ url: part.data.toString(), mediaType: part.mediaType, supportedUrls: supportedUrls ?? {} }) }; }); const downloadedFiles = await pMap( filesToDownload, async (fileItem) => { if (fileItem.isUrlSupportedByModel) { return null; } return { url: fileItem.url.toString(), ...await downloadFromUrl({ url: fileItem.url, downloadRetries }) }; }, { concurrency: downloadConcurrency } ); const downloadFileList = downloadedFiles.filter( (downloadedFile) => downloadedFile?.data != null ).map(({ url, data, mediaType }) => [url, { data, mediaType }]); return Object.fromEntries(downloadFileList); } // src/agent/message-list/utils/ai-v5/gemini-compatibility.ts function ensureGeminiCompatibleMessages(messages) { const result = [...messages]; const firstNonSystemIndex = result.findIndex((m) => m.role !== "system"); if (firstNonSystemIndex === -1) { throw new chunk5NTO7S5I_cjs.MastraError({ id: "NO_USER_OR_ASSISTANT_MESSAGES", domain: "AGENT" /* AGENT */, category: "USER" /* USER */, text: "This request does not contain any user or assistant messages. At least one user or assistant message is required to generate a response." }); } else if (result[firstNonSystemIndex]?.role === "assistant") { result.splice(firstNonSystemIndex, 0, { role: "user", content: "." }); } return result; } // src/agent/message-list/utils/ai-v5/tool.ts function getToolName(type) { if (typeof type === "object" && type && "type" in type) { type = type.type; } if (typeof type !== "string") { return "unknown"; } if (type === "dynamic-tool") { return "dynamic-tool"; } if (type.startsWith("tool-")) { return type.slice("tool-".length); } return type; } // 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(); memoryMessagesPersisted = /* @__PURE__ */ new Set(); newUserMessagesPersisted = /* @__PURE__ */ new Set(); newResponseMessagesPersisted = /* @__PURE__ */ new Set(); userContextMessagesPersisted = /* @__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 (messageSource === `user`) messageSource = `input`; 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; } serializeSet(set) { return Array.from(set).map((value) => value.id); } deserializeSet(ids) { return new Set(ids.map((id) => this.messages.find((m) => m.id === id)).filter(Boolean)); } serializeMessage(message) { return { ...message, createdAt: message.createdAt.toUTCString() }; } deserializeMessage(state) { return { ...state, createdAt: new Date(state.createdAt) }; } serialize() { return { messages: this.messages.map(this.serializeMessage), systemMessages: this.systemMessages, taggedSystemMessages: this.taggedSystemMessages, memoryInfo: this.memoryInfo, _agentNetworkAppend: this._agentNetworkAppend, memoryMessages: this.serializeSet(this.memoryMessages), newUserMessages: this.serializeSet(this.newUserMessages), newResponseMessages: this.serializeSet(this.newResponseMessages), userContextMessages: this.serializeSet(this.userContextMessages), memoryMessagesPersisted: this.serializeSet(this.memoryMessagesPersisted), newUserMessagesPersisted: this.serializeSet(this.newUserMessagesPersisted), newResponseMessagesPersisted: this.serializeSet(this.newResponseMessagesPersisted), userContextMessagesPersisted: this.serializeSet(this.userContextMessagesPersisted) }; } deserialize(state) { this.messages = state.messages.map(this.deserializeMessage); this.systemMessages = state.systemMessages; this.taggedSystemMessages = state.taggedSystemMessages; this.memoryInfo = state.memoryInfo; this._agentNetworkAppend = state._agentNetworkAppend; this.memoryMessages = this.deserializeSet(state.memoryMessages); this.newUserMessages = this.deserializeSet(state.newUserMessages); this.newResponseMessages = this.deserializeSet(state.newResponseMessages); this.userContextMessages = this.deserializeSet(state.userContextMessages); this.memoryMessagesPersisted = this.deserializeSet(state.memoryMessagesPersisted); this.newUserMessagesPersisted = this.deserializeSet(state.newUserMessagesPersisted); this.newResponseMessagesPersisted = this.deserializeSet(state.newResponseMessagesPersisted); this.userContextMessagesPersisted = this.deserializeSet(state.userContextMessagesPersisted); 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 getPersisted() { return { remembered: this.rememberedPersisted, input: this.inputPersisted, taggedSystemMessages: this.taggedSystemMessages, response: this.responsePersisted }; } 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; } }, response: { v2: () => { const responseMessages = Array.from(this.newResponseMessages); this.messages = this.messages.filter((m) => !this.newResponseMessages.has(m)); this.newResponseMessages.clear(); return responseMessages; } } }; } all = { v3: () => this.cleanV3Metadata(this.messages.map(this.mastraMessageV2ToMastraMessageV3)), v2: () => this.messages, v1: () => convertToV1Messages(this.all.v2()), aiV5: { model: () => this.aiV5UIMessagesToAIV5ModelMessages(this.all.aiV5.ui()), ui: () => this.all.v3().map(_MessageList.mastraMessageV3ToAIV5UIMessage), // Used when calling AI SDK streamText/generateText prompt: () => { const systemMessages = this.aiV4CoreMessagesToAIV5ModelMessages( [...this.systemMessages, ...Object.values(this.taggedSystemMessages).flat()], `system` ); const modelMessages = this.all.aiV5.model(); const messages = [...systemMessages, ...modelMessages]; return ensureGeminiCompatibleMessages(messages); }, // Used for creating LLM prompt messages without AI SDK streamText/generateText llmPrompt: async (options = { downloadConcurrency: 10, downloadRetries: 3 }) => { const modelMessages = this.all.aiV5.model(); const systemMessages = this.aiV4CoreMessagesToAIV5ModelMessages( [...this.systemMessages, ...Object.values(this.taggedSystemMessages).flat()], `system` ); const downloadedAssets = await downloadAssetsFromMessages({ messages: modelMessages, downloadConcurrency: options?.downloadConcurrency, downloadRetries: options?.downloadRetries, supportedUrls: options?.supportedUrls }); let messages = [...systemMessages, ...modelMessages]; const hasImageOrFileContent = modelMessages.some( (message) => message.role === "user" && typeof message.content !== "string" && message.content.some((part) => part.type === "image" || part.type === "file") ); if (hasImageOrFileContent) { messages = messages.map((message) => { if (message.role === "user") { if (typeof message.content === "string") { return { role: "user", content: [{ type: "text", text: message.content }], providerOptions: message.providerOptions }; } const convertedContent = message.content.map((part) => { if (part.type === "image" || part.type === "file") { return convertImageFilePart(part, downloadedAssets); } return part; }).filter((part) => part.type !== "text" || part.text !== ""); return { role: "user", content: convertedContent, providerOptions: message.providerOptions }; } return message; }); } messages = ensureGeminiCompatibleMessages(messages); return messages.map(_MessageList.aiV5ModelMessageToV2PromptMessage); } }, /* @deprecated use list.get.all.aiV4.prompt() instead */ prompt: () => this.all.aiV4.prompt(), /* @deprecated use list.get.all.aiV4.ui() */ ui: () => this.all.v2().map(_MessageList.mastraMessageV2ToAIV4UIMessage), /* @deprecated use list.get.all.aiV4.core() */ core: () => this.aiV4UIMessagesToAIV4CoreMessages(this.all.aiV4.ui()), aiV4: { ui: () => this.all.v2().map(_MessageList.mastraMessageV2ToAIV4UIMessage), core: () => this.aiV4UIMessagesToAIV4CoreMessages(this.all.aiV4.ui()), // Used when calling AI SDK streamText/generateText prompt: () => { const coreMessages = this.all.aiV4.core(); const messages = [...this.systemMessages, ...Object.values(this.taggedSystemMessages).flat(), ...coreMessages]; return ensureGeminiCompatibleMessages(messages); }, // Used for creating LLM prompt messages without AI SDK streamText/generateText llmPrompt: () => { const coreMessages = this.all.aiV4.core(); const systemMessages = [...this.systemMessages, ...Object.values(this.taggedSystemMessages).flat()]; let messages = [...systemMessages, ...coreMessages]; messages = ensureGeminiCompatibleMessages(messages); return messages.map(_MessageList.aiV4CoreMessageToV1PromptMessage); } } }; remembered = { v3: () => this.remembered.v2().map(this.mastraMessageV2ToMastraMessageV3), v2: () => this.messages.filter((m) => this.memoryMessages.has(m)), v1: () => convertToV1Messages(this.remembered.v2()), aiV5: { model: () => this.aiV5UIMessagesToAIV5ModelMessages(this.remembered.aiV5.ui()), ui: () => this.remembered.v3().map(_MessageList.mastraMessageV3ToAIV5UIMessage) }, /* @deprecated use list.get.remembered.aiV4.ui() */ ui: () => this.remembered.v2().map(_MessageList.mastraMessageV2ToAIV4UIMessage), /* @deprecated use list.get.remembered.aiV4.core() */ core: () => this.aiV4UIMessagesToAIV4CoreMessages(this.all.aiV4.ui()), aiV4: { ui: () => this.remembered.v2().map(_MessageList.mastraMessageV2ToAIV4UIMessage), core: () => this.aiV4UIMessagesToAIV4CoreMessages(this.all.aiV4.ui()) } }; // TODO: need to update this for new .aiV4/5.x() pattern rememberedPersisted = { v2: () => this.all.v2().filter((m) => this.memoryMessagesPersisted.has(m)), v1: () => convertToV1Messages(this.rememberedPersisted.v2()), ui: () => this.rememberedPersisted.v2().map(_MessageList.mastraMessageV2ToAIV4UIMessage), core: () => this.aiV4UIMessagesToAIV4CoreMessages(this.rememberedPersisted.ui()) }; input = { v3: () => this.cleanV3Metadata( this.messages.filter((m) => this.newUserMessages.has(m)).map(this.mastraMessageV2ToMastraMessageV3) ), v2: () => this.messages.filter((m) => this.newUserMessages.has(m)), v1: () => convertToV1Messages(this.input.v2()), aiV5: { model: () => this.aiV5UIMessagesToAIV5ModelMessages(this.input.aiV5.ui()), ui: () => this.input.v3().map(_MessageList.mastraMessageV3ToAIV5UIMessage) }, /* @deprecated use list.get.input.aiV4.ui() instead */ ui: () => this.input.v2().map(_MessageList.mastraMessageV2ToAIV4UIMessage), /* @deprecated use list.get.core.aiV4.ui() instead */ core: () => this.aiV4UIMessagesToAIV4CoreMessages(this.input.ui()), aiV4: { ui: () => this.input.v2().map(_MessageList.mastraMessageV2ToAIV4UIMessage), core: () => this.aiV4UIMessagesToAIV4CoreMessages(this.input.aiV4.ui()) } }; // TODO: need to update this for new .aiV4/5.x() pattern inputPersisted = { v3: () => this.cleanV3Metadata( this.messages.filter((m) => this.newUserMessagesPersisted.has(m)).map(this.mastraMessageV2ToMastraMessageV3) ), v2: () => this.messages.filter((m) => this.newUserMessagesPersisted.has(m)), v1: () => convertToV1Messages(this.inputPersisted.v2()), ui: () => this.inputPersisted.v2().map(_MessageList.mastraMessageV2ToAIV4UIMessage), core: () => this.aiV4UIMessagesToAIV4CoreMessages(this.inputPersisted.ui()) }; response = { v3: () => this.response.v2().map(this.mastraMessageV2ToMastraMessageV3), v2: () => this.messages.filter((m) => this.newResponseMessages.has(m)), v1: () => convertToV1Messages(this.response.v3().map(_MessageList.mastraMessageV3ToV2)), aiV5: { ui: () => this.response.v3().map(_MessageList.mastraMessageV3ToAIV5UIMessage), model: () => this.aiV5UIMessagesToAIV5ModelMessages(this.response.aiV5.ui()).filter( (m) => m.role === `tool` || m.role === `assistant` ), modelContent: (stepNumber) => { if (typeof stepNumber === "number") { const uiMessages = this.response.aiV5.ui(); const uiMessagesParts = uiMessages.flatMap((item) => item.parts); const stepBoundaries = []; uiMessagesParts.forEach((part, index) => { if (part.type === "step-start") { stepBoundaries.push(index); } }); if (stepNumber === -1) { const toolParts = uiMessagesParts.filter((p) => p.type?.startsWith("tool-")); const hasStepStart = stepBoundaries.length > 0; if (!hasStepStart && toolParts.length > 0) { const lastToolPart = toolParts[toolParts.length - 1]; if (!lastToolPart) { return []; } const lastToolIndex = uiMessagesParts.indexOf(lastToolPart); const previousToolPart = toolParts[toolParts.length - 2]; const previousToolIndex = previousToolPart ? uiMessagesParts.indexOf(previousToolPart) : -1; const startIndex2 = previousToolIndex + 1; const stepParts3 = uiMessagesParts.slice(startIndex2, lastToolIndex + 1); const stepUiMessages3 = [ { id: "last-step", role: "assistant", parts: stepParts3 } ]; const modelMessages3 = AIV5__namespace.convertToModelMessages(this.sanitizeV5UIMessages(stepUiMessages3)); return modelMessages3.flatMap(this.response.aiV5.stepContent); } const totalSteps = stepBoundaries.length + 1; if (totalSteps === 1 && !hasStepStart) { const stepUiMessages3 = [ { id: "last-step", role: "assistant", parts: uiMessagesParts } ]; const modelMessages3 = AIV5__namespace.convertToModelMessages(this.sanitizeV5UIMessages(stepUiMessages3)); return modelMessages3.flatMap(this.response.aiV5.stepContent); } const lastStepStart = stepBoundaries[stepBoundaries.length - 1]; if (lastStepStart === void 0) { return []; } const stepParts2 = uiMessagesParts.slice(lastStepStart + 1); if (stepParts2.length === 0) { return []; } const stepUiMessages2 = [ { id: "last-step", role: "assistant", parts: stepParts2 } ]; const modelMessages2 = AIV5__namespace.convertToModelMessages(this.sanitizeV5UIMessages(stepUiMessages2)); return modelMessages2.flatMap(this.response.aiV5.stepContent); } if (stepNumber === 1) { const firstStepStart = stepBoundaries[0] ?? uiMessagesParts.length; if (firstStepStart === 0) { return []; } const stepParts2 = uiMessagesParts.slice(0, firstStepStart); const stepUiMessages2 = [ { id: "step-1", role: "assistant", parts: stepParts2 } ]; const modelMessages2 = AIV5__namespace.convertToModelMessages(this.sanitizeV5UIMessages(stepUiMessages2)); return modelMessages2.flatMap(this.response.aiV5.stepContent); } const stepIndex = stepNumber - 2; if (stepIndex < 0 || stepIndex >= stepBoundaries.length) { return []; } const startIndex = (stepBoundaries[stepIndex] ?? 0) + 1; const endIndex = stepBoundaries[stepIndex + 1] ?? uiMessagesParts.length; if (startIndex >= endIndex) { return []; } const stepParts = uiMessagesParts.slice(startIndex, endIndex); const stepUiMessages = [ { id: `step-${stepNumber}`, role: "assistant", parts: stepParts } ]; const modelMessages = AIV5__namespace.convertToModelMessages(this.sanitizeV5UIMessages(stepUiMessages)); return modelMessages.flatMap(this.response.aiV5.stepContent); } return this.response.aiV5.model().map(this.response.aiV5.stepContent).flat(); }, stepContent: (message) => { const latest = message ? message : this.response.aiV5.model().at(-1); if (!latest) return []; if (typeof latest.content === `string`) { return [{ type: "text", text: latest.content }]; } return latest.content.map((c) => { if (c.type === `tool-result`) return { type: "tool-result", input: {}, // TODO: we need to find the tool call here and add the input from it output: c.output, toolCallId: c.toolCallId, toolName: c.toolName }; if (c.type === `file`) return { type: "file", file: new DefaultGeneratedFileWithType({ data: typeof c.data === `string` ? parseDataUri(c.data).base64Content : c.data instanceof URL ? c.data.toString() : convertDataContentToBase64String(c.data), mediaType: c.mediaType }) }; if (c.type === `image`) { return { type: "file", file: new DefaultGeneratedFileWithType({ data: typeof c.image === `string` ? parseDataUri(c.image).base64Content : c.image instanceof URL ? c.image.toString() : convertDataContentToBase64String(c.image), mediaType: c.mediaType || "unknown" }) }; } return { ...c }; }); } }, aiV4: { ui: () => this.response.v2().map(_MessageList.mastraMessageV2ToAIV4UIMessage), core: () => this.aiV4UIMessagesToAIV4CoreMessages(this.response.aiV4.ui()) } }; // TODO: need to update this for new .aiV4/5.x() pattern responsePersisted = { v3: () => this.cleanV3Metadata( this.messages.filter((m) => this.newResponseMessagesPersisted.has(m)).map(this.mastraMessageV2ToMastraMessageV3) ), v2: () => this.messages.filter((m) => this.newResponseMessagesPersisted.has(m)), ui: () => this.responsePersisted.v2().map(_MessageList.mastraMessageV2ToAIV4UIMessage) }; 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; } aiV4UIMessagesToAIV4CoreMessages(messages) { return AIV4__namespace.convertToCoreMessages(this.sanitizeAIV4UIMessages(messages)); } sanitizeAIV4UIMessages(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; } /** * Converts various message formats to AIV4 CoreMessage format for system messages * @param message - The message to convert (can be string, MastraMessageV2, or AI SDK message types) * @returns AIV4 CoreMessage in the proper format */ systemMessageToAICore(message) { if (typeof message === `string`) { return { role: "system", content: message }; } if (_MessageList.isAIV5CoreMessage(message)) { return this.aiV5ModelMessagesToAIV4CoreMessages([message], `system`)[0]; } if (_MessageList.isMastraMessageV2(message)) { return _MessageList.mastraMessageV2SystemToV4Core(message); } return message; } addOneSystem(message, tag) { const coreMessage = this.systemMessageToAICore(message); if (coreMessage.role !== `system`) { throw new Error( `Expected role "system" but saw ${coreMessage.role} for message ${JSON.stringify(coreMessage, null, 2)}` ); } if (tag && !this.isDuplicateSystem(coreMessage, tag)) { this.taggedSystemMessages[tag] ||= []; this.taggedSystemMessages[tag].push(coreMessage); } else if (!tag && !this.isDuplicateSystem(coreMessage)) { this.systemMessages.push(coreMessage); } } isDuplicateSystem(message, tag) { if (tag) { if (!this.taggedSystemMessages[tag]) return false; return this.taggedSystemMessages[tag].some( (m) => _MessageList.cacheKeyFromAIV4CoreMessageContent(m.content) === _MessageList.cacheKeyFromAIV4CoreMessageContent(message.content) ); } return this.systemMessages.som