@sema-agent/core
Version:
Stateless, task-oriented AI agent core
170 lines (169 loc) • 7.43 kB
JavaScript
import { UNREADABLE_LIST, snapshotToolNameList } from "./tool-name-list.js";
export const TOOL_SPEC_MEMBERS = Object.keys({
name: true, aliases: true, description: true, descriptionClassic: true, label: true, parameters: true, execute: true, effect: true,
egress: true, irreversibility: true, reversibilityProbe: true, permissionSeat: true, isConcurrencySafe: true, executionMode: true,
contentOrigin: true, offload: true, offloadThresholdChars: true, defer: true, alwaysLoad: true, prepareArguments: true,
validateInput: true, approvalPreview: true, family: true, pathTarget: true, ruleFace: true, renderHints: true, compactable: true,
budgetExempt: true, modelGate: true, contract: true, agentListing: true, agentModels: true, agentToolFaces: true,
agentToolLevelAllowTools: true, agentToolLevelDenyTools: true, agentToolPool: true, agentToolInheritsMcp: true, withAgents: true,
});
const FUNCTION_MEMBERS = new Set(["execute", "prepareArguments", "validateInput", "approvalPreview", "reversibilityProbe", "isConcurrencySafe", "withAgents"]);
const NAME_LIST_MEMBERS = new Set(["aliases", "agentModels", "agentToolLevelAllowTools", "agentToolLevelDenyTools"]);
const FACE_LIST_KEYS = new Set(["allowTools", "denyTools"]);
const FACE_PLACES = { elementKeys: FACE_LIST_KEYS };
const MAX_NODES = 20_000;
const MAX_DEPTH = 64;
const COPIED = new WeakSet();
class DeclarationRefused extends Error {
}
function refuse(path, why) {
const e = new DeclarationRefused(`ToolSpec ${path}: ${why}. A tool's declaration is data the run fixes once, before its first await — pass lists, schemas and hints as plain data (functions only in execute / prepareArguments / validateInput / approvalPreview / reversibilityProbe / isConcurrencySafe / withAgents).`);
e.code = "config.tool_mount_denied";
throw e;
}
const inertObject = () => Object.freeze(Object.create(null));
const inertFunction = () => Object.freeze(() => undefined);
function standIn(value) {
let list = false;
try {
list = Array.isArray(value);
}
catch {
}
return list ? Object.freeze([]) : inertObject();
}
function overBudget(path) {
return refuse(path, `declaration data over budget (${MAX_NODES} nodes / ${MAX_DEPTH} levels)`);
}
export const newCopyState = () => ({ nodes: 0, seen: new WeakMap(), seenLenient: new WeakMap(), inflight: new Set() });
function spendNodes(state, nodes, path) {
state.nodes += nodes;
if (state.nodes > MAX_NODES)
overBudget(path);
}
function copyNode(value, path, depth, state, lenient, places) {
if (value === null || (typeof value !== "object" && typeof value !== "function"))
return value;
if (typeof value === "function")
return lenient ? inertFunction() : refuse(path, "a function inside declaration data");
if (COPIED.has(value))
return value;
const seen = lenient ? state.seenLenient : state.seen;
const shared = seen.get(value);
if (shared !== undefined)
return shared;
if (depth > MAX_DEPTH)
overBudget(path);
if (state.inflight.has(value))
refuse(path, "a reference cycle is not declaration data");
state.inflight.add(value);
try {
if (Array.isArray(value)) {
const length = value.length;
spendNodes(state, length, path);
const items = new Array(length);
for (let i = 0; i < length; i++) {
const d = Object.getOwnPropertyDescriptor(value, i);
if (d === undefined)
continue;
if (!("value" in d)) {
if (lenient)
return standIn(value);
refuse(`${path}[${i}]`, "an accessor is not declaration data");
}
items[i] = copyNode(d.value, `${path}[${i}]`, depth + 1, state, lenient, places?.elementKeys === undefined ? undefined : { ownKeys: places.elementKeys });
}
const sealed = Object.freeze(items);
COPIED.add(sealed);
seen.set(value, sealed);
return sealed;
}
const proto = Object.getPrototypeOf(value);
if (proto !== null && proto !== Object.prototype)
return lenient ? inertObject() : refuse(path, "an object with a prototype (a class instance, a Date, a Map …) is not declaration data");
const out = Object.create(proto);
const keys = Reflect.ownKeys(value);
spendNodes(state, keys.length, path);
for (const key of keys) {
const d = Object.getOwnPropertyDescriptor(value, key);
if (d === undefined)
continue;
const at = `${path}.${String(key)}`;
if (!("value" in d)) {
if (lenient)
return inertObject();
refuse(at, "an accessor is not declaration data");
}
const kept = typeof key === "string" && places?.ownKeys?.has(key) === true ? copyNameList(d.value, at, state) : copyNode(d.value, at, depth + 1, state, lenient);
Object.defineProperty(out, key, { value: kept, enumerable: d.enumerable, writable: false, configurable: false });
}
const sealed = Object.freeze(out);
COPIED.add(sealed);
seen.set(value, sealed);
return sealed;
}
catch (cause) {
if (cause instanceof DeclarationRefused || !lenient)
throw cause;
return standIn(value);
}
finally {
state.inflight.delete(value);
}
}
function copyNameList(value, path, state) {
if (typeof value === "object" && value !== null) {
if (COPIED.has(value))
return value;
const shared = state.seenLenient.get(value);
if (shared !== undefined)
return shared;
}
let slots = 0;
try {
if (Array.isArray(value))
slots = value.length;
}
catch {
}
spendNodes(state, slots, path);
const kept = snapshotToolNameList(value);
if (kept === UNREADABLE_LIST)
return kept;
if (kept === value || !Array.isArray(kept))
return copyNode(value, path, 0, state, true);
if (kept.length > slots)
spendNodes(state, kept.length - slots, path);
const sealed = Object.freeze(kept.map((entry, i) => copyNode(entry, `${path}[${i}]`, 1, state, true)));
COPIED.add(sealed);
if (typeof value === "object" && value !== null)
state.seenLenient.set(value, sealed);
return sealed;
}
export function frozenMember(name, value, state) {
if (FUNCTION_MEMBERS.has(name) && typeof value === "function")
return value;
spendNodes(state, 1, name);
if (NAME_LIST_MEMBERS.has(name))
return copyNameList(value, name, state);
return copyNode(value, name, 0, state, false, name === "agentToolFaces" ? FACE_PLACES : undefined);
}
export function frozenExtra(key, value, state) {
try {
spendNodes(state, 1, key);
return { kept: true, value: copyNode(value, key, 0, state, false) };
}
catch {
return { kept: false };
}
}
export function frozenToolSpec(spec) {
const copy = {};
const state = newCopyState();
for (const name of TOOL_SPEC_MEMBERS) {
const value = Reflect.get(spec, name);
if (value !== undefined)
copy[name] = frozenMember(name, value, state);
}
return Object.freeze(copy);
}