@mastra/core
Version:
Mastra is a framework for building AI-powered applications and agents with a modern TypeScript stack.
644 lines (635 loc) • 22.9 kB
JavaScript
'use strict';
var chunkXB4FLS7A_cjs = require('./chunk-XB4FLS7A.cjs');
var chunkRLPRSIR2_cjs = require('./chunk-RLPRSIR2.cjs');
var chunkCAVARKYS_cjs = require('./chunk-CAVARKYS.cjs');
// src/tools/validation.ts
function safeValidate(schema, data) {
try {
const result = schema["~standard"].validate(data);
if (result instanceof Promise) {
throw new Error("Your schema is async, which is not supported. Please use a sync schema.");
}
return result;
} catch (err) {
if (err instanceof TypeError && err.message.includes("Cannot read properties of undefined")) {
throw new Error(
`Schema validation failed due to an invalid schema definition. This often happens when a union schema (z.union or z.or) has undefined options. Please check that all schema options are properly defined. Original error: ${err.message}`
);
}
throw err;
}
}
function isValidationError(value) {
return value !== null && typeof value === "object" && "error" in value && value.error === true && "validationErrors" in value;
}
function getPathKey(segment) {
if (typeof segment === "object" && segment !== null && "key" in segment) {
return String(segment.key);
}
return String(segment);
}
function createEmptyErrors() {
return { errors: [], fields: {} };
}
function buildFormattedErrors(issues) {
const result = createEmptyErrors();
for (const issue of issues) {
if (!issue.path || issue.path.length === 0) {
result.errors.push(issue.message);
} else {
let current = result;
for (let i = 0; i < issue.path.length; i++) {
const key = getPathKey(issue.path[i]);
if (i === issue.path.length - 1) {
if (!current.fields[key]) {
current.fields[key] = createEmptyErrors();
}
current.fields[key].errors.push(issue.message);
} else {
if (!current.fields[key]) {
current.fields[key] = createEmptyErrors();
}
current = current.fields[key];
}
}
}
}
return result;
}
function truncateForLogging(data, maxLength = 200) {
try {
const stringified = JSON.stringify(data, null, 2);
if (stringified.length <= maxLength) {
return stringified;
}
return stringified.slice(0, maxLength) + "... (truncated)";
} catch {
return "[Unable to serialize data]";
}
}
function validateToolSuspendData(schema, suspendData, toolId) {
if (!schema || !("~standard" in schema)) {
return { data: suspendData };
}
const validation = safeValidate(schema, suspendData);
if ("value" in validation) {
return { data: validation.value };
}
const errorMessages = validation.issues.map((e) => `- ${e.path?.join(".") || "root"}: ${e.message}`).join("\n");
const error = {
error: true,
message: `Tool suspension data validation failed${toolId ? ` for ${toolId}` : ""}. Please fix the following errors and try again:
${errorMessages}
Provided arguments: ${truncateForLogging(suspendData)}`,
validationErrors: buildFormattedErrors(validation.issues)
};
return { error };
}
function normalizeNullishInput(schema, input) {
if (typeof input !== "undefined" && input !== null) {
return input;
}
const jsonSchema = chunkXB4FLS7A_cjs.standardSchemaToJSONSchema(schema, { io: "input" });
if (jsonSchema.type === "array") {
return [];
}
if (jsonSchema.type === "object") {
return {};
}
return input;
}
function isPlainObject(value) {
if (value === null || typeof value !== "object") {
return false;
}
const proto = Object.getPrototypeOf(value);
return proto === Object.prototype || proto === null;
}
function convertUndefinedToNull(input) {
if (input === void 0) {
return null;
}
if (input === null || typeof input !== "object") {
return input;
}
if (Array.isArray(input)) {
return input.map(convertUndefinedToNull);
}
if (!isPlainObject(input)) {
return input;
}
const result = {};
for (const [key, value] of Object.entries(input)) {
result[key] = convertUndefinedToNull(value);
}
return result;
}
function stripNullishValues(input) {
if (input === null || input === void 0) {
return void 0;
}
if (typeof input !== "object") {
return input;
}
if (Array.isArray(input)) {
return input.map((item) => item === null ? null : stripNullishValues(item));
}
if (!isPlainObject(input)) {
return input;
}
const result = {};
for (const [key, value] of Object.entries(input)) {
if (value === null || value === void 0) {
continue;
}
result[key] = stripNullishValues(value);
}
return result;
}
function stripNullishValuesAtPaths(input, paths, currentPath = "") {
if (input === null || input === void 0) {
return paths.has(currentPath) ? void 0 : input;
}
if (typeof input !== "object") {
return input;
}
if (Array.isArray(input)) {
return input.map(
(item, i) => stripNullishValuesAtPaths(item, paths, currentPath ? `${currentPath}.${i}` : String(i))
);
}
if (!isPlainObject(input)) {
return input;
}
const result = {};
for (const [key, value] of Object.entries(input)) {
const fieldPath = currentPath ? `${currentPath}.${key}` : key;
if ((value === null || value === void 0) && paths.has(fieldPath)) {
continue;
}
result[key] = stripNullishValuesAtPaths(value, paths, fieldPath);
}
return result;
}
var PATH_NOT_FOUND = /* @__PURE__ */ Symbol("PATH_NOT_FOUND");
function getValueAtPath(obj, pathSegments) {
let current = obj;
for (const segment of pathSegments) {
if (current === null || current === void 0 || typeof current !== "object") {
return PATH_NOT_FOUND;
}
const key = typeof segment === "object" && segment !== null && "key" in segment ? String(segment.key) : String(segment);
current = current[key];
}
return current;
}
function coerceStringifiedJsonValues(schema, input) {
if (!isPlainObject(input)) {
return input;
}
const unwrapped = chunkRLPRSIR2_cjs.unwrapZodType(schema);
if (!chunkRLPRSIR2_cjs.isZodObject(unwrapped)) {
return input;
}
const shape = unwrapped.shape;
if (!shape || typeof shape !== "object") {
return input;
}
let changed = false;
const result = { ...input };
for (const [key, value] of Object.entries(input)) {
if (typeof value !== "string") {
continue;
}
const fieldSchema = shape[key];
if (!fieldSchema) {
continue;
}
const baseFieldSchema = chunkRLPRSIR2_cjs.unwrapZodType(fieldSchema);
if (chunkRLPRSIR2_cjs.getZodTypeName(baseFieldSchema) === "ZodString") {
continue;
}
const trimmed = value.trim();
if (chunkRLPRSIR2_cjs.isZodArray(baseFieldSchema) && trimmed.startsWith("[") || chunkRLPRSIR2_cjs.isZodObject(baseFieldSchema) && trimmed.startsWith("{")) {
try {
const parsed = JSON.parse(value);
if (chunkRLPRSIR2_cjs.isZodArray(baseFieldSchema) && Array.isArray(parsed) || chunkRLPRSIR2_cjs.isZodObject(baseFieldSchema) && isPlainObject(parsed)) {
result[key] = parsed;
changed = true;
}
} catch {
}
}
}
return changed ? result : input;
}
function validateToolInput(schema, input, toolId) {
if (!schema || !("~standard" in schema)) {
return { data: input };
}
let normalizedInput = normalizeNullishInput(schema, input);
normalizedInput = convertUndefinedToNull(normalizedInput);
const validation = safeValidate(schema, normalizedInput);
if ("value" in validation) {
return { data: validation.value };
}
const coercedInput = coerceStringifiedJsonValues(schema, normalizedInput);
if (coercedInput !== normalizedInput) {
const coercedValidation = safeValidate(schema, coercedInput);
if ("value" in coercedValidation) {
return { data: coercedValidation.value };
}
}
const failingNullPaths = new Set(
validation.issues.filter((issue) => {
if (!issue.path || issue.path.length === 0) return false;
const value = getValueAtPath(normalizedInput, issue.path);
return value === null || value === void 0;
}).map((issue) => issue.path?.map((p) => typeof p === "object" && "key" in p ? String(p.key) : String(p)).join(".")).filter((p) => !!p)
);
const strippedInput = failingNullPaths.size > 0 ? stripNullishValuesAtPaths(input, failingNullPaths) : stripNullishValues(input);
const normalizedStripped = normalizeNullishInput(schema, strippedInput);
const retryValidation = safeValidate(schema, normalizedStripped);
if ("value" in retryValidation) {
return { data: retryValidation.value };
}
const promptJsonSchema = chunkXB4FLS7A_cjs.standardSchemaToJSONSchema(schema, { io: "input" });
const schemaExpectsPrompt = promptJsonSchema.type === "object" && promptJsonSchema.properties != null && "prompt" in promptJsonSchema.properties;
if (schemaExpectsPrompt && normalizedInput != null && typeof normalizedInput === "object" && !Array.isArray(normalizedInput)) {
const obj = normalizedInput;
if (obj.prompt == null) {
const alias = [obj.query, obj.message, obj.input].find((v) => typeof v === "string");
if (alias !== void 0) {
const coercedPromptInput = { ...obj, prompt: alias };
const coercedPromptValidation = safeValidate(schema, coercedPromptInput);
if ("value" in coercedPromptValidation) {
return { data: coercedPromptValidation.value };
}
}
}
}
const errorMessages = validation.issues.map((e) => `- ${e.path?.join(".") || "root"}: ${e.message}`).join("\n");
const error = {
error: true,
message: `Tool input validation failed${toolId ? ` for ${toolId}` : ""}. Please fix the following errors and try again:
${errorMessages}
Provided arguments: ${truncateForLogging(input)}`,
validationErrors: buildFormattedErrors(validation.issues)
};
return { error };
}
function validateToolOutput(schema, output, toolId, suspendCalled) {
if (!schema || !("~standard" in schema) || suspendCalled) {
return { data: output };
}
const validation = safeValidate(schema, output);
if ("value" in validation) {
return { data: validation.value };
}
const errorMessages = validation.issues.map((e) => `- ${e.path?.join(".") || "root"}: ${e.message}`).join("\n");
const error = {
error: true,
message: `Tool output validation failed${toolId ? ` for ${toolId}` : ""}. The tool returned invalid output:
${errorMessages}
Returned output: ${truncateForLogging(output)}`,
validationErrors: buildFormattedErrors(validation.issues)
};
return { error };
}
var SENSITIVE_KEYS = ["password", "secret", "token", "apiKey", "api_key", "auth", "credential"];
function redactSensitiveKeys(obj) {
if (obj === null || typeof obj !== "object") {
return obj;
}
if (Array.isArray(obj)) {
return obj.map(redactSensitiveKeys);
}
const result = {};
for (const [key, value] of Object.entries(obj)) {
if (SENSITIVE_KEYS.some((sensitive) => key.toLowerCase().includes(sensitive.toLowerCase()))) {
result[key] = "[REDACTED]";
} else if (typeof value === "object" && value !== null) {
result[key] = redactSensitiveKeys(value);
} else {
result[key] = value;
}
}
return result;
}
function validateRequestContext(schema, requestContext, identifier) {
if (!schema) {
return { data: requestContext?.all ?? {} };
}
const contextValues = requestContext?.all ?? {};
const standardSchema = chunkXB4FLS7A_cjs.toStandardSchema(schema);
const validation = standardSchema["~standard"].validate(contextValues);
if (validation instanceof Promise) {
throw new Error("Your schema is async, which is not supported. Please use a sync schema.");
}
if ("value" in validation) {
return { data: validation.value };
}
const errorMessages = validation.issues.map((e) => `- ${e.path?.join(".") || "root"}: ${e.message}`).join("\n");
const redactedContext = redactSensitiveKeys(contextValues);
const error = {
error: true,
message: `Request context validation failed${identifier ? ` for ${identifier}` : ""}. Please fix the following errors and try again:
${errorMessages}
Provided request context: ${truncateForLogging(redactedContext)}`,
validationErrors: buildFormattedErrors(validation.issues)
};
return { data: contextValues, error };
}
// src/tools/tool.ts
var MASTRA_TOOL_MARKER = /* @__PURE__ */ Symbol.for("mastra.core.tool.Tool");
var Tool = class {
/** Unique identifier for the tool */
id;
/** Description of what the tool does */
description;
/** Schema for validating input parameters */
inputSchema;
/** Schema for validating output structure */
outputSchema;
/** Schema for suspend operation data */
suspendSchema;
/** Schema for resume operation data */
resumeSchema;
/**
* Schema for validating request context values.
* When provided, the request context will be validated against this schema before tool execution.
*/
requestContextSchema;
/**
* Tool execution function
* @param inputData - The raw, validated input data
* @param context - Optional execution context with metadata
* @returns Promise resolving to tool output or a ValidationError if input validation fails
*/
execute;
/** Parent Mastra instance for accessing shared resources */
mastra;
/**
* Whether the tool requires explicit user approval before execution.
* Accepts a boolean for static behavior, or a function evaluated per-call
* for conditional approval.
* @example
* ```typescript
* // Static
* requireApproval: true
*
* // Conditional — only require approval for non-dry-run calls
* requireApproval: async ({ isDryRun }) => !isDryRun
* ```
*/
requireApproval;
/**
* Enables strict tool input generation for providers that support it.
*/
strict;
/**
* Provider-specific options passed to the model when this tool is used.
* Keys are provider names (e.g., 'anthropic', 'openai'), values are provider-specific configs.
* @example
* ```typescript
* providerOptions: {
* anthropic: {
* cacheControl: { type: 'ephemeral' }
* }
* }
* ```
*/
providerOptions;
/**
* Optional function to transform the tool's raw output before sending it to the model.
* The raw result is still available for application logic; only the model sees the transformed version.
*/
toModelOutput;
/**
* Optional target-aware transform for display and transcript payloads.
*/
transform;
/**
* Optional MCP-specific properties including annotations and metadata.
* Only relevant when the tool is being used in an MCP context.
* @example
* ```typescript
* mcp: {
* annotations: {
* title: 'Weather Lookup',
* readOnlyHint: true,
* destructiveHint: false
* },
* _meta: {
* version: '1.0.0',
* author: 'team@example.com'
* }
* }
* ```
*/
mcp;
onInputStart;
onInputDelta;
onInputAvailable;
onOutput;
/**
* Examples of valid tool inputs passed through to the AI SDK.
*/
inputExamples;
/**
* Metadata identifying this tool as originating from an MCP server.
* Set automatically by the MCP client when creating tools.
*/
mcpMetadata;
/**
* Creates a new Tool instance with input validation wrapper.
*
* @param opts - Tool configuration and execute function
* @example
* ```typescript
* const tool = new Tool({
* id: 'my-tool',
* description: 'Does something useful',
* inputSchema: z.object({ name: z.string() }),
* execute: async (inputData) => ({ greeting: `Hello ${inputData.name}` })
* });
* ```
*/
constructor(opts) {
this[MASTRA_TOOL_MARKER] = true;
this.id = opts.id;
this.description = opts.description;
this.inputSchema = opts.inputSchema ? chunkXB4FLS7A_cjs.toStandardSchema(opts.inputSchema) : void 0;
this.outputSchema = opts.outputSchema ? chunkXB4FLS7A_cjs.toStandardSchema(opts.outputSchema) : void 0;
this.suspendSchema = opts.suspendSchema ? chunkXB4FLS7A_cjs.toStandardSchema(opts.suspendSchema) : void 0;
this.resumeSchema = opts.resumeSchema ? chunkXB4FLS7A_cjs.toStandardSchema(opts.resumeSchema) : void 0;
this.requestContextSchema = opts.requestContextSchema;
this.mastra = opts.mastra;
this.requireApproval = opts.requireApproval || false;
this.strict = opts.strict;
this.providerOptions = opts.providerOptions;
this.toModelOutput = opts.toModelOutput;
this.transform = opts.transform;
this.inputExamples = opts.inputExamples;
this.mcp = opts.mcp;
this.mcpMetadata = opts.mcpMetadata;
this.onInputStart = opts.onInputStart;
this.onInputDelta = opts.onInputDelta;
this.onInputAvailable = opts.onInputAvailable;
this.onOutput = opts.onOutput;
if (opts.execute) {
const originalExecute = opts.execute;
this.execute = async (inputData, context) => {
const { data, error } = validateToolInput(this.inputSchema, inputData, this.id);
if (error) {
return error;
}
const { error: requestContextError } = validateRequestContext(
this.requestContextSchema,
context?.requestContext,
this.id
);
if (requestContextError) {
return requestContextError;
}
let suspendData = null;
const baseContext = context ? {
...context,
...context.suspend ? {
suspend: (args, suspendOptions) => {
suspendData = args;
return context.suspend?.(args, suspendOptions);
}
} : {}
} : {};
let organizedContext = baseContext;
if (!context) {
organizedContext = {
requestContext: new chunkCAVARKYS_cjs.RequestContext(),
mastra: void 0
};
} else {
const isAgentExecution = baseContext.toolCallId && baseContext.messages;
const isWorkflowExecution = !isAgentExecution && (baseContext.workflow || baseContext.workflowId);
if (isAgentExecution && !baseContext.agent) {
const {
agentId,
toolCallId,
messages,
suspend,
resumeData: resumeData2,
threadId,
resourceId,
writableStream,
...rest
} = baseContext;
organizedContext = {
...rest,
agent: {
agentId: agentId || "",
toolCallId,
messages,
suspend,
resumeData: resumeData2,
threadId,
resourceId,
writableStream
},
// Ensure requestContext is always present
requestContext: rest.requestContext || new chunkCAVARKYS_cjs.RequestContext()
};
} else if (isWorkflowExecution && !baseContext.workflow) {
const { workflowId, runId, state, setState, suspend, resumeData: resumeData2, ...rest } = baseContext;
organizedContext = {
...rest,
workflow: {
workflowId,
runId,
state,
setState,
suspend,
resumeData: resumeData2
},
// Ensure requestContext is always present
requestContext: rest.requestContext || new chunkCAVARKYS_cjs.RequestContext()
};
} else {
organizedContext = {
...baseContext,
agent: baseContext.agent ? {
...baseContext.agent,
agentId: baseContext.agent.agentId ?? "",
suspend: (args, suspendOptions) => {
suspendData = args;
return baseContext.agent?.suspend?.(args, suspendOptions);
}
} : baseContext.agent,
workflow: baseContext.workflow ? {
...baseContext.workflow,
suspend: (args, suspendOptions) => {
suspendData = args;
return baseContext.workflow?.suspend?.(args, suspendOptions);
}
} : baseContext.workflow,
requestContext: baseContext.requestContext || new chunkCAVARKYS_cjs.RequestContext()
};
}
}
const resumeData = organizedContext.agent?.resumeData ?? organizedContext.workflow?.resumeData ?? organizedContext?.resumeData;
if (resumeData) {
const resumeValidation = validateToolInput(this.resumeSchema, resumeData, this.id);
if (resumeValidation.error) {
return resumeValidation.error;
}
}
const output = await originalExecute(data, organizedContext);
if (suspendData) {
const suspendValidation = validateToolSuspendData(this.suspendSchema, suspendData, this.id);
if (suspendValidation.error) {
return suspendValidation.error;
}
}
const skiptOutputValidation = !!(typeof output === "undefined" && suspendData);
const outputValidation = validateToolOutput(this.outputSchema, output, this.id, skiptOutputValidation);
if (outputValidation.error) {
return outputValidation.error;
}
return outputValidation.data;
};
}
}
};
function createTool(opts) {
return new Tool(opts);
}
// src/tools/toolchecks.ts
function isMastraTool(tool) {
return tool instanceof Tool || typeof tool === "object" && tool !== null && MASTRA_TOOL_MARKER in tool;
}
function isVercelTool(tool) {
return !!(tool && !isMastraTool(tool) && ("parameters" in tool || "execute" in tool && typeof tool.execute === "function" && "inputSchema" in tool));
}
function isProviderDefinedTool(tool) {
if (typeof tool !== "object" || tool === null) return false;
const t = tool;
const isProviderType = t.type === "provider-defined" || t.type === "provider";
return isProviderType && typeof t.id === "string";
}
var isProviderTool = isProviderDefinedTool;
function getProviderToolName(providerId) {
return providerId.split(".").slice(1).join(".");
}
exports.MASTRA_TOOL_MARKER = MASTRA_TOOL_MARKER;
exports.Tool = Tool;
exports.createTool = createTool;
exports.getProviderToolName = getProviderToolName;
exports.isMastraTool = isMastraTool;
exports.isProviderDefinedTool = isProviderDefinedTool;
exports.isProviderTool = isProviderTool;
exports.isValidationError = isValidationError;
exports.isVercelTool = isVercelTool;
exports.validateToolInput = validateToolInput;
exports.validateToolOutput = validateToolOutput;
exports.validateToolSuspendData = validateToolSuspendData;
//# sourceMappingURL=chunk-DEZHPHA5.cjs.map
//# sourceMappingURL=chunk-DEZHPHA5.cjs.map