@sema-agent/core
Version:
Stateless, task-oriented AI agent core
220 lines (219 loc) • 9.38 kB
JavaScript
import { TOOL_SPEC_MEMBERS, frozenExtra, frozenMember, newCopyState } from "../tool-spec-members.js";
import { isDefineToolProduct, rebindDefineToolCtx } from "../tools.js";
function specArmCanConstruct(t) {
if (typeof t !== "object" || t === null)
return false;
const name = Object.getOwnPropertyDescriptor(t, "name");
const execute = Object.getOwnPropertyDescriptor(t, "execute");
return name !== undefined && "value" in name && typeof name.value === "string" && name.enumerable === true && execute !== undefined && "value" in execute && typeof execute.value === "function" && execute.enumerable === true;
}
const ORIGIN = new WeakMap();
export function callerEntryOrigin(entry) {
return ORIGIN.get(entry) ?? entry;
}
const PRODUCT_OF = new WeakMap();
const IS_MEMBER = new Set(TOOL_SPEC_MEMBERS);
function faceKeys(t) {
const keys = new Set(Reflect.ownKeys(t).filter((k) => Object.getOwnPropertyDescriptor(t, k)?.enumerable === true));
for (const name of TOOL_SPEC_MEMBERS)
keys.add(name);
return [...keys];
}
const BUILDABLE = new WeakSet();
export function specArmCanBuild(entry) {
return BUILDABLE.has(entry);
}
const entryName = (t) => {
try {
const name = t !== null && (typeof t === "object" || typeof t === "function") ? Object.getOwnPropertyDescriptor(t, "name")?.value : undefined;
return typeof name === "string" ? JSON.stringify(name) : "(no own name)";
}
catch {
return "(no readable name)";
}
};
function refuseUnreadableEntry(index, entry, cause) {
let said = "";
try {
said = cause instanceof Error ? cause.message.slice(0, 120) : "";
}
catch {
}
const e = new Error(`spec.tools[${index}] ${entryName(entry)}: its members could not be read${said === "" ? "" : ` (${said})`} — the run fixes its tool roster by reading each entry once, before its first await, and an entry that cannot be read is not a tool. Pass a tool whose declaration reads without throwing.`);
e.code = "config.tool_mount_denied";
throw e;
}
export function rosterFaceOf(t, index) {
if ((typeof t !== "object" && typeof t !== "function") || t === null)
return t;
try {
const product = isDefineToolProduct(t) ? t : undefined;
const buildable = product === undefined && specArmCanConstruct(t);
const source = t;
const face = {};
const ledger = newCopyState();
for (const key of faceKeys(source)) {
const own = Object.prototype.hasOwnProperty.call(source, key);
const value = source[key];
if (value === undefined && !own)
continue;
let kept;
if (typeof key === "string" && IS_MEMBER.has(key))
kept = frozenMember(key, value, ledger);
else {
const extra = frozenExtra(String(key), value, ledger);
if (!extra.kept)
continue;
kept = extra.value;
}
Object.defineProperty(face, key, { value: kept, enumerable: true, writable: true, configurable: true });
}
ORIGIN.set(face, source);
if (product !== undefined)
PRODUCT_OF.set(face, product);
else if (buildable && specArmCanConstruct(face))
BUILDABLE.add(face);
return Object.freeze(face);
}
catch (cause) {
return refuseEntry(index, t, cause);
}
}
function refuseEntry(index, entry, cause) {
let refusal;
try {
if (cause instanceof Error && cause.code === "config.tool_mount_denied")
refusal = cause.message;
}
catch {
}
if (refusal === undefined)
return refuseUnreadableEntry(index, entry, cause);
const e = new Error(`spec.tools[${index}] ${entryName(entry)}: ${refusal}`);
e.code = "config.tool_mount_denied";
throw e;
}
function sameMember(was, now, ledger, depth = 0) {
if (Object.is(was, now))
return true;
if (depth > 64 || typeof was !== "object" || typeof now !== "object" || was === null || now === null)
return false;
const met = ledger.pairs.get(was);
if (met?.has(now) === true)
return true;
if (met === undefined)
ledger.pairs.set(was, new WeakSet([now]));
else
met.add(now);
try {
if (Array.isArray(was) !== Array.isArray(now))
return false;
const a = Reflect.ownKeys(was), b = Reflect.ownKeys(now);
if (a.length !== b.length)
return false;
ledger.steps += a.length + 1;
if (ledger.steps > COMPARE_STEPS) {
ledger.over = true;
return false;
}
for (const key of a) {
const x = Object.getOwnPropertyDescriptor(was, key), y = Object.getOwnPropertyDescriptor(now, key);
if (x === undefined || y === undefined || !("value" in x) || !("value" in y) || !sameMember(x.value, y.value, ledger, depth + 1))
return false;
}
return true;
}
catch {
return false;
}
}
const COMPARE_STEPS = 20_000;
function shown(v) {
if (v === undefined || typeof v === "boolean" || typeof v === "number")
return String(v);
if (typeof v === "string")
return JSON.stringify(v.length > 60 ? `${v.slice(0, 60)}…` : v);
try {
if (Array.isArray(v) && v.length <= 8 && v.every((x) => typeof x === "string"))
return `[${v.map((x) => shown(x)).join(", ")}]`;
}
catch {
}
return undefined;
}
function refuseProductDrift(face, rebound) {
const was = face;
const now = rebound;
const keys = new Set([...Reflect.ownKeys(face), ...faceKeys(rebound)]);
keys.delete("execute");
const driftProblems = [];
const ledger = { pairs: new WeakMap(), steps: 0, over: false };
for (const key of keys) {
let after;
try {
after = now[key];
}
catch {
driftProblems.push(`${String(key)} (unreadable)`);
continue;
}
if (sameMember(was[key], after, ledger))
continue;
const before = shown(was[key]), later = shown(after);
driftProblems.push(ledger.over ? `${String(key)} (declaration comparison over budget)` : before !== undefined && later !== undefined ? `${String(key)} (${before} → ${later})` : `${String(key)} (differs)`);
if (ledger.over)
break;
}
if (driftProblems.length === 0)
return;
const e = new Error(`defineTool product ${entryName(face)} changed after the run fixed its tool roster (before its first await): ${driftProblems.join(", ")}. The safety scan judged the product as it stood then, so a product that no longer matches is never mounted — not under its new name, not under the old one. Pass the final product before starting the run.`);
e.code = "config.tool_mount_denied";
throw e;
}
function mountedProduct(face, rebound) {
const members = {};
for (const key of Reflect.ownKeys(face))
members[key] = { value: face[key], enumerable: true };
members.execute = { value: rebound.execute.bind(rebound), enumerable: true };
return Object.freeze(Object.create(Object.prototype, members));
}
export function rebindProductEntry(entry, enrich) {
const product = PRODUCT_OF.get(entry);
if (product === undefined)
return undefined;
const rebound = rebindDefineToolCtx(product, enrich);
refuseProductDrift(entry, rebound);
return mountedProduct(entry, rebound);
}
export function snapshotCallerRoster(spec) {
const tools = spec.tools;
const descriptors = Object.getOwnPropertyDescriptors(spec);
const handed = Array.isArray(tools) ? Object.freeze([...tools]) : undefined;
const slot = handed !== undefined ? Object.freeze(handed.map((t, i) => rosterFaceOf(t, i))) : tools;
descriptors.tools = { value: slot, enumerable: slot !== undefined, writable: false, configurable: false };
const copy = Object.create(Object.getPrototypeOf(spec), descriptors);
return { spec: copy, roster: { caller: spec, handed: tools === undefined ? Object.freeze([]) : handed } };
}
export function refuseLateRosterChanges(roster) {
const { handed } = roster;
if (handed === undefined)
return;
const current = roster.caller.tools;
const now = current === undefined ? [] : Array.isArray(current) ? current : throwChanged([`spec.tools (reassigned to a value that is not an array)`]);
const changed = [];
for (let i = 0; i < Math.max(now.length, handed.length); i++) {
if (now[i] === handed[i])
continue;
if (i < now.length)
changed.push(`${entryName(now[i])} (${i < handed.length ? "replaced" : "added"})`);
else
changed.push(`${entryName(handed[i])} (removed)`);
}
if (changed.length > 0)
throwChanged(changed);
}
function throwChanged(changed) {
const e = new Error(`spec.tools changed after the run fixed its tool roster: ${changed.join(", ")}. The roster is the one the request carried when the run started (before its first await) — the scan that judges each tool's effect and marks and the mount read that one copy, so a later entry is never mounted. Pass the complete roster before starting the run.`);
e.code = "config.tool_mount_denied";
throw e;
}