@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
JavaScript
'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