agents
Version:
A home for your AI agents
239 lines (238 loc) • 8.77 kB
JavaScript
import { c as isModelInvocable, d as err, f as ok, o as parseSkillResourcePath, t as InvalidSkillResourcePathError } from "./resource-B59OV2a2.js";
//#region src/skills/source.ts
/** A source's backing store failed. */
var SkillSourceError = class extends Error {
/**
* @param sourceId - The source that failed.
* @param operation - What the source was doing.
* @param cause - The underlying store error.
*/
constructor(sourceId, operation, cause) {
super(`Skill source "${sourceId}" failed to ${operation === "list" ? "list skills" : "read a resource"}: ${cause instanceof Error ? cause.message : String(cause)}`, { cause });
this.sourceId = sourceId;
this.operation = operation;
this._tag = "SkillSourceError";
this.name = "SkillSourceError";
}
};
/** A skill has no file at the requested path. */
var SkillResourceNotFoundError = class extends Error {
/**
* @param skill - The skill that was searched.
* @param path - The missing path.
*/
constructor(skill, path) {
super(`Resource not found: ${skill}/${path}`);
this.skill = skill;
this.path = path;
this._tag = "SkillResourceNotFoundError";
this.name = "SkillResourceNotFoundError";
}
};
//#endregion
//#region src/skills/catalog.ts
/** The catalog has no skill with the requested name. */
var UnknownSkillError = class extends Error {
/** @param skill - The requested name. */
constructor(skill) {
super(`Skill not found: ${skill}`);
this.skill = skill;
this._tag = "UnknownSkillError";
this.name = "UnknownSkillError";
}
};
/**
* Successful listings behind each catalog, by source object. Lets a later
* load fall back to the last good listing of a failing source without
* putting listings on the public catalog shape.
*/
const catalogListings = /* @__PURE__ */ new WeakMap();
/**
* Load every source and merge them into a catalog.
*
* Sources apply in order: the first source to list a name wins. A failing
* source or an invalid skill becomes a diagnostic instead of an error, so
* one bad source never hides the others. Skills that set
* `disable-model-invocation: true` are left out.
*
* @param sources - Skill sources in priority order.
* @param options - Pass `previous` to keep a failing source's last skills.
* @returns The merged catalog.
*/
async function loadSkillCatalog(sources, options = {}) {
const previousListings = options.previous ? catalogListings.get(options.previous) : void 0;
const listings = await Promise.all(sources.map(async (source) => ({
source,
listed: await source.list().catch((cause) => err(new SkillSourceError(source.id, "list", cause)))
})));
const diagnostics = [];
const entries = /* @__PURE__ */ new Map();
const sourceBySkill = /* @__PURE__ */ new Map();
const goodListings = /* @__PURE__ */ new Map();
for (const { source, listed } of listings) {
let listing;
if (listed.ok) listing = listed.value;
else {
const fallback = previousListings?.get(source);
diagnostics.push({
_tag: "SourceFailed",
sourceId: source.id,
error: listed.error,
keptPrevious: fallback !== void 0
});
if (!fallback) continue;
listing = fallback;
}
goodListings.set(source, listing);
for (const invalid of listing.invalid) diagnostics.push({
_tag: "InvalidSkill",
sourceId: source.id,
location: invalid.location,
error: invalid.error
});
for (const { skill, resources, location, warnings } of listing.skills) {
const existing = entries.get(skill.name);
if (existing) {
diagnostics.push({
_tag: "DuplicateSkill",
name: skill.name,
sourceId: source.id,
keptSourceId: existing.sourceId
});
continue;
}
entries.set(skill.name, {
skill,
sourceId: source.id,
resources,
...location === void 0 ? {} : { location }
});
sourceBySkill.set(skill.name, source);
for (const warning of warnings ?? []) diagnostics.push({
_tag: "SkillWarning",
sourceId: source.id,
name: skill.name,
error: warning
});
}
}
const skills = [...entries.values()].filter((entry) => isModelInvocable(entry.skill));
const visible = new Map(skills.map((entry) => [entry.skill.name, entry]));
const prompt = renderCatalogPrompt(skills);
const catalog = {
fingerprint: fingerprintOf(prompt ?? ""),
skills,
diagnostics,
prompt: () => prompt,
find: (name) => visible.get(name),
activate(name) {
const entry = visible.get(name);
return entry ? ok(renderSkillActivation(entry)) : err(new UnknownSkillError(name));
},
async readResource(request) {
const target = resolveResourceTarget(visible, request);
if (!target.ok) return target;
const { entry, path } = target.value;
const source = sourceBySkill.get(entry.skill.name);
if (!source) return err(new UnknownSkillError(entry.skill.name));
const read = await source.readResource(entry.skill.name, path).catch((cause) => err(new SkillSourceError(source.id, "read-resource", cause)));
if (!read.ok) return read;
return ok({
entry,
resource: read.value
});
}
};
catalogListings.set(catalog, goodListings);
return catalog;
}
/**
* Describe a diagnostic in one line, for logs.
*
* @param diagnostic - A diagnostic from {@link SkillCatalog.diagnostics}.
*/
function formatSkillDiagnostic(diagnostic) {
switch (diagnostic._tag) {
case "InvalidSkill": return `Skipped invalid skill ${diagnostic.location} from source "${diagnostic.sourceId}": ${diagnostic.error.message}`;
case "SkillWarning": return `Skill "${diagnostic.name}" from source "${diagnostic.sourceId}" breaks the Agent Skills spec but was loaded: ${diagnostic.error.message}`;
case "SourceFailed": return `${diagnostic.error.message}. ${diagnostic.keptPrevious ? "Its last listed skills were kept." : "Its skills were skipped."}`;
case "DuplicateSkill": return `Duplicate skill "${diagnostic.name}" from source "${diagnostic.sourceId}" ignored; already provided by "${diagnostic.keptSourceId}".`;
}
}
/**
* Render a resource read for the model.
*
* @param read - A resource returned by {@link SkillCatalog.readResource}.
*/
function renderSkillResource(read) {
const { entry, resource } = read;
return [
`<skill_resource${attributes([
["name", entry.skill.name],
["path", resource.path],
["kind", resource.kind],
["encoding", resource.encoding],
["mimeType", resource.mimeType]
])}>`,
resource.content,
"</skill_resource>"
].join("\n");
}
function renderCatalogPrompt(skills) {
if (skills.length === 0) return null;
return [
"Available skills. When a task matches a skill, use activate_skill with its name before proceeding.",
"",
...skills.map(({ skill }) => `- ${skill.name}: ${skill.description.replace(/\s+/g, " ")}`)
].join("\n");
}
function renderSkillActivation(entry) {
const lines = [`<skill_content${attributes([["name", entry.skill.name], ["location", entry.location]])}>`, entry.skill.body.trim()];
if (entry.resources.length > 0) {
lines.push("", "<skill_resources>");
for (const resource of entry.resources) lines.push(` <file${attributes([
["kind", resource.kind],
["encoding", resource.encoding],
["size", resource.size === void 0 ? void 0 : String(resource.size)]
])}>${resource.path}</file>`);
lines.push("</skill_resources>");
}
lines.push("</skill_content>");
return lines.join("\n");
}
function resolveResourceTarget(skills, request) {
let name = request.name;
let relative = request.path;
if (name === void 0) {
const slash = relative.indexOf("/");
if (slash <= 0) return err(new InvalidSkillResourcePathError(relative));
name = relative.slice(0, slash);
relative = relative.slice(slash + 1);
}
const entry = skills.get(name);
if (!entry) return err(new UnknownSkillError(name));
const path = parseSkillResourcePath(relative);
if (!path.ok) return path;
if (!entry.resources.some((resource) => resource.path === path.value)) return err(new SkillResourceNotFoundError(entry.skill.name, path.value));
return ok({
entry,
path: path.value
});
}
function attributes(pairs) {
return pairs.filter((pair) => pair[1] !== void 0).map(([key, value]) => ` ${key}="${value.replace(/"/g, """)}"`).join("");
}
/** Two FNV-1a passes with different seeds: cheap and stable across isolates. */
function fingerprintOf(text) {
let a = 2166136261;
let b = 16777619;
for (let index = 0; index < text.length; index++) {
const code = text.charCodeAt(index);
a = Math.imul(a ^ code, 16777619);
b = Math.imul(b ^ code, 2166136261);
}
return `${(a >>> 0).toString(16).padStart(8, "0")}${(b >>> 0).toString(16).padStart(8, "0")}`;
}
//#endregion
export { SkillResourceNotFoundError as a, renderSkillResource as i, formatSkillDiagnostic as n, SkillSourceError as o, loadSkillCatalog as r, UnknownSkillError as t };
//# sourceMappingURL=catalog-BpOejOsE.js.map