UNPKG

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