@mastra/core
Version:
1,230 lines • 470 kB
JavaScript
const require_error = require("./error-B-e62x-A.cjs");
const require_deep_equal = require("./deep-equal-BvQBG8wE.cjs");
const require_payload_transform = require("./payload-transform-DJzDdfpE.cjs");
const require_signals = require("./signals-D2CulJo3.cjs");
const require_dist = require("./dist-BGdEcgoh.cjs");
const require_dist_3jq9sPhA = require("./dist-3jq9sPhA-BtxtCkky.cjs");
const require_dist$1 = require("./dist-CN0r_lDr.cjs");
const require_content = require("./content-fINNgB3A.cjs");
const require_file = require("./file-BdP8crPU.cjs");
let zod_v4 = require("zod/v4");
let _lukeed_uuid = require("@lukeed/uuid");
let _ai_sdk_provider_utils_v5 = require("@ai-sdk/provider-utils-v5");
//#region src/agent/message-list/detection/TypeDetector.ts
/**
* TypeDetector - Centralized type detection for different message formats
*
* This class provides consistent type detection across all message formats,
* which is critical for:
* - Determining which conversion path to use
* - Validating incoming message formats
* - Providing better TypeScript type narrowing
*
* The detection order is important because some formats share similar properties.
*/
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)}`);
}
};
//#endregion
//#region src/agent/message-list/prompt/image-utils.ts
/**
* Parses a data URI string into its components.
* Format: data:[<mediatype>][;base64],<data>
*
* @param dataUri - The data URI string to parse
* @returns Parsed components including MIME type and base64 content
*/
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(";");
return {
isDataUri: true,
mimeType: (semicolonIndex !== -1 ? header.substring(0, semicolonIndex) : header) || void 0,
base64Content
};
}
/**
* Creates a data URI from base64 content and MIME type.
*
* @param base64Content - The base64 encoded content
* @param mimeType - The MIME type (defaults to 'application/octet-stream')
* @returns A properly formatted data URI
*/
function createDataUri(base64Content, mimeType = "application/octet-stream") {
if (base64Content.startsWith("data:")) return base64Content;
return `data:${mimeType};base64,${base64Content}`;
}
/**
* Converts various image data formats to a string representation.
* - Strings are returned as-is (could be URLs or data URIs)
* - URL objects are converted to strings
* - Binary data (Uint8Array, ArrayBuffer, Buffer) is converted to base64
*
* @param image - The image data in various formats
* @param fallbackMimeType - MIME type to use when creating data URIs from binary data
* @returns String representation of the image (URL, data URI, or base64)
*/
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 = require_signals.convertDataContentToBase64String(image);
if (fallbackMimeType && !base64.startsWith("data:")) return `data:${fallbackMimeType};base64,${base64}`;
return base64;
}
return String(image);
}
/**
* Gets a stable cache key component for image content.
* Used for generating hash keys for caching purposes.
*
* @param image - The image data in various formats
* @returns A string or number suitable for cache key generation
*/
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;
}
/**
* Checks if a string is a valid URL (including protocol-relative URLs).
*
* @param str - The string to check
* @returns true if the string is a valid URL
*/
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;
}
}
/**
* Categorizes a string as a URL, data URI, or raw data (base64/other).
* Also extracts MIME type from data URIs when present.
*
* @param data - The string data to categorize
* @param fallbackMimeType - Optional fallback MIME type
* @returns Categorized data with type and extracted MIME type
*/
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 (data.startsWith("file-")) return {
type: "providerFileId",
mimeType,
data
};
if (isValidUrl(data)) return {
type: "url",
mimeType,
data
};
return {
type: "raw",
mimeType,
data
};
}
/**
* Resolve a stored file part's media type and payload across the AI SDK v4 and v5 shapes.
*
* Stored "v2" file parts are typed as the AI SDK v4 UI shape (`mimeType`/`data`), but
* v5-shaped file parts (`mediaType`/`url`, renamed in the v5 Media Type Standardization)
* reach the same read sites. Reading only the v4 fields leaves a v5 part with both values
* `undefined`, which downstream becomes `contentType: undefined` (making `attachmentsToParts`
* throw) or collapses distinct parts onto a single cache key. Read whichever shape is present.
*
* Returns the RAW resolved values (undefined-preserving); call sites that build a
* `contentType` should apply their own `'application/octet-stream'` fallback. Mirrors #17366.
*/
function resolveFilePartMediaTypeAndData(part) {
const filePart = part;
return {
mediaType: filePart.mimeType ?? filePart.mediaType,
data: filePart.data ?? filePart.url
};
}
//#endregion
//#region src/agent/message-list/utils/response-item-metadata.ts
const 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 azureItemId = providerMetadata.azure?.itemId;
const openaiItemId = providerMetadata.openai?.itemId;
if (typeof azureItemId === "string" && azureItemId === openaiItemId) return [{
provider: "azure",
itemId: azureItemId
}];
return RESPONSE_ITEM_ID_PROVIDERS.flatMap((provider) => {
const itemId = providerMetadata[provider]?.itemId;
return typeof itemId === "string" ? [{
provider,
itemId
}] : [];
});
}
function getResponseProviderItemKeys(providerMetadata) {
return getResponseProviderItemIds(providerMetadata).map(({ provider, itemId }) => formatResponseProviderItemKey(provider, itemId));
}
//#endregion
//#region src/agent/message-list/utils/provider-compat.ts
/**
* Ensures message array is compatible with Gemini API requirements.
*
* Gemini API requires:
* 1. The first non-system message must be from the user role
* 2. Cannot have only system messages - at least one user/assistant is required
*
* @param messages - Array of model messages to validate and fix
* @param logger - Optional logger for warnings
* @returns Modified messages array that satisfies Gemini requirements
*
* @see https://github.com/mastra-ai/mastra/issues/7287 - Tool call ordering
* @see https://github.com/mastra-ai/mastra/issues/8053 - Single turn validation
* @see https://github.com/mastra-ai/mastra/issues/13045 - Empty thread support
*/
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;
}
/**
* Ensures model messages are compatible with Anthropic API requirements.
*
* Anthropic API requires tool-result parts to include an 'input' field
* that matches the original tool call arguments.
*
* @param messages - Array of model messages to transform
* @param dbMessages - MastraDB messages to look up tool call args from
* @returns Messages with tool-result parts enriched with input field
*
* @see https://github.com/mastra-ai/mastra/issues/11376 - Anthropic models fail with empty object tool input
*/
function ensureAnthropicCompatibleMessages(messages, dbMessages) {
return messages.map((msg) => enrichToolResultsWithInput(msg, dbMessages));
}
/**
* Tool call ids in the assistant message at `index` that already have a matching tool_result,
* either inline in the same message or in the tool message immediately after it — the only
* two positions providers accept.
*/
function collectPairedToolCallIds(messages, index) {
const current = messages[index];
if (!Array.isArray(current.content)) return /* @__PURE__ */ new Set();
const useIds = /* @__PURE__ */ new Set();
const resultIds = /* @__PURE__ */ new Set();
for (const part of current.content) if (part.type === "tool-call") useIds.add(part.toolCallId);
else if (part.type === "tool-result") resultIds.add(part.toolCallId);
const next = messages[index + 1];
if (next && next.role === "tool" && Array.isArray(next.content)) {
for (const part of next.content) if (part.type === "tool-result") resultIds.add(part.toolCallId);
}
return new Set([...useIds].filter((id) => resultIds.has(id)));
}
/**
* Removes orphan tool_use / tool_result blocks. Anthropic requires every tool_result
* to be in the message immediately after its matching tool_use, and every tool_use
* to have a matching tool_result in the next message. Recall windows can slice
* through a parallel tool-call group and leave behind half a pair.
*/
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 validPairs = collectPairedToolCallIds(messages, i);
const next = messages[i + 1];
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;
}
/**
* Keeps result-less tool calls in the prompt by pairing each one with a placeholder result.
*
* Used when the caller opted to keep suspended tool calls visible to the agent
* (`filterIncompleteToolCalls: false`). Providers reject a tool_use with no matching
* tool_result, so dropping the call is not the only option — synthesizing the missing
* half keeps the pending call in context while satisfying the pairing requirement.
*
* Provider-executed calls are left alone: they may be legitimately deferred to the next
* request, and giving them a result would resolve a call the provider intends to resume.
*
* @see https://github.com/mastra-ai/mastra/issues/20610
*/
function pairOrphanedToolCalls(messages) {
const paired = [];
for (let i = 0; i < messages.length; i++) {
const current = messages[i];
paired.push(current);
if (current.role !== "assistant" || !Array.isArray(current.content)) continue;
const pairedIds = collectPairedToolCallIds(messages, i);
const placeholders = [];
for (const part of current.content) {
if (part.type !== "tool-call") continue;
const tc = part;
if (tc.providerExecuted === true || pairedIds.has(tc.toolCallId)) continue;
placeholders.push({
type: "tool-result",
toolCallId: tc.toolCallId,
toolName: tc.toolName,
output: {
type: "json",
value: { status: "pending" }
}
});
}
if (placeholders.length === 0) continue;
const next = messages[i + 1];
if (next && next.role === "tool" && Array.isArray(next.content)) {
paired.push({
...next,
content: [...next.content, ...placeholders]
});
i++;
} else paired.push({
role: "tool",
content: placeholders
});
}
return sanitizeOrphanedToolPairs(paired);
}
/**
* Enriches a single message's tool-result parts with input field
*/
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;
})
};
}
/**
* Checks if a message part has an OpenAI reasoning itemId.
*
* OpenAI Responses reasoning items are tracked via `providerMetadata.openai.itemId`.
* Each reasoning item has a unique itemId that must be preserved for proper deduplication.
*
* @param part - A message part to check
* @returns true if the part has an OpenAI itemId
*
* @see https://github.com/mastra-ai/mastra/issues/9005 - OpenAI reasoning items filtering
*/
function hasOpenAIReasoningItemId(part) {
return Boolean(getOpenAIReasoningItemId(part));
}
/**
* Checks if a message part has an OpenAI-compatible Responses itemId.
*
* Provider-neutral Responses item IDs are tracked via provider metadata or
* provider options fields such as `openai.itemId` or `azure.itemId`.
*/
function hasResponseProviderItemId(part) {
return Boolean(getResponseProviderItemIdFromPart(part));
}
/**
* Extracts an OpenAI itemId from a message part if present.
*
* This only inspects `providerMetadata.openai.itemId`; use
* `getResponseProviderItemIdFromPart` for provider-aware Azure/OpenAI lookups.
*
* @param part - A message part to extract from
* @returns The itemId string or undefined if not present
*/
function getOpenAIReasoningItemId(part) {
if (!part || typeof part !== "object") return void 0;
const openaiMetadata = part.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);
}
/**
* Finds the tool call args for a given toolCallId by searching through messages.
* This is used to reconstruct the input field when converting tool-result parts to StaticToolResult.
*
* Searches through messages in reverse order (most recent first) for better performance.
* Checks both content.parts (v2 format) and toolInvocations (legacy AIV4 format).
*
* @param messages - Array of MastraDB messages to search through
* @param toolCallId - The ID of the tool call to find args for
* @returns The args object from the matching tool call, or an empty object if not found
*/
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 {};
}
//#endregion
//#region src/agent/message-list/adapters/AIV4Adapter.ts
function getDisplayTransform$2(providerMetadata, phase, fallback, enabled = true) {
if (!enabled) return fallback;
const transform = require_payload_transform.getTransformedToolPayload(providerMetadata, "display", phase);
return require_payload_transform.hasTransformedToolPayload(transform) ? transform.transformed : fallback;
}
function transformV4ToolInvocationForDisplay(invocation, providerMetadata, enabled) {
return {
...invocation,
args: getDisplayTransform$2(providerMetadata, "input-available", invocation.args, enabled),
...invocation.state === "result" ? { result: getDisplayTransform$2(providerMetadata, "output-available", getDisplayTransform$2(providerMetadata, "error", invocation.result, enabled), enabled) } : {}
};
}
/**
* Cast Mastra parts (including data-* extensions) to the V4 UI parts type.
* Data-* parts (e.g. data-tool-call-suspended) are not natively typed in AI SDK V4,
* but must be preserved so features like HITL workflow resumption work after a page refresh.
*/
function preserveExtendedParts(parts) {
return parts;
}
/**
* Filter out empty text parts from message parts array.
* Empty text blocks are not allowed by Anthropic's API and cause request failures.
* This can happen during streaming when text-start/text-end events occur without actual content.
* However, if the only part is an empty text part, it is preserved as a legitimate placeholder
* (e.g. empty assistant messages between tool results and user messages).
*/
function filterEmptyTextParts$1(parts) {
if (!parts.some((part) => !(part.type === "text" && part.text === ""))) return parts;
return parts.filter((part) => {
if (part.type === "text") return part.text !== "";
return true;
});
}
function getSignalType$1(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$1(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$1(message);
if (type === "user") return "user";
if (type === "reactive") return message.type;
return type;
}
function isUserSignalType$1(type) {
return type === "user" || type === "user-message";
}
function toSignalDataPart$1(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$1(message) ?? "signal";
const tagName = getSignalTagName$1(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 } : {}
}
};
}
/**
* AIV4Adapter - Handles conversions between MastraDBMessage and AI SDK V4 formats
*
* This adapter centralizes all AI SDK V4 (UIMessage and CoreMessage) conversion logic.
*/
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`) {
const { mediaType: fileMimeType, data: fileData } = resolveFilePartMediaTypeAndData(part);
let normalizedUrl;
if (typeof fileData === "string") if (categorizeFileData(fileData, fileMimeType).type === "raw") normalizedUrl = createDataUri(fileData, fileMimeType || "application/octet-stream");
else normalizedUrl = fileData;
else normalizedUrl = imageContentToString(fileData, fileMimeType);
experimentalAttachments.push({
contentType: fileMimeType ?? "application/octet-stream",
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,
...isDeniedApproval ? { state: "result" } : {},
args: getDisplayTransform$2(part.providerMetadata, "input-available", part.toolInvocation.args, transformToolPayloads),
...part.toolInvocation.state === "result" ? { result: getDisplayTransform$2(part.providerMetadata, "output-available", getDisplayTransform$2(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 isUserMessageSignal = isUserSignalType$1(m.role === "signal" ? getSignalType$1(m) : void 0);
const v4Parts = preserveExtendedParts(m.role === "signal" && !isUserMessageSignal ? [toSignalDataPart$1(m, m.content.content || contentString)] : parts);
if (m.role === `user`) {
const uiMessage = {
id: m.id,
role: m.role,
content: m.content.content || contentString,
createdAt: m.createdAt,
parts: v4Parts,
experimental_attachments: experimentalAttachments
};
if (m.content.metadata) uiMessage.metadata = m.content.metadata;
return uiMessage;
} else if (m.role === `assistant`) {
const isSingleTextContentArray = Array.isArray(m.content.content) && m.content.content.length === 1 && m.content.content[0].type === `text`;
const uiMessage = {
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) uiMessage.metadata = m.content.metadata;
return uiMessage;
}
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 require_error.MastraError({
id: "INVALID_SYSTEM_MESSAGE_FORMAT",
domain: require_error.ErrorDomain.AGENT,
category: require_error.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 content = {
format: 2,
parts: message.parts ? filterEmptyTextParts$1(message.parts) : []
};
if (message.toolInvocations) content.toolInvocations = message.toolInvocations;
if (message.reasoning) content.reasoning = message.reasoning;
if (message.annotations) content.annotations = message.annotations;
if (message.experimental_attachments) content.experimental_attachments = message.experimental_attachments;
if ("metadata" in message && message.metadata !== null && message.metadata !== void 0) content.metadata = message.metadata;
return {
id: message.id || 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" || categorized.type === "providerFileId") {
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: require_signals.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: require_signals.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 content = {
format: 2,
parts: filterEmptyTextParts$1(parts)
};
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
};
}
};
//#endregion
//#region ../_vendored/ai_v6/dist/index.js
const VERSION$1 = "1.9.1";
const re = /^(\d+)\.(\d+)\.(\d+)(-(.+))?$/;
/**
* Create a function to test an API version to see if it is compatible with the provided ownVersion.
*
* The returned function has the following semantics:
* - Exact match is always compatible
* - Major versions must match exactly
* - 1.x package cannot use global 2.x package
* - 2.x package cannot use global 1.x package
* - The minor version of the API module requesting access to the global API must be less than or equal to the minor version of this API
* - 1.3 package may use 1.4 global because the later global contains all functions 1.3 expects
* - 1.4 package may NOT use 1.3 global because it may try to call functions which don't exist on 1.3
* - If the major version is 0, the minor version is treated as the major and the patch is treated as the minor
* - Patch and build tag differences are not considered at this time
*
* @param ownVersion version which should be checked against
*/
function _makeCompatibilityCheck(ownVersion) {
const acceptedVersions = /* @__PURE__ */ new Set([ownVersion]);
const rejectedVersions = /* @__PURE__ */ new Set();
const myVersionMatch = ownVersion.match(re);
if (!myVersionMatch) return () => false;
const ownVersionParsed = {
major: +myVersionMatch[1],
minor: +myVersionMatch[2],
patch: +myVersionMatch[3],
prerelease: myVersionMatch[4]
};
if (ownVersionParsed.prerelease != null) return function isExactmatch(globalVersion) {
return globalVersion === ownVersion;
};
function _reject(v) {
rejectedVersions.add(v);
return false;
}
function _accept(v) {
acceptedVersions.add(v);
return true;
}
return function isCompatible(globalVersion) {
if (acceptedVersions.has(globalVersion)) return true;
if (rejectedVersions.has(globalVersion)) return false;
const globalVersionMatch = globalVersion.match(re);
if (!globalVersionMatch) return _reject(globalVersion);
const globalVersionParsed = {
major: +globalVersionMatch[1],
minor: +globalVersionMatch[2],
patch: +globalVersionMatch[3],
prerelease: globalVersionMatch[4]
};
if (globalVersionParsed.prerelease != null) return _reject(globalVersion);
if (ownVersionParsed.major !== globalVersionParsed.major) return _reject(globalVersion);
if (ownVersionParsed.major === 0) {
if (ownVersionParsed.minor === globalVersionParsed.minor && ownVersionParsed.patch <= globalVersionParsed.patch) return _accept(globalVersion);
return _reject(globalVersion);
}
if (ownVersionParsed.minor <= globalVersionParsed.minor) return _accept(globalVersion);
return _reject(globalVersion);
};
}
/**
* Test an API version to see if it is compatible with this API.
*
* - Exact match is always compatible
* - Major versions must match exactly
* - 1.x package cannot use global 2.x package
* - 2.x package cannot use global 1.x package
* - The minor version of the API module requesting access to the global API must be less than or equal to the minor version of this API
* - 1.3 package may use 1.4 global because the later global contains all functions 1.3 expects
* - 1.4 package may NOT use 1.3 global because it may try to call functions which don't exist on 1.3
* - If the major version is 0, the minor version is treated as the major and the patch is treated as the minor
* - Patch and build tag differences are not considered at this time
*
* @param version version of the API requesting an instance of the global API
*/
const isCompatible = _makeCompatibilityCheck(VERSION$1);
const major = VERSION$1.split(".")[0];
const GLOBAL_OPENTELEMETRY_API_KEY = Symbol.for(`opentelemetry.js.api.${major}`);
const _global = typeof globalThis === "object" ? globalThis : typeof self === "object" ? self : typeof window === "object" ? window : typeof global === "object" ? global : {};
function registerGlobal(type, instance, diag, allowOverride = false) {
var _a;
const api = _global[GLOBAL_OPENTELEMETRY_API_KEY] = (_a = _global[GLOBAL_OPENTELEMETRY_API_KEY]) !== null && _a !== void 0 ? _a : { version: VERSION$1 };
if (!allowOverride && api[type]) {
const err = /* @__PURE__ */ new Error(`@opentelemetry/api: Attempted duplicate registration of API: ${type}`);
diag.error(err.stack || err.message);
return false;
}
if (api.version !== "1.9.1") {
const err = /* @__PURE__ */ new Error(`@opentelemetry/api: Registration of version v${api.version} for ${type} does not match previously registered API v${VERSION$1}`);
diag.error(err.stack || err.message);
return false;
}
api[type] = instance;
diag.debug(`@opentelemetry/api: Registered a global for ${type} v${VERSION$1}.`);
return true;
}
function getGlobal(type) {
var _a, _b;
const globalVersion = (_a = _global[GLOBAL_OPENTELEMETRY_API_KEY]) === null || _a === void 0 ? void 0 : _a.version;
if (!globalVersion || !isCompatible(globalVersion)) return;
return (_b = _global[GLOBAL_OPENTELEMETRY_API_KEY]) === null || _b === void 0 ? void 0 : _b[type];
}
function unregisterGlobal(type, diag) {
diag.debug(`@opentelemetry/api: Unregistering a global for ${type} v${VERSION$1}.`);
const api = _global[GLOBAL_OPENTELEMETRY_API_KEY];
if (api) delete api[type];
}
/**
* Component Logger which is meant to be used as part of any component which
* will add automatically additional namespace in front of the log message.
* It will then forward all message to global diag logger
* @example
* const cLogger = diag.createComponentLogger({ namespace: '@opentelemetry/instrumentation-http' });
* cLogger.debug('test');
* // @opentelemetry/instrumentation-http test
*/
var DiagComponentLogger = class {
constructor(props) {
this._namespace = props.namespace || "DiagComponentLogger";
}
debug(...args) {
return logProxy("debug", this._namespace, args);
}
error(...args) {
return logProxy("error", this._namespace, args);
}
info(...args) {
return logProxy("info", this._namespace, args);
}
warn(...args) {
return logProxy("warn", this._namespace, args);
}
verbose(...args) {
return logProxy("verbose", this._namespace, args);
}
};
function logProxy(funcName, namespace, args) {
const logger = getGlobal("diag");
if (!logger) return;
return logger[funcName](namespace, ...args);
}
/**
* Defines the available internal logging levels for the diagnostic logger, the numeric values
* of the levels are defined to match the original values from the initial LogLevel to avoid
* compatibility/migration issues for any implementation that assume the numeric ordering.
*/
var DiagLogLevel;
(function(DiagLogLevel) {
/** Diagnostic Logging level setting to disable all logging (except and forced logs) */
DiagLogLevel[DiagLogLevel["NONE"] = 0] = "NONE";
/** Identifies an error scenario */
DiagLogLevel[DiagLogLevel["ERROR"] = 30] = "ERROR";
/** Identifies a warning scenario */
DiagLogLevel[DiagLogLevel["WARN"] = 50] = "WARN";
/** General informational log message */
DiagLogLevel[DiagLogLevel["INFO"] = 60] = "INFO";
/** General debug log message */
DiagLogLevel[DiagLogLevel["DEBUG"] = 70] = "DEBUG";
/**
* Detailed trace level logging should only be used for development, should only be set
* in a development environment.
*/
DiagLogLevel[DiagLogLevel["VERBOSE"] = 80] = "VERBOSE";
/** Used to set the logging level to include all logging */
DiagLogLevel[DiagLogLevel["ALL"] = 9999] = "ALL";
})(DiagLogLevel || (DiagLogLevel = {}));
function createLogLevelDiagLogger(maxLevel, logger) {
if (maxLevel < DiagLogLevel.NONE) maxLevel = DiagLogLevel.NONE;
else if (maxLevel > DiagLogLevel.ALL) maxLevel = DiagLogLevel.ALL;
logger = logger || {};
function _filterFunc(funcName, theLevel) {
const theFunc = logger[funcName];
if (typeof theFunc === "function" && maxLevel >= theLevel) return theFunc.bind(logger);
return function() {};
}
return {
error: _filterFunc("error", DiagLogLevel.ERROR),
warn: _filterFunc("warn", DiagLogLevel.WARN),
info: _filterFunc("info", DiagLogLevel.INFO),
debug: _filterFunc("debug", DiagLogLevel.DEBUG),
verbose: _filterFunc("verbose", DiagLogLevel.VERBOSE)
};
}
const API_NAME$2 = "diag";
/**
* Singleton object which represents the entry point to the OpenTelemetry internal
* diagnostic API
*
* @since 1.0.0
*/
var DiagAPI = class DiagAPI {
/** Get the singleton instance of the DiagAPI API */
static instance() {
if (!this._instance) this._instance = new DiagAPI();
return this._instance;
}
/**
* Private internal constructor
* @private
*/
constructor() {
function _logProxy(funcName) {
return function(...args) {
const logger = getGlobal("diag");
if (!logger) return;
return logger[funcName](...args);
};
}
const self = this;
const setLogger = (logger, optionsOrLogLevel = { logLevel: DiagLogLevel.INFO }) => {
var _a, _b, _c;
if (logger === self) {
const err = /* @__PURE__ */ new Error("Cannot use diag as the logger for itself. Please use a DiagLogger implementation like ConsoleDiagLogger or a custom implementation");
self.error((_a = err.stack) !== null && _a !== void 0 ? _a : err.message);
return false;
}
if (typeof optionsOrLogLevel === "number") optionsOrLogLevel = { logLevel: optionsOrLogLevel };
const oldLogger = getGlobal("diag");
const newLogger = createLogLevelDiagLogger((_b = optionsOrLogLevel.logLevel) !== null && _b !== void 0 ? _b : DiagLogLevel.INFO, logger);
if (oldLogger && !optionsOrLogLevel.suppressOverrideMessage) {
const stack = (_c = (/* @__PURE__ */ new Error()).stack) !== null && _c !== void 0 ? _c : "<failed to generate stacktrace>";
oldLogger.warn(`Current logger will be overwritten from ${stack}`);
newLogger.warn(`Current logger will overwrite one already registered from ${stack}`);
}
return registerGlobal("diag", newLogger, self, true);
};
self.setLogger = setLogger;
self.disable = () => {
unregisterGlobal(API_NAME$2, self);
};
self.createComponentLogger = (options) => {
return new DiagComponentLogger(options);
};
self.verbose = _logProxy("verbose");
self.debug = _logProxy("debug");
self.info = _logProxy("info");
self.warn = _logProxy("warn");
self.error = _logProxy("error");
}
};
/**
* Get a key to uniquely identify a context value
*
* @since 1.0.0
*/
function createContextKey(description) {
return Symbol.for(description);
}
/**
* The root context is used as the default parent context when there is no active context
*
* @since 1.0.0
*/
const ROOT_CONTEXT = new class BaseContext {
/**
* Construct a new context which inherits val