UNPKG

@mastra/core

Version:

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

1,327 lines (1,318 loc) • 217 kB
import { isToolUIPart, convertToModelMessages } from './chunk-QBZCTB6N.js'; import { getTransformedToolPayload, hasTransformedToolPayload } from './chunk-GNP47JBD.js'; import { isToolUIPart as isToolUIPart$1 } from './chunk-5THMFV44.js'; import { convertToCoreMessages } from './chunk-SUISXAWH.js'; import { fetchWithRetry } from './chunk-RBJJ4Y4N.js'; import { MastraError } from './chunk-FJEVLHJT.js'; import { v4 } from '@lukeed/uuid'; import { convertUint8ArrayToBase64, convertBase64ToUint8Array, isUrlSupported } from '@ai-sdk/provider-utils-v5'; function convertDataContentToBase64String(content) { if (typeof content === "string") { return content; } if (content instanceof ArrayBuffer) { return convertUint8ArrayToBase64(new Uint8Array(content)); } return convertUint8ArrayToBase64(content); } // src/agent/signals.ts function isMastraSignalMessage(message) { return message.role === "signal"; } function normalizeSignal(signal) { return { ...signal, id: signal.id ?? crypto.randomUUID(), createdAt: signal.createdAt instanceof Date ? signal.createdAt : signal.createdAt ? new Date(signal.createdAt) : /* @__PURE__ */ new Date(), acceptedAt: signal.acceptedAt instanceof Date ? signal.acceptedAt : signal.acceptedAt ? new Date(signal.acceptedAt) : void 0 }; } function escapeXml(value) { return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;"); } function escapeXmlAttribute(value) { return escapeXml(value).replaceAll('"', "&quot;"); } var XML_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_.-]*$/; function assertXmlName(name, label) { if (!XML_NAME_PATTERN.test(name)) { throw new Error(`Invalid signal XML ${label}: ${name}`); } } function signalAttributesToXml(attributes) { if (!attributes) { return ""; } const serialized = Object.entries(attributes).filter((entry) => entry[1] !== null && entry[1] !== void 0).map(([key, value]) => { assertXmlName(key, "attribute name"); return `${key}="${escapeXmlAttribute(String(value))}"`; }).join(" "); return serialized ? ` ${serialized}` : ""; } function signalToXmlMarkup(signal) { assertXmlName(signal.type, "tag name"); const attributesXml = signalAttributesToXml(signal.attributes); if (!signal.contents) return `<${signal.type}${attributesXml} />`; return `<${signal.type}${attributesXml}>${escapeXml(signal.contents)}</${signal.type}>`; } function legacyContentsToSignalContents(value) { if (typeof value === "string") return value; if (Array.isArray(value)) { const parts = []; for (const entry of value) { if (typeof entry === "string") { parts.push({ type: "text", text: entry }); continue; } const decoded2 = legacyEntryToParts(entry); if (!decoded2) return void 0; parts.push(...decoded2); } return collapseLegacyParts(parts); } const decoded = legacyEntryToParts(value); return decoded ? collapseLegacyParts(decoded) : void 0; } function legacyEntryToParts(entry) { if (!entry || typeof entry !== "object") return void 0; const record = entry; if (record.role === "user" && "content" in record) { const content = record.content; if (typeof content === "string") return [{ type: "text", text: content }]; if (Array.isArray(content)) { const inner = []; for (const part2 of content) { const decoded = legacyPartToSignalPart(part2); if (!decoded) return void 0; inner.push(decoded); } return inner; } return void 0; } const part = legacyPartToSignalPart(record); return part ? [part] : void 0; } function legacyPartToSignalPart(part) { if (!part || typeof part !== "object") return void 0; const record = part; const providerOptions = record.providerOptions && typeof record.providerOptions === "object" && !Array.isArray(record.providerOptions) ? record.providerOptions : void 0; if (record.type === "text" && typeof record.text === "string") { return { type: "text", text: record.text, ...providerOptions ? { providerOptions } : {} }; } if (record.type === "file" || record.type === "image") { const data = record.type === "image" ? record.image ?? record.data : record.data; if (typeof data !== "string") return void 0; const mediaType = typeof record.mediaType === "string" ? record.mediaType : typeof record.mimeType === "string" ? record.mimeType : record.type === "image" ? "image/png" : ""; if (!mediaType) return void 0; return { type: "file", data, mediaType, ...typeof record.filename === "string" ? { filename: record.filename } : {}, ...providerOptions ? { providerOptions } : {} }; } return void 0; } function collapseLegacyParts(parts) { if (parts.length === 0) return void 0; const first = parts[0]; if (parts.length === 1 && first?.type === "text") return first.text; return parts; } function contentsToSignalParts(contents) { if (typeof contents === "string") return [{ type: "text", text: contents }]; return contents.map((part) => { if (part.type === "file") { const data = part.data instanceof URL ? part.data.toString() : convertDataContentToBase64String(part.data); return { type: "file", data, mediaType: part.mediaType, ...part.filename ? { filename: part.filename } : {}, ...part.providerOptions ? { providerOptions: part.providerOptions } : {} }; } return { type: "text", text: part.text, ...part.providerOptions ? { providerOptions: part.providerOptions } : {} }; }); } function storagePartsToSignalParts(parts) { const out = []; for (const part of parts) { const providerOptions = part.providerMetadata; if (part.type === "text") { out.push({ type: "text", text: part.text, ...providerOptions ? { providerOptions } : {} }); } else if (part.type === "file" && typeof part.data === "string") { const file = part; out.push({ type: "file", data: file.data, mediaType: typeof file.mimeType === "string" ? file.mimeType : "", ...typeof file.filename === "string" ? { filename: file.filename } : {}, ...providerOptions ? { providerOptions } : {} }); } } return out; } function partsToSignalContents(parts) { if (parts.length === 1 && parts[0]?.type === "text" && !parts[0].providerOptions) return parts[0].text; return parts.map( (part) => part.type === "file" ? { type: "file", data: part.data, mediaType: part.mediaType, ...part.filename ? { filename: part.filename } : {}, ...part.providerOptions ? { providerOptions: part.providerOptions } : {} } : { type: "text", text: part.text, ...part.providerOptions ? { providerOptions: part.providerOptions } : {} } ); } function hasMeaningfulAttributes(attributes) { if (!attributes) return false; return Object.keys(attributes).some((key) => { const value = attributes[key]; return value !== null && value !== void 0; }); } function injectMarkerInline(signal, parts) { let wrapped = false; const out = []; for (const part of parts) { if (!wrapped && part.type === "text") { wrapped = true; out.push({ ...part, text: signalToXmlMarkup({ ...signal, contents: part.text }) }); } else { out.push(part); } } if (!wrapped) { const markerText = signalToXmlMarkup({ type: signal.type, attributes: signal.attributes }); out.unshift({ type: "text", text: markerText }); } return out; } function signalToLLMMessage(signal, parts) { const isUserMessage = signal.type === "user-message"; const hasAttrs = hasMeaningfulAttributes(signal.attributes); const anyPartProviderOptions = parts.some((part) => part.providerOptions); let content; if (isUserMessage && !hasAttrs) { content = parts.length === 1 && parts[0]?.type === "text" && !parts[0].providerOptions ? parts[0].text : parts; } else if (parts.every((part) => part.type === "text") && !anyPartProviderOptions) { content = signalToXmlMarkup({ ...signal, contents: parts.map((part) => part.text).join("\n") }); } else { content = injectMarkerInline(signal, parts); } return { role: "user", content, ...signal.providerOptions ? { providerOptions: signal.providerOptions } : {} }; } function signalToDataPart(signal, parts) { return { type: `data-${signal.type}`, data: { id: signal.id, type: signal.type, contents: partsToSignalContents(parts), createdAt: signal.createdAt.toISOString(), ...signal.acceptedAt ? { acceptedAt: signal.acceptedAt.toISOString() } : {}, ...signal.attributes ? { attributes: signal.attributes } : {}, ...signal.metadata ? { metadata: signal.metadata } : {} } }; } function signalToDBMessage(signal, parts, options) { const storageParts = parts.length > 0 ? parts.map( (part) => part.type === "file" ? { type: "file", data: part.data, mimeType: part.mediaType, ...part.filename ? { filename: part.filename } : {}, ...part.providerOptions ? { providerMetadata: part.providerOptions } : {} } : { type: "text", text: part.text, ...part.providerOptions ? { providerMetadata: part.providerOptions } : {} } ) : [{ type: "text", text: "" }]; return { id: signal.id, role: "signal", createdAt: signal.createdAt, threadId: options?.threadId, resourceId: options?.resourceId, type: signal.type, content: { format: 2, parts: storageParts, ...signal.providerOptions ? { providerMetadata: signal.providerOptions } : {}, metadata: { signal: { id: signal.id, type: signal.type, createdAt: signal.createdAt.toISOString(), ...signal.acceptedAt ? { acceptedAt: signal.acceptedAt.toISOString() } : {}, ...signal.attributes ? { attributes: signal.attributes } : {}, ...signal.metadata ? { metadata: signal.metadata } : {} } } } }; } function isCreatedAgentSignal(input) { if (!input || typeof input !== "object" || Array.isArray(input)) return false; const candidate = input; return candidate.__isCreatedSignal === true; } function createSignal(input) { const signal = normalizeSignal(input); const parts = contentsToSignalParts(signal.contents); return { ...signal, __isCreatedSignal: true, toDBMessage: (options) => signalToDBMessage(signal, parts, options), toLLMMessage: () => signalToLLMMessage(signal, parts), toDataPart: () => signalToDataPart(signal, parts) }; } function signalToMessage(signal) { return createSignal(signal).toLLMMessage(); } function signalToMastraDBMessage(signal, options) { return createSignal(signal).toDBMessage(options); } function signalToDataPartFormat(signal) { return createSignal(signal).toDataPart(); } function mastraDBMessageToSignal(message) { const metadataSignal = message.content.metadata?.signal; const signalMetadata = metadataSignal && typeof metadataSignal === "object" && !Array.isArray(metadataSignal) ? metadataSignal : void 0; const type = typeof signalMetadata?.type === "string" ? signalMetadata.type : message.type ?? "user-message"; const rawLegacyContents = signalMetadata && "contents" in signalMetadata ? signalMetadata.contents : void 0; const legacyContents = legacyContentsToSignalContents(rawLegacyContents); const partsContents = partsToSignalContents(storagePartsToSignalParts(message.content.parts)); const contents = legacyContents ?? partsContents; const providerMetadata = message.content.providerMetadata; const base = { id: typeof signalMetadata?.id === "string" ? signalMetadata.id : message.id, createdAt: typeof signalMetadata?.createdAt === "string" ? signalMetadata.createdAt : message.createdAt, acceptedAt: typeof signalMetadata?.acceptedAt === "string" ? signalMetadata.acceptedAt : void 0, attributes: signalMetadata?.attributes && typeof signalMetadata.attributes === "object" && !Array.isArray(signalMetadata.attributes) ? signalMetadata.attributes : void 0, metadata: signalMetadata?.metadata && typeof signalMetadata.metadata === "object" && !Array.isArray(signalMetadata.metadata) ? signalMetadata.metadata : void 0, providerOptions: providerMetadata && typeof providerMetadata === "object" && !Array.isArray(providerMetadata) ? providerMetadata : void 0 }; return createSignal({ ...base, type, contents }); } function dataPartToSignal(part) { return createSignal(part.data); } // src/agent/message-list/detection/TypeDetector.ts var TypeDetector = class _TypeDetector { /** * Check if a message is a MastraDBMessage (format 2) */ static isMastraDBMessage(msg) { return Boolean( "content" in msg && msg.content && !Array.isArray(msg.content) && typeof msg.content !== "string" && "format" in msg.content && msg.content.format === 2 ); } /** * Check if a message is a MastraMessageV1 (legacy format) */ static isMastraMessageV1(msg) { return !_TypeDetector.isMastraDBMessage(msg) && ("threadId" in msg || "resourceId" in msg); } /** * Check if a message is either Mastra format (V1 or V2/DB) */ static isMastraMessage(msg) { return _TypeDetector.isMastraDBMessage(msg) || _TypeDetector.isMastraMessageV1(msg); } /** * Check if a message is an AIV4 UIMessage */ static isAIV4UIMessage(msg) { return !_TypeDetector.isMastraMessage(msg) && !_TypeDetector.isAIV4CoreMessage(msg) && "parts" in msg && !_TypeDetector.hasAIV5UIMessageCharacteristics(msg); } /** * Check if a message is an AIV6 UIMessage. * * At runtime, the v5 and v6 UI shapes overlap heavily. We only treat a * message as distinctly v6 if it uses v6-only parts or tool states. */ static isAIV6UIMessage(msg) { return !_TypeDetector.isMastraMessage(msg) && !_TypeDetector.isAIV4CoreMessage(msg) && "parts" in msg && _TypeDetector.hasAIV6UIMessageCharacteristics( msg ); } /** * Check if a message is an AIV5 UIMessage */ static isAIV5UIMessage(msg) { return !_TypeDetector.isMastraMessage(msg) && !_TypeDetector.isAIV6UIMessage(msg) && !_TypeDetector.isAIV5CoreMessage(msg) && "parts" in msg && _TypeDetector.hasAIV5UIMessageCharacteristics(msg); } /** * Check if a message is an AIV4 CoreMessage */ static isAIV4CoreMessage(msg) { return !_TypeDetector.isMastraMessage(msg) && !("parts" in msg) && "content" in msg && !_TypeDetector.hasAIV5CoreMessageCharacteristics(msg); } /** * Check if a message is an AIV6 ModelMessage (CoreMessage equivalent). */ static isAIV6CoreMessage(msg) { return !_TypeDetector.isMastraMessage(msg) && !("parts" in msg) && "content" in msg && _TypeDetector.hasAIV6CoreMessageCharacteristics( msg ); } /** * Check if a message is an AIV5 ModelMessage (CoreMessage equivalent) */ static isAIV5CoreMessage(msg) { return !_TypeDetector.isMastraMessage(msg) && !_TypeDetector.isAIV6CoreMessage(msg) && !("parts" in msg) && "content" in msg && _TypeDetector.hasAIV5CoreMessageCharacteristics(msg); } /** * Check if a message has AIV6-only UI characteristics. */ static hasAIV6UIMessageCharacteristics(msg) { if (!("parts" in msg) || !msg.parts) return false; for (const part of msg.parts) { if (part.type === "source-document") return true; if (part.type === "dynamic-tool") return true; if ("toolCallId" in part && "state" in part && (part.state === "approval-requested" || part.state === "approval-responded" || part.state === "output-denied")) { return true; } } return false; } /** * Check if a message has AIV5 UIMessage characteristics * * V5 UIMessages have specific part types and field names that differ from V4. */ static hasAIV5UIMessageCharacteristics(msg) { if ("toolInvocations" in msg || "reasoning" in msg || "experimental_attachments" in msg || "data" in msg || "annotations" in msg) return false; if (!msg.parts) return false; for (const part of msg.parts) { if ("metadata" in part) return true; if ("toolInvocation" in part) return false; if ("toolCallId" in part) return true; if (part.type === "source") return false; if (part.type === "source-url") return true; if (part.type === "reasoning") { if ("state" in part || "text" in part) return true; if ("reasoning" in part || "details" in part) return false; } if (part.type === "file" && "mediaType" in part) return true; } return false; } /** * Check if a message has AIV6-only core characteristics. */ static hasAIV6CoreMessageCharacteristics(msg) { if ("parts" in msg || typeof msg.content === "string") return false; return msg.content.some((part) => part.type === "tool-approval-request" || part.type === "tool-approval-response"); } /** * Check if a message has AIV5 CoreMessage characteristics * * V5 ModelMessages use different field names from v4 * (for example `output` vs `result`, `input` vs `args`, * `mediaType` vs `mimeType`). */ static hasAIV5CoreMessageCharacteristics(msg) { if ("experimental_providerMetadata" in msg) return false; if (typeof msg.content === "string") return true; for (const part of msg.content) { if (part.type === "tool-result" && "output" in part) return true; if (part.type === "tool-call" && "input" in part) return true; if (part.type === "tool-result" && "result" in part) return false; if (part.type === "tool-call" && "args" in part) return false; if ("mediaType" in part) return true; if ("mimeType" in part) return false; if ("experimental_providerMetadata" in part) return false; if (part.type === "reasoning" && "signature" in part) return false; if (part.type === "redacted-reasoning") return false; } return true; } /** * Get the normalized role for a message * Maps `tool` to `assistant` because tool messages are displayed as part of * the assistant conversation. */ static getRole(message) { if (message.role === "assistant" || message.role === "tool") return "assistant"; if (message.role === "user") return "user"; if (message.role === "system") return "system"; throw new Error( `BUG: add handling for message role ${message.role} in message ${JSON.stringify(message, null, 2)}` ); } }; // 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); return base64; } return String(image); } 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/utils/response-item-metadata.ts var RESPONSE_ITEM_ID_PROVIDERS = ["openai", "azure"]; function formatResponseProviderItemKey(provider, itemId) { return `${provider}:${itemId}`; } function getResponseProviderItemId(providerMetadata) { return getResponseProviderItemIds(providerMetadata)[0]; } function getResponseProviderItemKey(providerMetadata) { const item = getResponseProviderItemId(providerMetadata); return item ? formatResponseProviderItemKey(item.provider, item.itemId) : void 0; } function getResponseProviderItemIds(providerMetadata) { if (!providerMetadata) return []; const azureMetadata = providerMetadata.azure; const azureItemId = azureMetadata?.itemId; const openaiMetadata = providerMetadata.openai; const openaiItemId = openaiMetadata?.itemId; if (typeof azureItemId === "string" && azureItemId === openaiItemId) { return [{ provider: "azure", itemId: azureItemId }]; } return RESPONSE_ITEM_ID_PROVIDERS.flatMap((provider) => { const metadata = providerMetadata[provider]; const itemId = metadata?.itemId; return typeof itemId === "string" ? [{ provider, itemId }] : []; }); } function getResponseProviderItemKeys(providerMetadata) { return getResponseProviderItemIds(providerMetadata).map( ({ provider, itemId }) => formatResponseProviderItemKey(provider, itemId) ); } // src/agent/message-list/utils/provider-compat.ts function ensureGeminiCompatibleMessages(messages, logger) { const result = [...messages]; const firstNonSystemIndex = result.findIndex((m) => m.role !== "system"); if (firstNonSystemIndex === -1) { if (result.length > 0) { logger?.warn( "No user or assistant messages in the request. Some providers (e.g. Gemini) require at least one user message to generate a response." ); } } else if (result[firstNonSystemIndex]?.role === "assistant") { result.splice(firstNonSystemIndex, 0, { role: "user", content: "." }); } return result; } function ensureAnthropicCompatibleMessages(messages, dbMessages) { return messages.map((msg) => enrichToolResultsWithInput(msg, dbMessages)); } function sanitizeOrphanedToolPairs(messages) { const filteredContents = messages.map((m) => Array.isArray(m.content) ? [...m.content] : null); for (let i = 0; i < messages.length; i++) { const current = messages[i]; if (current.role === "assistant" && Array.isArray(current.content)) { const useIds = /* @__PURE__ */ new Set(); const inlineResultIds = /* @__PURE__ */ new Set(); for (const part of current.content) { if (part.type === "tool-call") useIds.add(part.toolCallId); else if (part.type === "tool-result") inlineResultIds.add(part.toolCallId); } const next = messages[i + 1]; const nextResultIds = /* @__PURE__ */ new Set(); if (next && next.role === "tool" && Array.isArray(next.content)) { for (const part of next.content) { if (part.type === "tool-result") nextResultIds.add(part.toolCallId); } } const validPairs = new Set([...useIds].filter((id) => inlineResultIds.has(id) || nextResultIds.has(id))); filteredContents[i] = filteredContents[i].filter((p) => { if (p.type !== "tool-call") return true; const tc = p; return tc.providerExecuted === true || validPairs.has(tc.toolCallId); }); if (next && next.role === "tool" && Array.isArray(next.content)) { filteredContents[i + 1] = filteredContents[i + 1].filter( (p) => p.type !== "tool-result" || validPairs.has(p.toolCallId) ); } } else if (current.role === "tool" && Array.isArray(current.content)) { const prev = messages[i - 1]; if (!prev || prev.role !== "assistant" || !Array.isArray(prev.content)) { filteredContents[i] = filteredContents[i].filter((p) => p.type !== "tool-result"); } } } const result = []; for (let i = 0; i < messages.length; i++) { const original = messages[i]; const filtered = filteredContents[i]; if (filtered == null) { result.push(original); continue; } if (filtered.length === 0) continue; if (Array.isArray(original.content) && filtered.length === original.content.length) { result.push(original); continue; } result.push({ ...original, content: filtered }); } return result; } function enrichToolResultsWithInput(message, dbMessages) { if (message.role !== "tool" || !Array.isArray(message.content)) { return message; } return { ...message, content: message.content.map((part) => { if (part.type === "tool-result") { return { ...part, input: findToolCallArgs(dbMessages, part.toolCallId) }; } return part; }) }; } function hasOpenAIReasoningItemId(part) { return Boolean(getOpenAIReasoningItemId(part)); } function hasResponseProviderItemId(part) { return Boolean(getResponseProviderItemIdFromPart(part)); } function getOpenAIReasoningItemId(part) { if (!part || typeof part !== "object") return void 0; const partAny = part; const providerMetadata = partAny.providerMetadata; const openaiMetadata = providerMetadata?.openai; return typeof openaiMetadata?.itemId === "string" ? openaiMetadata.itemId : void 0; } function getResponseProviderItemIdFromPart(part) { if (!part || typeof part !== "object") return void 0; const partAny = part; return getResponseProviderItemId(partAny.providerMetadata) || getResponseProviderItemId(partAny.providerOptions); } function findToolCallArgs(messages, toolCallId) { for (let i = messages.length - 1; i >= 0; i--) { const msg = messages[i]; if (!msg || msg.role !== "assistant") { continue; } if (msg.content.parts) { const toolCallPart = msg.content.parts.find( (p) => p.type === "tool-invocation" && p.toolInvocation.toolCallId === toolCallId ); if (toolCallPart && toolCallPart.type === "tool-invocation") { const args = toolCallPart.toolInvocation.args || {}; if (typeof args === "object" && Object.keys(args).length > 0) { return args; } } } if (msg.content.toolInvocations) { const toolInvocation = msg.content.toolInvocations.find((inv) => inv.toolCallId === toolCallId); if (toolInvocation) { const args = toolInvocation.args || {}; if (typeof args === "object" && Object.keys(args).length > 0) { return args; } } } } return {}; } // src/agent/message-list/adapters/AIV4Adapter.ts function getDisplayTransform(providerMetadata, phase, fallback, enabled = true) { if (!enabled) { return fallback; } const transform = getTransformedToolPayload(providerMetadata, "display", phase); return hasTransformedToolPayload(transform) ? transform.transformed : fallback; } function transformV4ToolInvocationForDisplay(invocation, providerMetadata, enabled) { return { ...invocation, args: getDisplayTransform(providerMetadata, "input-available", invocation.args, enabled), ...invocation.state === "result" ? { result: getDisplayTransform( providerMetadata, "output-available", getDisplayTransform(providerMetadata, "error", invocation.result, enabled), enabled ) } : {} }; } function preserveExtendedParts(parts) { return parts; } function filterEmptyTextParts(parts) { const hasNonEmptyParts = parts.some((part) => !(part.type === "text" && part.text === "")); if (!hasNonEmptyParts) return parts; return parts.filter((part) => { if (part.type === "text") { return part.text !== ""; } return true; }); } function getSignalType(message) { const signal = message.content.metadata?.signal; if (signal && typeof signal === "object" && !Array.isArray(signal)) { const type = signal.type; return typeof type === "string" ? type : message.type; } return message.type; } function toSignalDataPart(message, contents) { const signal = message.content.metadata?.signal && typeof message.content.metadata.signal === "object" ? message.content.metadata.signal : {}; const metadata = signal.metadata && typeof signal.metadata === "object" && !Array.isArray(signal.metadata) ? signal.metadata : {}; return { type: `data-${getSignalType(message) ?? "signal"}`, data: { id: typeof signal.id === "string" ? signal.id : message.id, type: getSignalType(message) ?? "signal", contents: "contents" in signal ? signal.contents : contents, createdAt: typeof signal.createdAt === "string" ? signal.createdAt : message.createdAt.toISOString(), ...Object.keys(metadata).length ? { metadata } : {} } }; } var AIV4Adapter = class { /** * Convert MastraDBMessage to AI SDK V4 UIMessage */ static toUIMessage(m, options) { const transformToolPayloads = options?.transformToolPayloads ?? true; 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 = []; const sourceParts = m.content.parts ?? []; if (sourceParts.length) { for (const part of sourceParts) { if (part.type === `file`) { let normalizedUrl; if (typeof part.data === "string") { const categorized = categorizeFileData(part.data, part.mimeType); if (categorized.type === "raw") { normalizedUrl = createDataUri(part.data, part.mimeType || "application/octet-stream"); } else { normalizedUrl = part.data; } } else { normalizedUrl = part.data; } experimentalAttachments.push({ contentType: part.mimeType, url: normalizedUrl }); } else if (part.type === "tool-invocation" && (part.toolInvocation.state === "call" || part.toolInvocation.state === "partial-call")) { continue; } else if (part.type === "tool-invocation") { const toolInvocation = { ...part.toolInvocation, args: getDisplayTransform( part.providerMetadata, "input-available", part.toolInvocation.args, transformToolPayloads ), ...part.toolInvocation.state === "result" ? { result: getDisplayTransform( part.providerMetadata, "output-available", getDisplayTransform( part.providerMetadata, "error", part.toolInvocation.result, transformToolPayloads ), transformToolPayloads ) } : {} }; let currentStep = -1; let toolStep = -1; for (const innerPart of sourceParts) { if (innerPart.type === `step-start`) currentStep++; if (innerPart.type === `tool-invocation` && innerPart.toolInvocation.toolCallId === part.toolInvocation.toolCallId) { toolStep = currentStep; break; } } if (toolStep >= 0) { const preparedInvocation = { step: toolStep, ...toolInvocation }; parts.push({ type: "tool-invocation", toolInvocation: preparedInvocation }); } else { parts.push({ type: "tool-invocation", toolInvocation }); } } else { parts.push(part); } } } if (parts.length === 0 && experimentalAttachments.length > 0) { parts.push({ type: "text", text: "" }); } const signalType = m.role === "signal" ? getSignalType(m) : void 0; const isUserMessageSignal = signalType === "user-message"; const v4Parts = preserveExtendedParts( m.role === "signal" && !isUserMessageSignal ? [toSignalDataPart(m, m.content.content || contentString)] : parts ); if (m.role === `user`) { const uiMessage2 = { id: m.id, role: m.role, content: m.content.content || contentString, createdAt: m.createdAt, parts: v4Parts, experimental_attachments: experimentalAttachments }; if (m.content.metadata) { uiMessage2.metadata = m.content.metadata; } return uiMessage2; } else if (m.role === `assistant`) { const isSingleTextContentArray = Array.isArray(m.content.content) && m.content.content.length === 1 && m.content.content[0].type === `text`; const uiMessage2 = { id: m.id, role: m.role, content: isSingleTextContentArray ? contentString : m.content.content || contentString, createdAt: m.createdAt, parts: v4Parts, reasoning: void 0, toolInvocations: `toolInvocations` in m.content ? m.content.toolInvocations?.filter((t) => t.state === "result").map((toolInvocation) => { const partProviderMetadata = m.content.parts?.find( (part) => part.type === "tool-invocation" && part.toolInvocation.toolCallId === toolInvocation.toolCallId )?.providerMetadata; return transformV4ToolInvocationForDisplay( toolInvocation, partProviderMetadata, transformToolPayloads ); }) : void 0 }; if (m.content.metadata) { uiMessage2.metadata = m.content.metadata; } return uiMessage2; } const uiMessage = { id: m.id, role: m.role === "signal" ? isUserMessageSignal ? "user" : "system" : m.role, content: m.role === "signal" && !isUserMessageSignal ? "" : m.content.content || contentString, createdAt: m.createdAt, parts: v4Parts, experimental_attachments: experimentalAttachments }; if (m.content.metadata) { uiMessage.metadata = m.content.metadata; } return uiMessage; } /** * Converts a MastraDBMessage system message directly to AIV4 CoreMessage format */ static systemToV4Core(message) { if (message.role !== `system` || !message.content.content) throw new MastraError({ id: "INVALID_SYSTEM_MESSAGE_FORMAT", domain: "AGENT" /* AGENT */, category: "USER" /* USER */, text: `Invalid system message format. System messages must include 'role' and 'content' properties. The content should be a string.`, details: { receivedMessage: JSON.stringify(message, null, 2) } }); const coreMessage = { role: "system", content: message.content.content }; if (message.content.providerMetadata) { coreMessage.experimental_providerMetadata = message.content.providerMetadata; } return coreMessage; } /** * Convert AI SDK V4 UIMessage to MastraDBMessage */ static fromUIMessage(message, ctx, messageSource) { const filteredParts = message.parts ? filterEmptyTextParts(message.parts) : []; const content = { format: 2, parts: filteredParts }; 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 || ctx.newMessageId(), role: TypeDetector.getRole(message), createdAt: ctx.generateCreatedAt(messageSource, message.createdAt), threadId: ctx.memoryInfo?.threadId, resourceId: ctx.memoryInfo?.resourceId, content }; } /** * Convert AI SDK V4 CoreMessage to MastraDBMessage */ static fromCoreMessage(coreMessage, ctx, messageSource) { const id = `id` in coreMessage ? coreMessage.id : ctx.newMessageId(); const parts = []; const experimentalAttachments = []; const toolInvocations = []; const isSingleTextContent = messageSource === `response` && Array.isArray(coreMessage.content) && coreMessage.content.length === 1 && coreMessage.content[0] && coreMessage.content[0].type === `text` && `text` in coreMessage.content[0] && coreMessage.content[0].text; if (isSingleTextContent && messageSource === `response`) { coreMessage.content = isSingleTextContent; } if (typeof coreMessage.content === "string") { parts.push({ type: "text", text: coreMessage.content }); } else if (Array.isArray(coreMessage.content)) { for (const aiV4Part of coreMessage.content) { switch (aiV4Part.type) { case "text": { const prevPart = parts.at(-1); if (coreMessage.role === "assistant" && prevPart && prevPart.type === "tool-invocation") { parts.push({ type: "step-start" }); } const part = { type: "text", text: aiV4Part.text }; if (aiV4Part.providerOptions) { part.providerMetadata = aiV4Part.providerOptions; } parts.push(part); break; } case "tool-call": { const part = { type: "tool-invocation", toolInvocation: { state: "call", toolCallId: aiV4Part.toolCallId, toolName: aiV4Part.toolName, args: aiV4Part.args } }; if (aiV4Part.providerOptions) { part.providerMetadata = aiV4Part.providerOptions; } parts.push(part); break; } case "tool-result": { let toolArgs = {}; const toolCallInSameMsg = coreMessage.content.find( (p) => p.type === "tool-call" && p.toolCallId === aiV4Part.toolCallId ); if (toolCallInSameMsg && toolCallInSameMsg.type === "tool-call") { toolArgs = toolCallInSameMsg.args; } if (Object.keys(toolArgs).length === 0 && ctx.dbMessages) { toolArgs = findToolCallArgs(ctx.dbMessages, aiV4Part.toolCallId); } const invocation = { state: "result", toolCallId: aiV4Part.toolCallId, toolName: aiV4Part.toolName, result: aiV4Part.result ?? "", args: toolArgs }; const part = { type: "tool-invocation", toolInvocation: invocation }; if (aiV4Part.providerOptions) { part.providerMetadata = aiV4Part.providerOptions; } parts.push(part); toolInvocations.push(invocation); } break; case "reasoning": { const part = { type: "reasoning", reasoning: "", details: [{ type: "text", text: aiV4Part.text, signature: aiV4Part.signature }] }; if (aiV4Part.providerOptions) { part.providerMetadata = aiV4Part.providerOptions; } parts.push(part); } break; case "redacted-reasoning": { const part = { type: "reasoning", reasoning: "", details: [{ type: "redacted", data: aiV4Part.data }] }; if (aiV4Part.providerOptions) { part.providerMetadata = aiV4Part.providerOptions; } parts.push(part); } break; case "image": { const part = { type: "file", data: imageContentToString(aiV4Part.image), mimeType: aiV4Part.mimeType }; if (aiV4Part.providerOptions) { part.providerMetadata = aiV4Part.providerOptions; } parts.push(part); break; } case "file": { if (aiV4Part.data instanceof URL) { const part = { type: "file", data: aiV4Part.data.toString(), mimeType: aiV4Part.mimeType }; if (aiV4Part.providerOptions) { part.providerMetadata = aiV4Part.providerOptions; } if (aiV4Part.filename) { part.filename = aiV4Part.filename; } parts.push(part); } else if (typeof aiV4Part.data === "string") { const categorized = categorizeFileData(aiV4Part.data, aiV4Part.mimeType); if (categorized.type === "url" || categorized.type === "dataUri") { const part = { type: "file", data: aiV4Part.data, mimeType: categorized.mimeType || "image/png" }; if (aiV4Part.providerOptions) { part.providerMetadata = aiV4Part.providerOptions; } if (aiV4Part.filename) { part.filename = aiV4Part.filename; } parts.push(part); } else { try { const part = { type: "file", mimeType: categorized.mimeType || "image/png", data: convertDataContentToBase64String(aiV4Part.data) }; if (aiV4Part.providerOptions) { part.providerMetadata = aiV4Part.providerOptions; } if (aiV4Part.filename) { part.filename = aiV4Part.filename; } parts.push(part); } catch (error) { console.error(`Failed to convert binary data to base64 in CoreMessage file part: ${error}`, error); } } } else { try { const part = { type: "file", mimeType: aiV4Part.mimeType, data: convertDataContentToBase64String(aiV4Part.data) }; if (aiV4Part.providerOptions) { part.providerMetadata = aiV4Part.providerOptions; } if (aiV4Part.filename) { part.filename = aiV4Part.filename; } parts.push(part); } catch (error) { console.error(`Failed to convert binary data to base64 in CoreMessage file part: ${error}`, error); } } break; } } } } const filteredParts = filterEmptyTextParts(parts); const content = { format: 2, parts: filteredParts }; if (toolInvocations.length) content.toolInvocations = toolInvocations; if (typeof coreMessage.content === `string`) content.content = coreMessage.content; if (experimentalAttachments.length) content.experimental_attachments = experimentalAttachments; if (coreMessage.providerOptions) { content.providerMetadata = coreMessage.providerOptions; } else if ("experimental_providerMetadata" in coreMessage && coreMessage.experimental_providerMetadata) { content.providerMetadata = coreMessage.experimental_providerMetadata; } if ("metadata" in coreMessage && coreMessage.metadata !== null && coreMessage.metadata !== void 0) { content.metadata = coreMessage.metadata; } const rawCreatedAt = "metadata" in coreMessage && coreMessage.metadata && typeof coreMessage.metadata === "object" && "createdAt" in coreMessage.metadata ? coreMessage.metadata.createdAt : void 0; return { id, role: TypeDetector.getRole(coreMessage), createdAt: ctx.generateCreatedAt(messageSource, rawCreatedAt), threadId: ctx.memoryInfo?.threadId, resourceId: ctx.memoryInfo?.resourceId, content }; } }; // src/agent/message-list/utils/tool-name.ts var TOOL_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/; var FALLBACK_TOOL_NAME = "unknown_tool"; function sanitizeToolName(toolName) { if (typeof toolName !== "string") { return FALLBACK_TOOL_NAME; } return TOOL_NAME_PATTERN.test(toolName) ? toolName : FALLBACK_TOOL_NAME; } // src/agent/message-list/adapters/AIV5Adapter.ts function filterEmptyTextParts2(parts) { const hasNonEmptyParts = parts.some((part) => !(part.type === "text" && part.text === "")); if (!hasNonEmptyParts) return parts; return parts.filter((part) => { if (part.type === "text") { return part.text !== ""; } return true; }); } function getSignalType2(message) { const signal = message.content.metadata?.signal; if (signal && typeof signal === "object" && !Array.isArray(signal)) { const type = signal.type; return typeof type === "string" ? type : message.type; } return message.type; } function getTextContent(message) { return typeof message.content.content === "string" ? message.content.content : message.content.parts.find((part) => part.type === "text")?.text ?? ""; } function toSignalDataPart2(message) { const signal = message.content.metadata?.signal && typeof message.content.metadata.signal === "object" ? message.content.metadata.signal : {}; const metadata = signal.metadata && typeof signal.metadata === "object" && !Array.isArray(signal.metadata) ? signal.metadata : {}; const type = getSignalType2(message) ?? "signal"; return { type: `data-${type}`, data: { id: typeof signal.id === "string" ? signal.id : message.id, type, contents: "contents" in signal ? signal.contents : getTextContent(message), createdAt: typeof signal.createdAt === "string" ? signal.createdAt : message.createdAt.toISOString(), ...Object.keys(metadata).length ? { metadata } : {} } }; } function getToolName(type) { if (typeof type === "object" && type && "type" in type) { type = type.type; } if (typeof type !== "string") { return sanitizeToolName(type); } if (type === "dynamic-tool") { return "dynamic-tool"; } if (type.startsWith("tool-")) { return sanitizeToolName(type.slice("tool-".length)); } return sanitizeToolName(type); } function mergeMastraCreatedAt(metadata, createdAt) { if (createdAt == null) { return metadata; } return { ...metadata || {}, mastra: { ...(metadata || {}).mastra || {}, createdAt } }; } function getMastraCreatedAt(providerMetadata) { const value = providerMetadata?.mastra; if (!value || typeof value !== "object") { return void 0; } const createdAt = value.createdAt; return typeof createdAt === "number" ? createdAt : void 0; } function getDisplayTransform2(providerMetadata, phase, fallback, enabled = true) { if (!enabled) { return fallback; } const transform = getTransformedToolPayload(providerMetadata, "display", phase); return hasTransformedToolPayload(transform) ? transform.transformed : fallback; } function transformToolStateDataForDisplay(data, phase, enabled = true) { if (!enabled) { return data; } if (!data || typeof data !== "object") { return data; } const stateData = data; const metadata = stateData.metadata ?? stateData.providerMetadata; const argsTransform = getTransformedToolPayload(metadata, "display", phase); const inputTransform = getTransformedToolPayload(metadata, "display", "input-available"); const transformedArgs = phase === "approval" ? hasTransformedToolPayload(argsTransform) ? argsTransform.transformed : hasTransformedToolPayload(inputTransform) ? inputTransform.transformed : void 0 : hasTransformedToolPayload(inputTransform) ? inputTransform.transformed : hasTransformedToolPayload(argsTransform) ? argsTransform.transformed : void 0; const transformedSuspendPayload = phase === "suspend" && hasTransformedToolPayload(argsTransform) ? argsTransform.transformed : void 0; return { ...stateData, ...transformedArgs !== void 0 ? { args: transformedArgs } : {}, ...transformedSuspendPayload !== void 0 ? { suspendPayload: transformedSuspendPayload } : {} }; } var AIV5Adapter = class { /** * Direct conversion from MastraDBMessage to AIV5 UIMessage */ static toUIMessage(dbMsg, options) { const signalType = dbMsg.role === "signal" ? getSignalType2(dbMsg) : void 0; const isUserMessageSignal = signalType === "user-message"; const transformToolPayloads = options?.transform