UNPKG

@mastra/core

Version:
1,088 lines (1,087 loc) • 44.8 kB
import { t as MastraBase } from "./base-BeUQ6mLP.js"; import { i as MastraError, n as ErrorDomain, t as ErrorCategory } from "./error-MjDSls8S.js"; import { i as createObservabilityContext, r as wrapMastra } from "./observability-Cz-X7NF_.js"; import { a as getOrCreateSpan, f as EntityType, t as executeWithContext } from "./utils-DxsDNzD2.js"; import "./tracing-Bm0k4FBA.js"; import { a as RequestContext } from "./request-context-p_Tq-4EM.js"; import { isStandardSchemaWithJSON, standardSchemaToJSONSchema, toStandardSchema } from "./schema/index.js"; import { a as isZodObject, o as safeExtendZodObject } from "./zod-utils-DTkc-hhd.js"; import { a as validateToolInput, n as Tool, o as validateToolOutput, s as validateToolSuspendData } from "./tool-qGw4ZhYO.js"; import { i as MastraFGAPermissions } from "./ee-DXvSoTl7-BqTsKmQd.js"; import { n as backgroundOverrideJsonSchema, r as backgroundOverrideZodSchema } from "./background-tasks-6lJjk3_t.js"; import { i as isProviderDefinedTool, o as isVercelTool, t as getNeedsApprovalFn } from "./toolchecks-BWgiThPN.js"; import { n as ToolStream, t as noopObserve } from "./types-C59tsW89.js"; import { safeStringify } from "./utils/safe-stringify.js"; import { createHash } from "crypto"; import { z } from "zod/v4"; import { jsonSchemaToZod } from "@mastra/schema-compat/json-to-zod"; import { AnthropicSchemaCompatLayer, DeepSeekSchemaCompatLayer, GoogleSchemaCompatLayer, MetaSchemaCompatLayer, OpenAIReasoningSchemaCompatLayer, OpenAISchemaCompatLayer, applyCompatLayer, convertZodSchemaToAISDKSchema, jsonSchema } from "@mastra/schema-compat"; //#region src/tools/tool-builder/builder.ts /** * Merge two RequestContexts so non-serializable values survive the evented * workflow engine's toJSON/reconstruct cycle. * * The evented engine serialises the RequestContext via `toJSON()` when * publishing workflow events. Values that fail `JSON.stringify` (functions, * objects with circular references — e.g. the `controller` context) are silently * dropped. The reconstructed RC handed to steps is therefore *degraded*. * * Tools, however, also hold a reference to the *original* RC captured during * tool conversion (the "closure" RC). By merging both — exec first, then * closure on top — keys that survived serialisation are preserved while * non-serializable keys from the closure (like `controller`) are restored. */ /** * Detect RequestContext-like objects structurally. We cannot use `instanceof` * here because duplicate copies of @mastra/core may be loaded in the same * process (bundlers, monorepos) and the prototype identity is not guaranteed. */ function isRequestContextLike(value) { if (!value || typeof value !== "object") return false; const rc = value; return typeof rc.get === "function" && typeof rc.set === "function" && typeof rc.entries === "function" && typeof rc.size === "function"; } function mergeRequestContexts(closureRC, execRC) { if (closureRC && closureRC === execRC) return closureRC; if (!closureRC && !execRC) return new RequestContext(); if (!closureRC) return isRequestContextLike(execRC) ? execRC : new RequestContext(); if (!execRC || !isRequestContextLike(execRC) || execRC.size() === 0) return closureRC; const merged = new RequestContext(); for (const [key, value] of execRC.entries()) merged.set(key, value); for (const [key, value] of closureRC.entries()) merged.set(key, value); return merged; } /** * Detect Zod v4 schemas. Zod v3 stores the type name as `_def.typeName` * (e.g. "ZodObject"); Zod v4 stores it as `_def.type` (e.g. "object"). We * cannot use `instanceof` here because both Zod versions may be loaded in the * same process and the prototype identity is not guaranteed. */ function isZodV4Schema(schema) { const def = schema?._def; return !!def && typeof def.type === "string" && !def.typeName; } /** * Build a Standard Schema that: * - exposes the spliced JSON Schema (with `_background`/`suspendedToolRunId`/ * `resumeData` properties added) so provider compat layers see the override * fields when serializing the tool to an LLM, and * - delegates runtime `validate` to the *original* schema so Zod v3 * `.transform()` / `.default()` / `.refine()` and other Standard Schema * parsing behavior still run before `execute()` sees the args. * * Injected override keys (`_background`, `suspendedToolRunId`, `resumeData`) * are stripped from the input before delegating, then merged back into the * validated value so the inner `execute()` still receives them — matching the * Zod v4 `.extend()` path's behavior. * * If the original schema has no `~standard.validate` (e.g. a raw JSON Schema * with no Standard Schema wrapper), fall back to validating against the * spliced JSON Schema directly. */ function buildJsonOverrideSchema(originalSchema, splicedJsonSchema, injectedKeys) { const fallback = toStandardSchema(splicedJsonSchema); const original = originalSchema; const originalValidate = original?.["~standard"]?.validate?.bind(original["~standard"]); const splicedProperties = splicedJsonSchema && typeof splicedJsonSchema === "object" && "properties" in splicedJsonSchema ? splicedJsonSchema.properties ?? {} : {}; const injectedProperties = {}; for (const key of injectedKeys) if (splicedProperties[key] !== void 0) injectedProperties[key] = splicedProperties[key]; const injectedValidator = toStandardSchema({ type: "object", properties: injectedProperties, additionalProperties: false }); const stripInjected = (input) => { if (!input || typeof input !== "object" || Array.isArray(input)) return { stripped: input, injected: {} }; const injected = {}; const stripped = {}; for (const [k, v] of Object.entries(input)) if (injectedKeys.includes(k)) injected[k] = v; else stripped[k] = v; return { stripped, injected }; }; const validate = (input) => { const { stripped, injected } = stripInjected(input); const baseResult = originalValidate ? originalValidate(stripped) : fallback["~standard"].validate(stripped); const injectedResult = injectedValidator["~standard"].validate(injected); const combine = (base, inj) => { const baseIssues = "issues" in base ? base.issues ?? [] : []; const injIssues = "issues" in inj ? inj.issues ?? [] : []; if (baseIssues.length || injIssues.length) return { issues: [...baseIssues, ...injIssues] }; const baseValue = base.value; const injValue = inj.value; if (baseValue && typeof baseValue === "object" && !Array.isArray(baseValue)) { const injMerged = injValue && typeof injValue === "object" && !Array.isArray(injValue) ? injValue : injected; return { value: { ...baseValue, ...injMerged } }; } return base; }; const baseIsPromise = baseResult && typeof baseResult.then === "function"; const injIsPromise = injectedResult && typeof injectedResult.then === "function"; if (baseIsPromise || injIsPromise) return Promise.all([baseResult, injectedResult]).then(([b, i]) => combine(b, i)); return combine(baseResult, injectedResult); }; return { "~standard": { version: 1, vendor: "mastra-json-override", validate, jsonSchema: fallback["~standard"].jsonSchema } }; } var CoreToolBuilder = class extends MastraBase { originalTool; options; logType; constructor(input) { super({ name: "CoreToolBuilder" }); this.originalTool = input.originalTool; this.options = input.options; this.logType = input.logType; const isBackgroundEligible = !!input.backgroundTaskEnabled; const isResumableTool = input.autoResumeSuspendedTools || this.originalTool.id?.startsWith("agent-") || this.originalTool.id?.startsWith("workflow-"); if (!isVercelTool(this.originalTool) && !isProviderDefinedTool(this.originalTool)) { if (isBackgroundEligible || isResumableTool) { let schema = this.originalTool.inputSchema; if (typeof schema === "function") schema = schema(); if (!schema) schema = z.object({}); if (isZodObject(schema) && isZodV4Schema(schema)) { let nextSchema = schema; if (isBackgroundEligible) nextSchema = safeExtendZodObject(nextSchema, { _background: backgroundOverrideZodSchema }); if (isResumableTool) nextSchema = safeExtendZodObject(nextSchema, { suspendedToolRunId: z.string().describe("The runId of the suspended tool").nullable().optional(), resumeData: z.any().describe("The resumeData object created from the resumeSchema of suspended tool").optional() }); this.originalTool.inputSchema = nextSchema; } else { const jsonSchema = standardSchemaToJSONSchema(isStandardSchemaWithJSON(schema) ? schema : toStandardSchema(schema), { io: "input" }); if (jsonSchema && typeof jsonSchema === "object" && jsonSchema.type === "object") { const properties = { ...jsonSchema.properties ?? {} }; const injectedKeys = []; if (isBackgroundEligible) { properties._background = backgroundOverrideJsonSchema; injectedKeys.push("_background"); } if (isResumableTool) { properties.suspendedToolRunId = { type: ["string", "null"], description: "The runId of the suspended tool" }; properties.resumeData = { description: "The resumeData object created from the resumeSchema of suspended tool" }; injectedKeys.push("suspendedToolRunId", "resumeData"); } this.originalTool.inputSchema = buildJsonOverrideSchema(schema, { ...jsonSchema, properties }, injectedKeys); } } } } } getParameters = () => { if (isVercelTool(this.originalTool)) { let schema = this.originalTool.parameters ?? ("inputSchema" in this.originalTool ? this.originalTool.inputSchema : void 0) ?? z.object({}); if (typeof schema === "function") schema = schema(); return schema; } let schema = this.originalTool.inputSchema; if (isStandardSchemaWithJSON(schema)) return schema; if (typeof schema === "function") schema = schema(); return schema; }; getOutputSchema = () => { if ("outputSchema" in this.originalTool) { let schema = this.originalTool.outputSchema; if (isStandardSchemaWithJSON(schema)) return schema; if (typeof schema === "function") schema = schema(); return schema; } return null; }; getResumeSchema = () => { if ("resumeSchema" in this.originalTool) { let schema = this.originalTool.resumeSchema; if (typeof schema === "function") schema = schema(); return schema; } return null; }; getSuspendSchema = () => { if ("suspendSchema" in this.originalTool) { let schema = this.originalTool.suspendSchema; if (typeof schema === "function") schema = schema(); return schema; } return null; }; buildProviderTool(tool) { if ("type" in tool && (tool.type === "provider-defined" || tool.type === "provider") && "id" in tool && typeof tool.id === "string" && tool.id.includes(".")) { let parameters = "parameters" in tool ? tool.parameters : "inputSchema" in tool ? tool.inputSchema : void 0; if (typeof parameters === "function") parameters = parameters(); let outputSchema = "outputSchema" in tool ? tool.outputSchema : void 0; if (typeof outputSchema === "function") outputSchema = outputSchema(); let processedParameters; if (parameters !== void 0 && parameters !== null) if (typeof parameters === "object" && "jsonSchema" in parameters) processedParameters = parameters; else if (isStandardSchemaWithJSON(parameters)) processedParameters = { jsonSchema: standardSchemaToJSONSchema(parameters, { io: "input" }) }; else processedParameters = convertZodSchemaToAISDKSchema(parameters); else processedParameters = { jsonSchema: { type: "object", properties: {}, additionalProperties: false } }; let processedOutputSchema; if (outputSchema !== void 0 && outputSchema !== null) if (typeof outputSchema === "object" && "jsonSchema" in outputSchema) processedOutputSchema = outputSchema; else if (isStandardSchemaWithJSON(outputSchema)) processedOutputSchema = { jsonSchema: standardSchemaToJSONSchema(outputSchema) }; else processedOutputSchema = convertZodSchemaToAISDKSchema(outputSchema); return { ...processedOutputSchema ? { outputSchema: processedOutputSchema } : {}, type: "provider-defined", id: tool.id, ..."name" in tool && typeof tool.name === "string" ? { name: tool.name } : {}, args: "args" in this.originalTool ? this.originalTool.args : {}, description: tool.description, parameters: processedParameters, execute: this.originalTool.execute ? this.createExecute(this.originalTool, { ...this.options, description: this.originalTool.description }, this.logType) : void 0, toModelOutput: "toModelOutput" in this.originalTool ? this.originalTool.toModelOutput : void 0, transform: "transform" in this.originalTool ? this.originalTool.transform : void 0, inputExamples: "inputExamples" in this.originalTool ? this.originalTool.inputExamples : void 0 }; } } createLogMessageOptions({ agentName, toolName, type }) { const toolType = type === "toolset" ? "toolset" : "tool"; return { start: `Executing ${toolType}`, error: `Failed ${toolType} execution`, logData: { agent: agentName, tool: toolName } }; } createExecute(tool, options, logType) { const { logger, mastra: _mastra, memory: _memory, requestContext, model, tracingContext: _tracingContext, tracingPolicy: _tracingPolicy, ...rest } = options; const logModelObject = { modelId: model?.modelId, provider: model?.provider, specificationVersion: model?.specificationVersion }; const { start, logData } = this.createLogMessageOptions({ agentName: options.agentName, toolName: options.name, type: logType }); const mcpMeta = !isVercelTool(tool) && "mcpMetadata" in tool ? tool.mcpMetadata : void 0; const execFunction = async (args, execOptions, toolSpan) => { try { let result; let suspendData = null; if (isVercelTool(tool)) result = await executeWithContext({ span: toolSpan, fn: async () => tool?.execute?.(args, execOptions) }); else { /** * MASTRA INSTANCE TYPES IN TOOL EXECUTION: * * Full Mastra & MastraPrimitives (has getAgent, getWorkflow, etc.): * - Auto-generated workflow tools from agent.listWorkflows() * - These get this.#mastra directly and can be wrapped * * MastraPrimitives only (limited interface): * - Memory tools (from memory.listTools()) * - Assigned tools (agent.tools) * - Toolset tools (from toolsets) * - Client tools (passed as tools in generate/stream options) * - These get mastraProxy and have limited functionality * * TODO: Consider providing full Mastra instance to more tool types for enhanced functionality */ const wrappedMastra = options.mastra ? wrapMastra(options.mastra, { currentSpan: toolSpan }) : options.mastra; const resumeSchema = this.getResumeSchema(); const baseContext = { threadId: options.threadId, resourceId: options.resourceId, mastra: wrappedMastra, memory: options.memory, runId: options.runId, requestContext: mergeRequestContexts(options.requestContext, execOptions.requestContext), actor: execOptions.actor, workspace: execOptions.workspace ?? options.workspace, browser: options.browser, observe: execOptions.observe ?? noopObserve, writer: new ToolStream({ prefix: "tool", callId: execOptions.toolCallId, name: options.name, runId: options.runId }, options.outputWriter || execOptions.outputWriter), ...createObservabilityContext({ currentSpan: toolSpan }), abortSignal: execOptions.abortSignal, suspend: (args, suspendOptions) => { suspendData = args; const newSuspendOptions = { ...suspendOptions ?? {}, resumeSchema: suspendOptions?.resumeSchema ?? (resumeSchema ? JSON.stringify(standardSchemaToJSONSchema(toStandardSchema(resumeSchema), { io: "input" })) : void 0) }; return execOptions.suspend?.(args, newSuspendOptions); }, resumeData: execOptions.resumeData }; const isAgentExecution = execOptions.toolCallId && execOptions.messages || options.agentName && options.threadId && !options.workflowId; const isWorkflowExecution = !isAgentExecution && (options.workflow || options.workflowId); let toolContext; if (isAgentExecution) { const { suspend, resumeData, threadId, resourceId, ...restBaseContext } = baseContext; toolContext = { ...restBaseContext, agent: { agentId: options.agentId || "", toolCallId: execOptions.toolCallId || "", messages: execOptions.messages || [], suspend, resumeData, threadId, resourceId, outputWriter: options.outputWriter || execOptions.outputWriter, flushMessages: execOptions.flushMessages } }; } else if (isWorkflowExecution) { const { suspend, resumeData, ...restBaseContext } = baseContext; toolContext = { ...restBaseContext, workflow: options.workflow || { runId: options.runId, workflowId: options.workflowId, state: options.state, setState: options.setState, suspend, resumeData } }; } else if (execOptions.mcp) toolContext = { ...baseContext, mcp: execOptions.mcp }; else toolContext = baseContext; const resumeData = execOptions.resumeData; if (resumeData) { const resumeValidation = validateToolInput(resumeSchema, resumeData, options.name); if (resumeValidation.error) { logger?.warn(resumeValidation.error.message); toolSpan?.end({ output: resumeValidation.error, attributes: { success: false } }); return resumeValidation.error; } } result = await executeWithContext({ span: toolSpan, fn: async () => tool?.execute?.(args, toolContext) }); } if (suspendData) { const suspendValidation = validateToolSuspendData(this.getSuspendSchema(), suspendData, options.name); if (suspendValidation.error) { logger?.warn(suspendValidation.error.message); toolSpan?.end({ output: suspendValidation.error, attributes: { success: false } }); return suspendValidation.error; } } if (typeof result === "undefined" && !!suspendData) { toolSpan?.end({ output: result, attributes: { success: true } }); return result; } if (isVercelTool(tool)) { const outputValidation = validateToolOutput(this.getOutputSchema(), result, options.name, false); if (outputValidation.error) { logger?.warn(outputValidation.error.message); toolSpan?.end({ output: outputValidation.error, attributes: { success: false } }); return outputValidation.error; } result = outputValidation.data; } toolSpan?.end({ output: result, attributes: { success: true } }); return result; } catch (error) { toolSpan?.error({ error, attributes: { success: false } }); throw error; } }; return async (args, execOptions) => { let logger = options.logger || this.logger; const tracingContext = execOptions?.tracingContext || options.tracingContext; const toolRequestContext = execOptions?.requestContext ?? options.requestContext; const toolSpan = getOrCreateSpan({ type: mcpMeta ? "mcp_tool_call" : "tool_call", name: mcpMeta ? `mcp_tool: '${options.name}' on '${mcpMeta.serverName}'` : `tool: '${options.name}'`, input: args, entityType: EntityType.TOOL, entityId: options.name, entityName: options.name, attributes: mcpMeta ? { mcpServer: mcpMeta.serverName, serverVersion: mcpMeta.serverVersion, toolDescription: options.description, toolCallId: execOptions?.toolCallId } : { toolDescription: options.description, toolType: logType || "tool", toolCallId: execOptions?.toolCallId }, tracingPolicy: options.tracingPolicy, tracingContext, requestContext: toolRequestContext, mastra: options.mastra && "observability" in options.mastra ? options.mastra : void 0 }); const fgaProvider = options.mastra?.getServer?.()?.fga; const user = toolRequestContext?.get("user"); if (fgaProvider) { const { getAgentToolFGAResourceId, getMCPToolFGAResourceId, getStandaloneToolFGAResourceId, requireFGA } = await import("./auth/ee/fga-check.js"); await requireFGA({ fgaProvider, user, resource: { type: "tool", id: mcpMeta?.serverName ? getMCPToolFGAResourceId(mcpMeta.serverName, options.name) : options.agentId ? getAgentToolFGAResourceId(options.agentId, options.name) : getStandaloneToolFGAResourceId(options.name) }, permission: MastraFGAPermissions.TOOLS_EXECUTE, requestContext: toolRequestContext, actor: execOptions?.actor, context: { resourceId: options.resourceId }, metadata: { toolName: options.name, agentId: options.agentId, agentName: options.agentName, runId: options.runId, threadId: options.threadId, executionResourceId: options.resourceId, mcpMetadata: mcpMeta } }); } try { logger.debug(start, { ...logData, ...rest, model: logModelObject, args }); const isResuming = !!execOptions?.resumeData; const parameters = this.getParameters(); if (!isResuming) { const { data, error } = validateToolInput(parameters, args, options.name); const suspendedToolRunIdErrToIgnore = error?.message?.includes("suspendedToolRunId: Required") && !args?.resumeData; if (error && !suspendedToolRunIdErrToIgnore) { logger.warn("Tool input validation failed", { ...logData, validationError: error.message }); toolSpan?.end({ output: error, attributes: { success: false } }); return error; } args = data; } return await new Promise((resolve, reject) => { setImmediate(async () => { try { resolve(await execFunction(args, execOptions, toolSpan)); } catch (err) { reject(err); } }); }); } catch (err) { const mastraError = new MastraError({ id: "TOOL_EXECUTION_FAILED", domain: ErrorDomain.TOOL, category: ErrorCategory.USER, details: { errorMessage: String(err), argsJson: safeStringify(args), model: model?.modelId ?? "" } }, err); toolSpan?.error({ error: mastraError, attributes: { success: false } }); logger.trackException(mastraError, { ...logData, ...rest, model: logModelObject, args }); throw mastraError; } }; } buildV5() { const builtTool = this.build(); if (!builtTool.parameters) throw new Error("Tool parameters are required"); const base = { ...builtTool, inputSchema: builtTool.parameters, onInputStart: "onInputStart" in this.originalTool ? this.originalTool.onInputStart : void 0, onInputDelta: "onInputDelta" in this.originalTool ? this.originalTool.onInputDelta : void 0, onInputAvailable: "onInputAvailable" in this.originalTool ? this.originalTool.onInputAvailable : void 0, onOutput: "onOutput" in this.originalTool ? this.originalTool.onOutput : void 0 }; if (builtTool.type === "provider-defined") { const { execute, parameters, ...rest } = base; const name = ("name" in builtTool && typeof builtTool.name === "string" ? builtTool.name : null) || builtTool.id.split(".")[1] || builtTool.id; return { ...rest, type: builtTool.type, id: builtTool.id, name, args: builtTool.args }; } return base; } build() { const providerTool = this.buildProviderTool(this.originalTool); if (providerTool) return providerTool; const model = this.options.model; const schemaCompatLayers = []; if (model) { const supportsStructuredOutputs = "supportsStructuredOutputs" in model ? model.supportsStructuredOutputs ?? false : false; const modelInfo = { modelId: model.modelId, supportsStructuredOutputs, provider: model.provider }; schemaCompatLayers.push(new OpenAIReasoningSchemaCompatLayer(modelInfo), new OpenAISchemaCompatLayer(modelInfo), new GoogleSchemaCompatLayer(modelInfo), new AnthropicSchemaCompatLayer(modelInfo), new DeepSeekSchemaCompatLayer(modelInfo), new MetaSchemaCompatLayer(modelInfo)); } const originalSchema = this.getParameters(); let processedInputSchema; if (originalSchema) if (isStandardSchemaWithJSON(originalSchema)) { const applicableLayer = schemaCompatLayers.find((layer) => layer.shouldApply()); let schemaToUse; if (applicableLayer) schemaToUse = applicableLayer.processToCompatSchema(originalSchema); else schemaToUse = toStandardSchema(originalSchema); processedInputSchema = jsonSchema(standardSchemaToJSONSchema(schemaToUse, { io: "input" }), { validate: (value) => { const result = schemaToUse["~standard"].validate(value); if (result instanceof Promise) return result.then((r) => { if ("issues" in r && r.issues) return { success: false, error: new Error(r.issues.map((i) => i.message).join(", ")) }; return { success: true, value: r.value }; }); if ("issues" in result && result.issues) return { success: false, error: new Error(result.issues.map((i) => i.message).join(", ")) }; return { success: true, value: result.value }; } }); } else processedInputSchema = applyCompatLayer({ schema: originalSchema, compatLayers: schemaCompatLayers, mode: "aiSdkSchema" }); const outputSchema = this.getOutputSchema(); let processedOutputSchema; if (outputSchema) if (isStandardSchemaWithJSON(outputSchema)) processedOutputSchema = standardSchemaToJSONSchema(outputSchema, { io: "output" }); else processedOutputSchema = applyCompatLayer({ schema: outputSchema, compatLayers: [], mode: "aiSdkSchema" }); let requireApproval = false; let needsApprovalFn; if (typeof this.options.requireApproval === "function") { requireApproval = true; needsApprovalFn = this.options.requireApproval; } else if (typeof this.options.requireApproval === "boolean") { requireApproval = this.options.requireApproval; needsApprovalFn = void 0; } if (isVercelTool(this.originalTool) && "needsApproval" in this.originalTool) { const needsApproval = this.originalTool.needsApproval; if (typeof needsApproval === "boolean") { requireApproval = needsApproval; needsApprovalFn = void 0; } else if (typeof needsApproval === "function") { needsApprovalFn = needsApproval; requireApproval = true; } } const instanceNeedsApprovalFn = getNeedsApprovalFn(this.originalTool); if (!needsApprovalFn && instanceNeedsApprovalFn) { needsApprovalFn = instanceNeedsApprovalFn; requireApproval = true; } return { type: "function", description: this.originalTool.description, requireApproval, needsApprovalFn, hasSuspendSchema: !!this.getSuspendSchema(), execute: this.originalTool.execute ? this.createExecute(this.originalTool, { ...this.options, description: this.originalTool.description }, this.logType) : void 0, id: "id" in this.originalTool ? this.originalTool.id : void 0, parameters: processedInputSchema ?? z.object({}), outputSchema: processedOutputSchema, strict: "strict" in this.originalTool ? this.originalTool.strict : void 0, providerOptions: "providerOptions" in this.originalTool ? this.originalTool.providerOptions : void 0, mcp: "mcp" in this.originalTool ? this.originalTool.mcp : void 0, toModelOutput: "toModelOutput" in this.originalTool ? this.originalTool.toModelOutput : void 0, transform: "transform" in this.originalTool ? this.originalTool.transform : void 0, inputExamples: "inputExamples" in this.originalTool ? this.originalTool.inputExamples : void 0, onInputStart: "onInputStart" in this.originalTool ? this.originalTool.onInputStart : void 0, onInputDelta: "onInputDelta" in this.originalTool ? this.originalTool.onInputDelta : void 0, onInputAvailable: "onInputAvailable" in this.originalTool ? this.originalTool.onInputAvailable : void 0, onOutput: "onOutput" in this.originalTool ? this.originalTool.onOutput : void 0, backgroundConfig: this.options.backgroundConfig }; } }; //#endregion //#region src/utils.ts const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); /** * Read a positive-integer environment variable (e.g. a TTL in ms). Unset, empty, * non-numeric, fractional, or non-positive values fall back to `fallback`. */ function readPositiveIntEnv(name, fallback) { const raw = process.env[name]; if (!raw) return fallback; const parsed = Number(raw); return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback; } /** * Checks if a value is a plain object (not an array, function, Date, RegExp, etc.) */ function isPlainObject(value) { if (value === null || typeof value !== "object") return false; const proto = Object.getPrototypeOf(value); return proto === Object.prototype || proto === null; } /** * Deep merges two objects, recursively merging nested plain objects. * Arrays, functions, and other non-plain objects are replaced (not merged). */ function deepMerge(target, source) { const output = { ...target }; if (!source) return output; Object.keys(source).forEach((key) => { const targetValue = output[key]; const sourceValue = source[key]; if (isPlainObject(targetValue) && isPlainObject(sourceValue)) output[key] = deepMerge(targetValue, sourceValue); else if (sourceValue !== void 0) output[key] = sourceValue; }); return output; } /** * Generate an empty object from a JSON Schema definition. * Accepts both a JSON string and a pre-parsed object. * Recursively initializes nested object properties and respects default values. */ function generateEmptyFromSchema(schema) { try { const parsedSchema = typeof schema === "string" ? JSON.parse(schema) : schema; if (!parsedSchema || parsedSchema.type !== "object" || !parsedSchema.properties) return {}; const obj = {}; for (const [key, prop] of Object.entries(parsedSchema.properties)) if (prop.default !== void 0) obj[key] = typeof prop.default === "object" && prop.default !== null ? JSON.parse(JSON.stringify(prop.default)) : prop.default; else if (prop.type === "object" && prop.properties) obj[key] = generateEmptyFromSchema(prop); else if (prop.type === "object") obj[key] = {}; else if (prop.type === "string") obj[key] = ""; else if (prop.type === "array") obj[key] = []; else if (prop.type === "number" || prop.type === "integer") obj[key] = 0; else if (prop.type === "boolean") obj[key] = false; else obj[key] = null; return obj; } catch { return {}; } } /** * Transforms a stream by masking content between XML tags. * @param stream Input stream to transform * @param tag Tag name to mask between (e.g. for <foo>...</foo>, use 'foo') * @param options Optional configuration for masking behavior */ async function* maskStreamTags(stream, tag, options = {}) { const { onStart, onEnd, onMask } = options; const openTag = `<${tag}>`; const closeTag = `</${tag}>`; let buffer = ""; let fullContent = ""; let isMasking = false; let isBuffering = false; const trimOutsideDelimiter = (text, delimiter, trim) => { if (!text.includes(delimiter)) return text; const parts = text.split(delimiter); if (trim === `before-start`) return `${delimiter}${parts[1]}`; return `${parts[0]}${delimiter}`; }; const startsWith = (text, pattern) => { if (pattern.includes(openTag.substring(0, 3))) pattern = trimOutsideDelimiter(pattern, `<`, `before-start`); return text.trim().startsWith(pattern.trim()); }; for await (const chunk of stream) { fullContent += chunk; if (isBuffering) buffer += chunk; const chunkHasTag = startsWith(chunk, openTag); const bufferHasTag = !chunkHasTag && isBuffering && startsWith(openTag, buffer); let toYieldBeforeMaskedStartTag = ``; if (!isMasking && (chunkHasTag || bufferHasTag)) { isMasking = true; isBuffering = false; const taggedTextToMask = trimOutsideDelimiter(buffer, `<`, `before-start`); if (taggedTextToMask !== buffer.trim()) toYieldBeforeMaskedStartTag = buffer.replace(taggedTextToMask, ``); buffer = ""; onStart?.(); } if (!isMasking && !isBuffering && startsWith(openTag, chunk) && chunk.trim() !== "") { isBuffering = true; buffer += chunk; continue; } if (isBuffering && buffer && !startsWith(openTag, buffer)) { yield buffer; buffer = ""; isBuffering = false; continue; } if (isMasking && fullContent.includes(closeTag)) { onMask?.(chunk); onEnd?.(); isMasking = false; const lastFullContent = fullContent; fullContent = ``; const textUntilEndTag = trimOutsideDelimiter(lastFullContent, closeTag, "after-end"); if (textUntilEndTag !== lastFullContent) yield lastFullContent.replace(textUntilEndTag, ``); continue; } if (isMasking) { onMask?.(chunk); if (toYieldBeforeMaskedStartTag) yield toYieldBeforeMaskedStartTag; continue; } yield chunk; } } /** * Resolve serialized zod output - This function takes the string output ot the `jsonSchemaToZod` function * and instantiates the zod object correctly. * * @param schema - serialized zod object * @returns resolved zod object */ function resolveSerializedZodOutput(schema) { return Function("z", `"use strict";return (${schema});`)(z); } /** * Checks if a value is a Zod type * @param value - The value to check * @returns True if the value is a Zod type, false otherwise */ function isZodType$1(value) { return typeof value === "object" && value !== null && "_def" in value && "parse" in value && typeof value.parse === "function" && "safeParse" in value && typeof value.safeParse === "function"; } function createDeterministicId(input) { return createHash("sha256").update(input).digest("hex").slice(0, 8); } /** * Sets the properties for a Vercel Tool, including an ID and inputSchema * @param tool - The tool to set the properties for * @returns The tool with the properties set */ function setVercelToolProperties(tool) { const inputSchema = "inputSchema" in tool ? tool.inputSchema : convertVercelToolParameters(tool); const toolId = !("id" in tool) ? tool.description ? `tool-${createDeterministicId(tool.description)}` : `tool-${Math.random().toString(36).substring(2, 9)}` : tool.id; return { ...tool, id: toolId, inputSchema }; } /** * Ensures a tool has an ID and inputSchema by generating one if not present * @param tool - The tool to ensure has an ID and inputSchema * @returns The tool with an ID and inputSchema */ function ensureToolProperties(tools) { return Object.keys(tools).reduce((acc, key) => { const tool = tools?.[key]; if (tool) { if (typeof tool === "function" && !(tool instanceof Tool) && !isVercelTool(tool)) throw new MastraError({ id: "TOOL_INVALID_FORMAT", domain: ErrorDomain.TOOL, category: ErrorCategory.USER, text: `Tool "${key}" is not a valid tool format. Tools must be created using createTool() or be a valid Vercel AI SDK tool. Received a function.` }); if (isVercelTool(tool)) acc[key] = setVercelToolProperties(tool); else acc[key] = tool; } return acc; }, {}); } function convertVercelToolParameters(tool) { let schema = tool.parameters ?? z.object({}); if (typeof schema === "function") schema = schema(); return isZodType$1(schema) ? schema : resolveSerializedZodOutput(jsonSchemaToZod(schema)); } /** * Converts a Vercel Tool or Mastra Tool into a CoreTool format * @param originalTool - The tool to convert (either VercelTool or ToolAction) * @param options - Tool options including Mastra-specific settings * @param logType - Type of tool to log (tool or toolset) * @returns A CoreTool that can be used by the system */ function makeCoreTool(originalTool, options, logType, autoResumeSuspendedTools, backgroundTaskEnabled) { return new CoreToolBuilder({ originalTool, options, logType, autoResumeSuspendedTools, backgroundTaskEnabled }).build(); } function makeCoreToolV5(originalTool, options, logType, autoResumeSuspendedTools, backgroundTaskEnabled) { return new CoreToolBuilder({ originalTool, options, logType, autoResumeSuspendedTools, backgroundTaskEnabled }).buildV5(); } /** * Creates a proxy for a Mastra instance to handle deprecated properties * @param mastra - The Mastra instance to proxy * @param logger - The logger to use for warnings * @returns A proxy for the Mastra instance */ function createMastraProxy({ mastra, logger }) { return new Proxy(mastra, { get(target, prop) { if (Reflect.has(target, prop)) { const value = Reflect.get(target, prop); if (typeof value === "function") return value.bind(target); return value; } if (prop === "logger") { logger.warn("Please use 'getLogger' instead, logger is deprecated"); return Reflect.apply(target.getLogger, target, []); } if (prop === "storage") { logger.warn("Please use 'getStorage' instead, storage is deprecated"); return Reflect.get(target, "storage"); } if (prop === "agents") { logger.warn("Please use 'listAgents' instead, agents is deprecated"); return Reflect.apply(target.listAgents, target, []); } if (prop === "tts") { logger.warn("Please use 'getTTS' instead, tts is deprecated"); return Reflect.apply(target.getTTS, target, []); } if (prop === "vectors") { logger.warn("Please use 'getVectors' instead, vectors is deprecated"); return Reflect.apply(target.getVectors, target, []); } if (prop === "memory") { logger.warn("Please use 'getMemory' instead, memory is deprecated"); return Reflect.get(target, "memory"); } return Reflect.get(target, prop); } }); } function checkEvalStorageFields(traceObject, logger) { const missingFields = []; if (!traceObject.input) missingFields.push("input"); if (!traceObject.output) missingFields.push("output"); if (!traceObject.agentName) missingFields.push("agent_name"); if (!traceObject.metricName) missingFields.push("metric_name"); if (!traceObject.instructions) missingFields.push("instructions"); if (!traceObject.globalRunId) missingFields.push("global_run_id"); if (!traceObject.runId) missingFields.push("run_id"); if (missingFields.length > 0) { if (logger) logger.warn("Skipping evaluation storage due to missing required fields", { missingFields, runId: traceObject.runId, agentName: traceObject.agentName }); else console.warn("Skipping evaluation storage due to missing required fields", { missingFields, runId: traceObject.runId, agentName: traceObject.agentName }); return false; } return true; } function detectSingleMessageCharacteristics(message) { if (typeof message === "object" && message !== null && (message.role === "function" || message.role === "data" || "toolInvocations" in message || "parts" in message || "experimental_attachments" in message)) return "has-ui-specific-parts"; else if (typeof message === "object" && message !== null && "content" in message && (Array.isArray(message.content) || "experimental_providerMetadata" in message || "providerOptions" in message)) return "has-core-specific-parts"; else if (typeof message === "object" && message !== null && "role" in message && "content" in message && typeof message.content === "string" && [ "system", "user", "assistant", "tool" ].includes(message.role)) return "message"; else return "other"; } function isUiMessage(message) { return detectSingleMessageCharacteristics(message) === `has-ui-specific-parts`; } function isCoreMessage(message) { return [`has-core-specific-parts`, `message`].includes(detectSingleMessageCharacteristics(message)); } const SQL_IDENTIFIER_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*$/; /** * Parses and returns a valid SQL identifier (such as a table or column name). * The identifier must: * - Start with a letter (a-z, A-Z) or underscore (_) * - Contain only letters, numbers, or underscores * - Be at most 63 characters long * * @param name - The identifier string to parse. * @param kind - Optional label for error messages (e.g., 'table name'). * @returns The validated identifier as a branded type. * @throws {Error} If the identifier does not conform to SQL naming rules. * * @example * const id = parseSqlIdentifier('my_table'); // Ok * parseSqlIdentifier('123table'); // Throws error */ function parseSqlIdentifier(name, kind = "identifier") { if (!SQL_IDENTIFIER_PATTERN.test(name) || name.length > 63) throw new Error(`Invalid ${kind}: ${name}. Must start with a letter or underscore, contain only letters, numbers, or underscores, and be at most 63 characters long.`); return name; } /** * Parses and returns a valid dot-separated SQL field key (e.g., 'user.profile.name'). * Each segment must: * - Start with a letter (a-z, A-Z) or underscore (_) * - Contain only letters, numbers, or underscores * - Be at most 63 characters long * * @param key - The dot-separated field key string to parse. * @returns The validated field key as a branded type. * @throws {Error} If any segment of the key is invalid. * * @example * const key = parseFieldKey('user_profile.name'); // Ok * parseFieldKey('user..name'); // Throws error * parseFieldKey('user.123name'); // Throws error */ function parseFieldKey(key) { if (!key) throw new Error("Field key cannot be empty"); const segments = key.split("."); for (const segment of segments) if (!SQL_IDENTIFIER_PATTERN.test(segment) || segment.length > 63) throw new Error(`Invalid field key segment: ${segment} in ${key}`); return key; } /** * Removes specific keys from an object. * @param obj - The original object * @param keysToOmit - Keys to exclude from the returned object * @returns A new object with the specified keys removed */ function omitKeys(obj, keysToOmit) { return Object.fromEntries(Object.entries(obj).filter(([key]) => !keysToOmit.includes(key))); } /** * Selectively extracts specific fields from an object using dot notation. * Does not error if fields don't exist - simply omits them from the result. * @param obj - The source object to extract fields from * @param fields - Array of field paths (supports dot notation like 'output.text') * @returns New object containing only the specified fields */ function selectFields(obj, fields) { if (!obj || typeof obj !== "object") return obj; const result = {}; for (const field of fields) { const value = getNestedValue(obj, field); if (value !== void 0) setNestedValue(result, field, value); } return result; } /** * Gets a nested value from an object using dot notation * @param obj - Source object * @param path - Dot notation path (e.g., 'output.text') * @returns The value at the path, or undefined if not found */ function getNestedValue(obj, path) { return path.split(".").reduce((current, key) => { return current && typeof current === "object" ? current[key] : void 0; }, obj); } /** * Sets a nested value in an object using dot notation * @param obj - Target object * @param path - Dot notation path (e.g., 'output.text') * @param value - Value to set */ function setNestedValue(obj, path, value) { const keys = path.split("."); const lastKey = keys.pop(); if (!lastKey) return; for (const key of keys) if (key === "__proto__" || key === "constructor" || key === "prototype") return; if (lastKey === "__proto__" || lastKey === "constructor" || lastKey === "prototype") return; let current = obj; for (const key of keys) { const existing = Object.prototype.hasOwnProperty.call(current, key) ? current[key] : void 0; if (existing === null || typeof existing !== "object") Object.defineProperty(current, key, { value: Object.create(null), writable: true, enumerable: true, configurable: true }); current = current[key]; } Object.defineProperty(current, lastKey, { value, writable: true, enumerable: true, configurable: true }); } const removeUndefinedValues = (obj) => { return Object.fromEntries(Object.entries(obj).filter(([_, value]) => value !== void 0)); }; //#endregion export { readPositiveIntEnv as _, ensureToolProperties as a, selectFields as b, isCoreMessage as c, makeCoreTool as d, makeCoreToolV5 as f, parseSqlIdentifier as g, parseFieldKey as h, delay as i, isUiMessage as l, omitKeys as m, createMastraProxy as n, generateEmptyFromSchema as o, maskStreamTags as p, deepMerge as r, getNestedValue as s, checkEvalStorageFields as t, isZodType$1 as u, removeUndefinedValues as v, setNestedValue as x, resolveSerializedZodOutput as y }; //# sourceMappingURL=utils-CCbB2dG1.js.map