UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

58 lines (57 loc) • 3 kB
import { readSkillTable } from "../skill-grant.js"; import { assertNoRetiredKeys, RETIRED_TASK_SPEC_KEYS } from "../retired-keys.js"; import { skillBaseDirOf } from "./skill-base-dir.js"; const SKILLS_SEAT = "TaskSpec.skills"; const kindOf = (v) => (v === null ? "null" : Array.isArray(v) ? "an array" : typeof v); function skillsSeatRefusal(field, problem) { const e = new Error(`${field} ${problem} — an entry the engine cannot read is refused rather than dropped.`); e.code = "config.invalid_argument"; e.detail = { field }; return e; } function readSkillFiles(files, at) { if (files === undefined) return undefined; if (!Array.isArray(files)) throw skillsSeatRefusal(at, `must be a list of { path, content } attachments (got ${kindOf(files)})`); const out = []; for (const [j, f] of files.entries()) { if (typeof f !== "object" || f === null || Array.isArray(f)) throw skillsSeatRefusal(`${at}[${j}]`, `must be an attachment object { path, content } (got ${kindOf(f)})`); const { path, content } = f; if (typeof path !== "string") throw skillsSeatRefusal(`${at}[${j}].path`, `must be a string (got ${kindOf(path)})`); if (typeof content !== "string") throw skillsSeatRefusal(`${at}[${j}].content`, `must be a string (got ${kindOf(content)})`); out.push({ path, content }); } return out; } export function readSkillsSeat(skills) { if (skills === undefined) return []; if (!Array.isArray(skills)) throw skillsSeatRefusal(SKILLS_SEAT, `must be a list (got ${kindOf(skills)})`); assertNoRetiredKeys({ skills }, RETIRED_TASK_SPEC_KEYS, "TaskSpec"); const seen = new Set(); const out = []; for (const [i, raw] of skills.entries()) { const at = `${SKILLS_SEAT}[${i}]`; if (typeof raw !== "object" || raw === null || Array.isArray(raw)) throw skillsSeatRefusal(at, `must be an object { name, description, content } (got ${kindOf(raw)})`); const { name, description, content, allowedTools, disallowedTools, files: rawFiles, baseDir, ...rest } = raw; void rest; for (const [member, value] of [["name", name], ["description", description], ["content", content]]) { if (typeof value !== "string") throw skillsSeatRefusal(`${at}.${member}`, `must be a string (got ${kindOf(value)})`); } const files = readSkillFiles(rawFiles, `${at}.files`); skillBaseDirOf(baseDir); if (seen.has(name)) continue; seen.add(name); const [allowed, denied] = [readSkillTable(allowedTools), readSkillTable(disallowedTools)]; out.push({ name, description, content, ...(allowed !== undefined ? { allowedTools: allowed } : {}), ...(denied !== undefined ? { disallowedTools: denied } : {}), ...(files !== undefined ? { files } : {}), ...(baseDir !== undefined ? { baseDir } : {}) }); } return out; }