@sema-agent/core
Version:
Stateless, task-oriented AI agent core
200 lines (199 loc) • 10.6 kB
JavaScript
import { types } from "node:util";
import { DECLARATION_PATH_RULES, declarationFaultOf, declarerWord, readToolAxes, receiveDeclaration, toolAxesRefusal } from "./mcp-server-spec.js";
import { isPlainRecord, receive } from "./mods/kernel/receive.js";
import { CONFIG_RECEIPT_MODE } from "./own-property-receipt.js";
import { isToolContentOrigin, TOOL_CONTENT_ORIGINS } from "./memory-engine/content-origin.js";
import { inlineUntrusted } from "./untrusted-text.js";
import { assertNoRetiredToolPolicyKeys } from "./retired-keys.js";
export const LOCKED_KEY_REGISTRY = {
mcp: { specFields: ["mcp"] },
toolPolicy: { specFields: ["toolPolicy", "basePolicyForResumeEdit"] },
compliancePosture: { specFields: [] },
retentionPolicy: { specFields: [] },
};
export const ENTER_AS_IS = (_seat, door) => door();
function codedError(code, message) {
const e = new Error(message);
e.code = code;
return e;
}
export function resolveLockedKeys(config) {
if (config === undefined)
return new Set();
const keys = new Set();
const listed = declaredLockKeys(config);
for (let index = 0; index < listed.length; index++) {
const key = listed[index];
if (!Object.hasOwn(LOCKED_KEY_REGISTRY, key)) {
throw lockDeclarationRefusal(`RunnerDeps.lockedConfig.keys[${index}]`, `is ${inlineUntrusted(JSON.stringify(key), 80)}, which is not a member of the locked-key registry (${Object.keys(LOCKED_KEY_REGISTRY).join(", ")})`);
}
keys.add(key);
}
return keys;
}
function declaredLockKeys(config) {
const received = receiveDeclaration(config);
if (!received.ok)
throw lockDeclarationRefusal(memberPath(received.fault.path, "RunnerDeps.lockedConfig"), `cannot be read: ${received.fault.problem}`);
const copy = received.value;
if (!isPlainRecord(copy))
throw lockDeclarationRefusal("RunnerDeps.lockedConfig", `is ${inlineUntrusted(describeDeclared(copy), 80)}, not an object with a \`keys\` list`);
const keys = Object.hasOwn(copy, "keys") ? copy["keys"] : undefined;
if (!Array.isArray(keys))
throw lockDeclarationRefusal("RunnerDeps.lockedConfig.keys", `is ${inlineUntrusted(describeDeclared(keys), 80)}, not a list of locked keys`);
return keys;
}
function lockDeclarationRefusal(member, problem) {
return codedError("config.locked_registry", `${member} ${problem}. A lock that does not parse must not half-arm — fix the deployment's lock declaration.`);
}
export function snapshotMcpEntries(entries) {
return entries.map((entry) => snapshotDeclaredEntry(entry, "mcp"));
}
function memberPath(path, base = "") {
let rendered = base;
for (const key of path) {
const name = typeof key === "string" && /^\d+$/.test(key) ? JSON.stringify(key) : String(key);
rendered += typeof key === "number" ? `[${key}]` : rendered === "" ? name : `.${name}`;
if (rendered.length > 120 + base.length)
break;
}
return rendered;
}
function unreadableDeclaration(at, declarer, member, problem) {
const refusal = codedError("config.invalid_argument", `${declarerWord(at)}${declarer === undefined ? "" : ` ${declarerNameOf(declarer)}`} declares ${inlineUntrusted(member, 80)} the engine cannot read: ${problem}. A declaration the engine cannot read is refused ` +
`rather than partly applied — what the caller wrote and what the engine runs on would otherwise differ without anyone being told.`);
if (declarer !== undefined)
Object.defineProperty(refusal, "declarer", { value: declarer, enumerable: true, writable: false, configurable: false });
return refusal;
}
function ownDataString(holder, key) {
const slot = Object.getOwnPropertyDescriptor(holder, key);
const value = slot !== undefined && Object.hasOwn(slot, "value") ? slot.value : undefined;
return typeof value === "string" ? value : undefined;
}
export function refusedDeclarer(refusal) {
return typeof refusal === "object" && refusal !== null && !types.isProxy(refusal) ? ownDataString(refusal, "declarer") : undefined;
}
function capturedName(entry) {
return typeof entry === "object" && entry !== null && !types.isProxy(entry) ? ownDataString(entry, "name") : undefined;
}
function entryFaultRefusal(at, declarer, path, problem) {
return unreadableDeclaration(at, declarer, path.length === 0 ? "an entry" : memberPath(path), problem);
}
export function snapshotDeclaredEntry(entry, at) {
const declared = entry;
const declarer = capturedName(declared);
const received = receiveDeclaration(declared, DECLARATION_PATH_RULES[at]);
if (!received.ok)
throw entryFaultRefusal(at, declarer, received.fault.path, received.fault.problem);
if (!isPlainRecord(received.value))
throw unreadableDeclaration(at, declarer, "an entry", `it is ${inlineUntrusted(describeDeclared(received.value), 80)}, not an object`);
return received.value;
}
function describeDeclared(value) {
try {
return JSON.stringify(value) ?? String(value);
}
catch {
try {
return String(value);
}
catch {
return `[unrepresentable ${typeof value}]`;
}
}
}
const declarerNameOf = (name) => inlineUntrusted(typeof name === "string" ? JSON.stringify(name) : describeDeclared(name), 80);
export function judgeEntryToolAxes(copy, at) {
const { toolAxes: declaredAxes, ...rest } = copy;
const axes = readToolAxes(declaredAxes, at);
if (!axes.ok)
return { ok: false, error: toolAxesRefusal(at, declarerNameOf(copy.name), axes.problem) };
if (axes.toolAxes !== undefined) {
for (const perTool of Object.values(axes.toolAxes))
Object.freeze(perTool);
Object.freeze(axes.toolAxes);
}
return { ok: true, entry: Object.freeze((axes.toolAxes === undefined ? rest : { ...rest, toolAxes: axes.toolAxes })) };
}
function declaredEntriesOf(declared, at) {
const field = at;
const received = receive(declared, CONFIG_RECEIPT_MODE);
let problem;
if (received.ok) {
const list = received.value;
if (Array.isArray(list) && list !== declared)
return Array.from({ length: list.length }, (_, index) => list[index]);
problem = typeof list === "object" && list !== null ? "it is not an array" : `it is ${inlineUntrusted(describeDeclared(list), 80)}`;
}
else {
const fault = declarationFaultOf(received.fault);
if (fault.path.length > 0)
throw unreadableDeclaration(at, undefined, "an entry", fault.problem);
problem = fault.problem;
}
throw codedError("config.invalid_argument", `TaskSpec.${field} must be a readable array of ${declarerWord(at)} declarations; ${problem}. A list the engine cannot read ` +
`is refused rather than read as "no entries" — the declared ${declarerWord(at)}s would otherwise be dropped without anyone being told.`);
}
function snapshotAndValidateA2aEntries(declared) {
const entries = declaredEntriesOf(declared, "a2a");
if (entries.length === 0)
return undefined;
return entries.map((entry) => {
const judged = judgeEntryToolAxes(snapshotDeclaredEntry(entry, "a2a"), "a2a");
if (!judged.ok)
throw judged.error;
return judged.entry;
});
}
function snapshotAndValidateMcpEntries(declared) {
return snapshotMcpEntries(declaredEntriesOf(declared, "mcp")).map((copy) => {
const declared = copy.contentOrigin;
if (declared !== undefined && !isToolContentOrigin(declared)) {
throw codedError("config.mcp_content_class", `MCP server ${declarerNameOf(copy.name)} declares contentOrigin ` +
`${inlineUntrusted(describeDeclared(declared), 80)}, which is not one of ` +
`${TOOL_CONTENT_ORIGINS.map((v) => `"${v}"`).join(" | ")}. The declaration decides whether this server's tools ` +
`mark the session's memory externally exposed — a value this engine cannot read is refused at the door rather ` +
`than folded to a class, because folding it would let a deployment believe a declaration is in force while every ` +
`call keeps writing a one-way durable mark.`);
}
const judged = judgeEntryToolAxes(copy, "mcp");
if (!judged.ok)
throw judged.error;
return judged.entry;
});
}
export function preflightLockedConfig(spec, deps, enter = ENTER_AS_IS) {
const lockedKeys = enter("deps", () => resolveLockedKeys(deps.lockedConfig));
enter("taskConfig", () => {
for (const key of lockedKeys) {
for (const field of LOCKED_KEY_REGISTRY[key].specFields) {
if (spec[field] !== undefined) {
throw codedError("config.locked_key", `TaskSpec.${field} is administratively locked by this deployment (locked key "${key}") — ` +
`the task-supplied value is refused wholesale (locks are config-time; there is no request-time ` +
`unlock). Remove the field from the task, or change the deployment's lock configuration.`);
}
}
}
});
const toolPolicyLocked = lockedKeys.has("toolPolicy");
const task = toolPolicyLocked ? {} : { toolPolicy: spec.toolPolicy, basePolicyForResumeEdit: spec.basePolicyForResumeEdit };
const policies = callerPolicySeats(task, deps, toolPolicyLocked);
enter(!toolPolicyLocked && task.toolPolicy != null ? "taskConfig" : "deps", () => assertNoRetiredToolPolicyKeys(policies.toolPolicy));
enter(!toolPolicyLocked && task.basePolicyForResumeEdit != null ? "taskConfig" : "deps", () => assertNoRetiredToolPolicyKeys(policies.basePolicyForResumeEdit));
const declaredServers = lockedKeys.has("mcp") ? undefined : spec.mcp;
const mcp = declaredServers === undefined ? undefined : enter("taskConfig", () => snapshotAndValidateMcpEntries(declaredServers));
const declaredPeers = spec.a2a;
return {
lockedKeys,
mcp,
a2a: declaredPeers === undefined ? undefined : enter("taskConfig", () => snapshotAndValidateA2aEntries(declaredPeers)),
...policies,
};
}
export function callerPolicySeats(spec, deps, toolPolicyLocked) {
return {
toolPolicy: toolPolicyLocked ? deps.toolPolicy : (spec.toolPolicy ?? deps.toolPolicy),
basePolicyForResumeEdit: toolPolicyLocked ? deps.basePolicyForResumeEdit : (spec.basePolicyForResumeEdit ?? deps.basePolicyForResumeEdit),
};
}