UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

170 lines (169 loc) • 7.43 kB
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); }