UNPKG

@mastra/core

Version:
357 lines (356 loc) 11.5 kB
import { n as LocalSkillSource, r as validateSkillMetadata, t as WorkspaceSkillsImpl } from "./workspace-skills-BcLsoENh.js"; import matter from "gray-matter"; //#region src/skills/create-skill.ts /** * createSkill() — factory for creating inline skills in code. * * Creates a Skill object that can be passed to an Agent's `skills` config * without requiring a Workspace or filesystem. * * @example * ```typescript * import { createSkill } from '@mastra/core/skills'; * * const reviewSkill = createSkill({ * name: 'code-review', * description: 'Use when reviewing code changes.', * instructions: ` * When reviewing code: * 1. Check for correctness * 2. Check for style consistency * 3. Look for potential bugs * `, * references: { * 'checklist.md': '# Review Checklist\n...', * }, * }); * ``` */ /** * Create an inline skill from code — no filesystem needed. * * The returned object implements the `Skill` interface and can be passed * directly to an Agent's `skills` config or used anywhere a `Skill` is expected. * * @throws Error if the skill metadata fails validation */ function createSkill(input) { const { name, description, instructions, license, compatibility, metadata, references } = input; const validation = validateSkillMetadata({ name, description, license, compatibility, "user-invocable": input["user-invocable"], metadata }, void 0, instructions); if (!validation.valid) throw new Error(`Invalid skill "${name}": ${validation.errors.join("; ")}`); const referenceKeys = references ? Object.keys(references) : []; return { __inline: true, __referenceContents: references ?? {}, name, description, instructions, license, compatibility, "user-invocable": input["user-invocable"], metadata, path: `inline/${name}`, source: { type: "local", projectPath: `inline/${name}` }, references: referenceKeys, scripts: [], assets: [] }; } /** * Type guard: is this skill an inline skill (from createSkill)? */ function isInlineSkill(skill) { return typeof skill === "object" && skill !== null && "__inline" in skill && skill.__inline === true; } //#endregion //#region src/skills/inline-skill-source.ts /** * InlineSkillSource — in-memory SkillSource for code-defined skills. * * Serves skills created via `createSkill()` without any filesystem dependency. * Implements the SkillSource interface so it can be used with WorkspaceSkillsImpl. * * Directory layout emulation: * Each inline skill appears as a directory at `inline/<name>/` with: * - SKILL.md (generated from the skill's metadata + instructions) * - references/<file> (from the skill's `references` map) */ var InlineSkillSource = class { #skills; /** Pre-built SKILL.md content per skill name */ #skillMdCache; constructor(skills) { this.#skills = new Map(skills.map((s) => [s.name, s])); this.#skillMdCache = /* @__PURE__ */ new Map(); for (const skill of skills) this.#skillMdCache.set(skill.name, this.#buildSkillMd(skill)); } /** * Build a synthetic SKILL.md from an inline skill's metadata and instructions. */ #buildSkillMd(skill) { const frontmatter = { name: skill.name, description: skill.description }; if (skill.license) frontmatter.license = skill.license; if (skill.compatibility) frontmatter.compatibility = skill.compatibility; if (skill["user-invocable"] !== void 0) frontmatter["user-invocable"] = skill["user-invocable"]; if (skill.metadata) frontmatter.metadata = skill.metadata; return matter.stringify(skill.instructions, frontmatter); } /** * Parse a path into skill name and relative sub-path. * Paths look like `inline/<name>`, `inline/<name>/SKILL.md`, `inline/<name>/references/file.md` */ #parsePath(inputPath) { if (!inputPath.startsWith("inline/")) return null; const rest = inputPath.slice(7); const slashIdx = rest.indexOf("/"); if (slashIdx === -1) return { skillName: rest, subPath: "" }; return { skillName: rest.slice(0, slashIdx), subPath: rest.slice(slashIdx + 1) }; } #getSkill(inputPath) { const parsed = this.#parsePath(inputPath); if (!parsed) return null; const skill = this.#skills.get(parsed.skillName); if (!skill) return null; return { skill, subPath: parsed.subPath }; } async exists(path) { const result = this.#getSkill(path); if (!result) return false; const { skill, subPath } = result; if (subPath === "") return true; if (subPath === "SKILL.md") return true; if (subPath === "references") return (skill.references?.length ?? 0) > 0; if (subPath.startsWith("references/")) { const refPath = subPath.slice(11); return skill.references.includes(refPath); } return false; } async stat(path) { const result = this.#getSkill(path); if (!result) throw new Error(`ENOENT: no such file or directory: ${path}`); const { skill, subPath } = result; const now = /* @__PURE__ */ new Date(); if (subPath === "") return { name: skill.name, type: "directory", size: 0, createdAt: now, modifiedAt: now }; if (subPath === "SKILL.md") { const content = this.#skillMdCache.get(skill.name) ?? ""; return { name: "SKILL.md", type: "file", size: Buffer.byteLength(content, "utf-8"), createdAt: now, modifiedAt: now, mimeType: "text/markdown" }; } if (subPath === "references") return { name: "references", type: "directory", size: 0, createdAt: now, modifiedAt: now }; throw new Error(`ENOENT: no such file or directory: ${path}`); } async readFile(path) { const result = this.#getSkill(path); if (!result) throw new Error(`ENOENT: no such file or directory: ${path}`); const { skill, subPath } = result; if (subPath === "SKILL.md") return this.#skillMdCache.get(skill.name) ?? ""; if (subPath.startsWith("references/")) { const refPath = subPath.slice(11); const content = skill.__referenceContents[refPath]; if (content !== void 0) return content; } throw new Error(`ENOENT: no such file or directory: ${path}`); } async readdir(path) { const parsed = this.#parsePath(path); if (!parsed) return []; const { skillName, subPath } = parsed; const skill = this.#skills.get(skillName); if (!skill) return []; if (subPath === "") { const entries = [{ name: "SKILL.md", type: "file" }]; if (skill.references.length > 0) entries.push({ name: "references", type: "directory" }); return entries; } if (subPath === "references") return skill.references.map((ref) => ({ name: ref, type: "file" })); return []; } }; //#endregion //#region src/skills/agent-skills-resolver.ts /** * Combines multiple SkillSources, routing by path prefix. * Inline skills use `inline:<name>` paths; everything else goes to LocalSkillSource. */ var CompositeSkillSource = class { #local; #inline; constructor(local, inline) { this.#local = local; this.#inline = inline; } #route(path) { return path.startsWith("inline/") ? this.#inline : this.#local; } exists(path) { return this.#route(path).exists(path); } stat(path) { return this.#route(path).stat(path); } readFile(path) { return this.#route(path).readFile(path); } readdir(path) { return this.#route(path).readdir(path); } async realpath(path) { const source = this.#route(path); return source.realpath ? source.realpath(path) : path; } }; /** * Resolve an array of SkillInput items into a WorkspaceSkills instance. * * @param skills - Array of path strings and/or inline skills * @returns A WorkspaceSkills implementation ready for use by the Agent */ function resolveAgentSkills(skills) { const inlineSkills = []; const pathSkills = []; for (const skill of skills) if (isInlineSkill(skill)) inlineSkills.push(skill); else pathSkills.push(skill); let source; const skillPaths = [...pathSkills]; if (inlineSkills.length > 0 && pathSkills.length > 0) { source = new CompositeSkillSource(new LocalSkillSource(), new InlineSkillSource(inlineSkills)); for (const skill of inlineSkills) skillPaths.push(`inline/${skill.name}`); } else if (inlineSkills.length > 0) { source = new InlineSkillSource(inlineSkills); for (const skill of inlineSkills) skillPaths.push(`inline/${skill.name}`); } else source = new LocalSkillSource(); return new WorkspaceSkillsImpl({ source, skills: skillPaths, validateOnLoad: true }); } /** * Merge two WorkspaceSkills instances by combining their skill lists. * Agent-level skills take precedence on name conflicts (returned first in list). */ async function mergeWorkspaceSkills(agentSkills, workspaceSkills) { return { merged: new MergedWorkspaceSkills(agentSkills, workspaceSkills), agentSkillNames: new Set((await agentSkills.list()).map((s) => s.name)) }; } /** * A WorkspaceSkills wrapper that merges two skill sets. * Agent skills take precedence on name conflicts. */ var MergedWorkspaceSkills = class { #primary; #secondary; constructor(primary, secondary) { this.#primary = primary; this.#secondary = secondary; } async list() { const primaryList = await this.#primary.list(); const secondaryList = await this.#secondary.list(); const primaryNames = new Set(primaryList.map((s) => s.name)); return [...primaryList, ...secondaryList.filter((s) => !primaryNames.has(s.name))]; } async get(name) { const primary = await this.#primary.get(name); if (primary) return primary; return this.#secondary.get(name); } async has(name) { return await this.#primary.has(name) || await this.#secondary.has(name); } async refresh() { await Promise.all([this.#primary.refresh(), this.#secondary.refresh()]); } async maybeRefresh(context) { await Promise.all([this.#primary.maybeRefresh(context), this.#secondary.maybeRefresh(context)]); } async search(query, options) { const [primaryResults, secondaryResults] = await Promise.all([this.#primary.search(query, options), this.#secondary.search(query, options)]); return [...primaryResults, ...secondaryResults].sort((a, b) => b.score - a.score); } async getReference(skillName, referencePath) { const primary = await this.#primary.getReference(skillName, referencePath); if (primary !== null) return primary; return this.#secondary.getReference(skillName, referencePath); } async getScript(skillName, scriptPath) { const primary = await this.#primary.getScript(skillName, scriptPath); if (primary !== null) return primary; return this.#secondary.getScript(skillName, scriptPath); } async getAsset(skillName, assetPath) { const primary = await this.#primary.getAsset(skillName, assetPath); if (primary !== null) return primary; return this.#secondary.getAsset(skillName, assetPath); } async listReferences(skillName) { const primary = await this.#primary.listReferences(skillName); if (primary.length > 0) return primary; return this.#secondary.listReferences(skillName); } async listScripts(skillName) { const primary = await this.#primary.listScripts(skillName); if (primary.length > 0) return primary; return this.#secondary.listScripts(skillName); } async listAssets(skillName) { const primary = await this.#primary.listAssets(skillName); if (primary.length > 0) return primary; return this.#secondary.listAssets(skillName); } }; //#endregion export { isInlineSkill as a, createSkill as i, resolveAgentSkills as n, InlineSkillSource as r, mergeWorkspaceSkills as t }; //# sourceMappingURL=agent-skills-resolver-O3RWfuIN.js.map