@mastra/core
Version:
1,340 lines (1,333 loc) • 234 kB
JavaScript
import { isToolUIPart, convertToModelMessages } from './chunk-KJTM46JT.js';
import { isToolUIPart as isToolUIPart$1 } from './chunk-TOBPSKTN.js';
import { convertToCoreMessages } from './chunk-D23QKBCZ.js';
import { DefaultGeneratedFileWithType } from './chunk-HHYICF3X.js';
import { fetchWithRetry } from './chunk-RBJJ4Y4N.js';
import { MastraError, ErrorCategory, ErrorDomain } from './chunk-M7RBQNFP.js';
import { v4 } from '@lukeed/uuid';
import { convertUint8ArrayToBase64, isUrlSupported, convertBase64ToUint8Array } from '@ai-sdk/provider-utils-v5';
// src/tools/payload-transform.ts
function normalizeToolPayloadTransformPolicy(policy) {
if (!policy) {
return void 0;
}
if ("transformToolPayload" in policy) {
return policy;
}
if ("projectToolPayload" in policy && policy.projectToolPayload) {
return {
transformToolPayload: policy.projectToolPayload,
targets: policy.targets
};
}
return void 0;
}
var PHASE_TO_TOOL_TRANSFORMER = {
"input-delta": "inputDelta",
"input-available": "input",
"output-available": "output",
error: "error",
approval: "approval",
suspend: "suspend",
resume: "resume"
};
function isPolicyConfiguredForTarget(policy, target) {
return Boolean(policy?.transformToolPayload && (!policy.targets || policy.targets.includes(target)));
}
function isTransformConfigured(source, target) {
return Boolean(isPolicyConfiguredForTarget(source?.policy, target) || source?.toolTransform?.[target]);
}
function getToolTransformer(source, target, phase) {
return source?.toolTransform?.[target]?.[PHASE_TO_TOOL_TRANSFORMER[phase]];
}
function safePlaceholder(context) {
return {
message: `Tool ${context.phase} payload unavailable`
};
}
async function transformOneTarget(context, source, logger) {
const configured = isTransformConfigured(source, context.target);
if (!configured) {
return void 0;
}
const transformers = [
isPolicyConfiguredForTarget(source?.policy, context.target) ? source?.policy?.transformToolPayload : void 0,
getToolTransformer(source, context.target, context.phase)
].filter(Boolean);
if (transformers.length === 0) {
return context.phase === "input-delta" ? { suppress: true } : { transformed: safePlaceholder(context) };
}
for (const transformer of transformers) {
try {
const transformed = await transformer(context);
if (transformed !== void 0) {
return { transformed };
}
} catch (error) {
logger?.warn?.("Tool payload transform failed", {
toolName: context.toolName,
toolCallId: context.toolCallId,
target: context.target,
phase: context.phase,
error
});
return context.phase === "input-delta" ? { suppress: true, failed: true } : { transformed: safePlaceholder(context), failed: true };
}
}
return context.phase === "input-delta" ? { suppress: true } : { transformed: safePlaceholder(context) };
}
async function transformToolPayloadForTargets(context, source, logger) {
const display = await transformOneTarget({ ...context, target: "display" }, source, logger);
const transcript = await transformOneTarget({ ...context, target: "transcript" }, source, logger);
if (!display && !transcript) {
return void 0;
}
return {
...display ? { display: { [context.phase]: display } } : {},
...transcript ? { transcript: { [context.phase]: transcript } } : {}
};
}
function getTransformedToolPayload(metadata, target, phase) {
const mastraMetadata = metadata?.mastra;
const state = mastraMetadata?.toolPayloadTransform?.[target]?.[phase] ?? mastraMetadata?.toolPayloadProjection?.[target]?.[phase];
if (state && Object.prototype.hasOwnProperty.call(state, "projected") && !Object.prototype.hasOwnProperty.call(state, "transformed")) {
const { projected, ...rest } = state;
return { ...rest, transformed: projected };
}
return state;
}
function hasTransformedToolPayload(transform) {
return Boolean(transform && Object.prototype.hasOwnProperty.call(transform, "transformed"));
}
function mergeTransformMetadata(existing, next) {
if (!existing) {
return next;
}
if (!next) {
return existing;
}
return {
display: {
...existing.display ?? {},
...next.display ?? {}
},
transcript: {
...existing.transcript ?? {},
...next.transcript ?? {}
}
};
}
function withToolPayloadTransformProviderMetadata(providerMetadata, transformMetadata) {
const transform = transformMetadata?.mastra?.toolPayloadTransform;
if (!transform) {
return providerMetadata;
}
return {
...providerMetadata ?? {},
mastra: {
...providerMetadata?.mastra ?? {},
toolPayloadTransform: mergeTransformMetadata(providerMetadata?.mastra?.toolPayloadTransform, transform)
}
};
}
function withToolPayloadTransformMetadata(chunk, transform) {
if (!transform) {
return chunk;
}
return {
...chunk,
metadata: {
...chunk.metadata ?? {},
mastra: {
...chunk.metadata?.mastra ?? {},
toolPayloadTransform: mergeTransformMetadata(chunk.metadata?.mastra?.toolPayloadTransform, transform)
}
}
};
}
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 normalizeSignalType(input) {
if (input.type === "user-message") {
return { type: "user", tagName: input.tagName ?? "user" };
}
if (input.type === "system-reminder") {
return { type: "reactive", tagName: input.tagName ?? "system-reminder" };
}
if (input.type === "user" || input.type === "state" || input.type === "notification") {
return { type: input.type, tagName: input.tagName ?? input.type };
}
if (input.type === "reactive") {
return { type: "reactive", tagName: input.tagName ?? "system-reminder" };
}
throw new Error(
`Invalid signal type: ${input.type}. Use a supported signal type and set tagName for custom XML tags.`
);
}
function normalizeSignal(signal) {
const { type, tagName } = normalizeSignalType(signal);
return {
...signal,
type,
tagName,
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("&", "&").replaceAll("<", "<").replaceAll(">", ">");
}
function escapeXmlAttribute(value) {
return escapeXml(value).replaceAll('"', """);
}
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) {
const tagName = signal.tagName ?? normalizeSignalType(signal).tagName;
assertXmlName(tagName, "tag name");
const attributesXml = signalAttributesToXml(signal.attributes);
if (!signal.contents) return `<${tagName}${attributesXml} />`;
return `<${tagName}${attributesXml}>${escapeXml(signal.contents)}</${tagName}>`;
}
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, tagName: signal.tagName, attributes: signal.attributes });
out.unshift({ type: "text", text: markerText });
}
return out;
}
function signalToLLMMessage(signal, parts) {
const isUserMessage = signal.type === "user";
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: signal.type === "user" ? "data-user-message" : "data-signal",
data: {
id: signal.id,
type: signal.type,
tagName: signal.tagName,
contents: partsToSignalContents(parts),
createdAt: signal.createdAt.toISOString(),
...signal.acceptedAt ? { acceptedAt: signal.acceptedAt.toISOString() } : {},
...signal.attributes ? { attributes: signal.attributes } : {},
...signal.metadata ? { metadata: signal.metadata } : {},
...signal.providerOptions ? { providerOptions: signal.providerOptions } : {}
},
transient: true
};
}
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.tagName,
content: {
format: 2,
parts: storageParts,
...signal.providerOptions ? { providerMetadata: signal.providerOptions } : {},
metadata: {
signal: {
id: signal.id,
type: signal.type,
tagName: signal.tagName,
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 resolveDeliveryAttributes(signal, attributes) {
if (!attributes || Object.keys(attributes).length === 0) return signal;
return createSignal({
...signal,
attributes: { ...signal.attributes, ...attributes }
});
}
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 rawType = typeof signalMetadata?.type === "string" ? signalMetadata.type : message.type ?? "user-message";
const type = rawType;
const tagName = typeof signalMetadata?.tagName === "string" ? signalMetadata.tagName : void 0;
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, tagName, contents });
}
function createMessageSignal(input, options) {
const message = typeof input === "string" || Array.isArray(input) ? { contents: input } : input;
return createSignal({
...message,
...options,
type: "user",
tagName: "user"
});
}
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 getSignalTagName(message) {
const signal = message.content.metadata?.signal;
if (signal && typeof signal === "object" && !Array.isArray(signal)) {
const tagName = signal.tagName;
if (typeof tagName === "string") return tagName;
}
const type = getSignalType(message);
if (type === "user") return "user";
if (type === "reactive") return message.type;
return type;
}
function isUserSignalType(type) {
return type === "user" || type === "user-message";
}
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 : {};
const attributes = signal.attributes && typeof signal.attributes === "object" && !Array.isArray(signal.attributes) ? signal.attributes : {};
const type = getSignalType(message) ?? "signal";
const tagName = getSignalTagName(message) ?? type;
return {
type: type === "user" ? "data-user-message" : "data-signal",
data: {
id: typeof signal.id === "string" ? signal.id : message.id,
type,
tagName,
contents: "contents" in signal ? signal.contents : contents,
createdAt: typeof signal.createdAt === "string" ? signal.createdAt : message.createdAt.toISOString(),
...typeof signal.acceptedAt === "string" ? { acceptedAt: signal.acceptedAt } : {},
...Object.keys(attributes).length ? { attributes } : {},
...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 isDeniedApproval = part.toolInvocation.state === "output-denied";
const toolInvocation = {
...part.toolInvocation,
// v4 has no denied state and AI SDK v4's convertToCoreMessages requires every
// tool invocation to carry a result. Downgrade a declined approval to a normal
// result whose value is the decline reason so the conversion accepts it.
...isDeniedApproval ? { state: "result" } : {},
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
)
} : isDeniedApproval ? { result: part.toolInvocation.approval?.reason ?? "Tool call was not approved by the user" } : {}
};
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 = isUserSignalType(signalType);
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: ErrorDomain.AGENT,
category: ErrorCategory.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: aiV4Part.text,
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