@sema-agent/core
Version:
Stateless, task-oriented AI agent core
150 lines (149 loc) • 6.95 kB
JavaScript
import { callerToolNameProblem } from "../caller-tool-name.js";
import { engineCardTypes, handBandEffects } from "../tool-registry.js";
import { callerCardIdOf, offendingResultCard, rosterMemberBoundProblem, schemaKeyBoundProblem, survivorsByName } from "../tool-roster.js";
import { toolFaceProblem } from "./tool-face-overlay.js";
import { OFFLOAD_TOOL_NAME } from "../tool-result-store.js";
import { PRESENT_PLAN_TOOL_NAME, ENTER_PLAN_MODE_TOOL_NAME } from "../present-plan-tool.js";
const REPEAT_RISK = { read: 0, idempotent: 1, write: 2 };
const leastRepeatSafe = (a, b) => (REPEAT_RISK[a] >= REPEAT_RISK[b] ? a : b);
const tierOf = (t) => t.irreversibility ?? (t.reversibilityProbe ? "maybe" : undefined);
export function prepareSafetyScan(input) {
const { spec, deps } = input;
const toolEffects = new Map();
const survivors = survivorsByName(spec.tools ?? []);
const claimedAliases = new Set();
const egressTools = new Set();
const irreversibleTools = new Set();
const irreversibilityTier = new Map();
const axisExplicitNegatives = new Map();
const reversibilityProbes = new Map();
const toolMarkSources = new Map();
const markedBy = (name, source) => {
const set = toolMarkSources.get(name);
if (set !== undefined)
set.add(source);
else
toolMarkSources.set(name, new Set([source]));
};
for (const t of spec.tools ?? []) {
{
const bound = rosterMemberBoundProblem({
name: t.name,
aliases: t.aliases,
...(t.contract !== undefined ? { contractId: t.contract.contractId, implementationRevision: t.contract.implementationRevision } : {}),
...(typeof t.modelGate === "string" ? { modelGate: t.modelGate } : {}),
});
if (bound !== undefined) {
const e = new Error(`A caller tool cannot ride a roster row: ${bound.message}.`);
e.code = bound.code;
throw e;
}
}
{
const keyProblem = schemaKeyBoundProblem(t.parameters);
if (keyProblem !== undefined) {
const e = new Error(`Tool "${t.name}" cannot ride a roster row: ${keyProblem}.`);
e.code = "config.tool_schema_bound";
throw e;
}
const declared = {
...(t.family !== undefined ? { family: t.family } : {}),
...(t.pathTarget !== undefined ? { pathTarget: t.pathTarget } : {}),
...(t.ruleFace !== undefined ? { ruleFace: t.ruleFace } : {}),
...(t.renderHints !== undefined ? { renderHints: t.renderHints } : {}),
};
const faceProblem = toolFaceProblem(declared, t.parameters, { toolName: t.name });
if (faceProblem !== undefined) {
const e = new Error(`Tool "${t.name}" declares a face its schema cannot carry: ${faceProblem}.`);
e.code = "config.tool_face_invalid";
throw e;
}
const foreignCard = offendingResultCard(t.renderHints, callerCardIdOf(t), engineCardTypes());
if (foreignCard !== undefined) {
const e = new Error(`Tool "${t.name}" declares a result card outside its own namespace: ${JSON.stringify(foreignCard)} (a caller card is "${callerCardIdOf(t)}/<card>"; engine cards and other contracts' namespaces are reserved).`);
e.code = "config.tool_face_invalid";
throw e;
}
}
for (const [what, n] of [["name", t.name], ...(t.aliases ?? []).map((a) => ["alias", a])]) {
const problem = callerToolNameProblem(n);
if (problem === undefined)
continue;
const e = new Error(`Tool ${what} ${JSON.stringify(n)}${what === "alias" ? ` (of ${JSON.stringify(t.name)})` : ""} is invalid: ${problem}.`);
e.code = "config.tool_name_invalid";
throw e;
}
if (t.effect && survivors.get(t.name) === t) {
toolEffects.set(t.name, t.effect);
}
const tier = tierOf(t);
if (tier !== undefined) {
irreversibilityTier.set(t.name, tier);
if (tier === "always" || tier === "maybe") {
irreversibleTools.add(t.name);
markedBy(t.name, "deployment");
}
}
if (t.reversibilityProbe)
reversibilityProbes.set(t.name, t.reversibilityProbe);
if (t.egress) {
if (t.effect !== undefined && t.effect !== "write") {
const e = new Error(`Tool "${t.name}" declares egress:true with effect:"${t.effect}" — an egress tool (external write) must have effect:"write" (or omit effect; write is the default).`);
e.code = "config.egress_requires_write_effect";
throw e;
}
egressTools.add(t.name);
markedBy(t.name, "deployment");
}
}
for (const t of survivors.values()) {
const tier = tierOf(t);
for (const alias of t.aliases ?? []) {
if (alias === t.name || survivors.has(alias))
continue;
if (claimedAliases.has(alias))
continue;
claimedAliases.add(alias);
if (t.effect)
toolEffects.set(alias, t.effect);
if (tier !== undefined) {
irreversibilityTier.set(alias, tier);
if (tier === "always" || tier === "maybe") {
irreversibleTools.add(alias);
markedBy(alias, "deployment");
}
}
if (t.reversibilityProbe)
reversibilityProbes.set(alias, t.reversibilityProbe);
if (t.egress) {
egressTools.add(alias);
markedBy(alias, "deployment");
}
}
}
toolEffects.set(OFFLOAD_TOOL_NAME, "read");
const claimerEffects = new Map([...claimedAliases, ...survivors.keys()].map((a) => [a, toolEffects.get(a) ?? "write"]));
const recordHandEffect = (name, effect) => {
const claimed = claimerEffects.get(name);
toolEffects.set(name, claimed === undefined ? effect : leastRepeatSafe(claimed, effect));
};
if (deps.executionEnvFactory || deps.executionEnv) {
for (const [name, effect] of handBandEffects())
recordHandEffect(name, effect);
if (spec.handsReadOnly === true)
recordHandEffect("Bash", "read");
}
if (spec.enablePlanMode === true) {
toolEffects.set(PRESENT_PLAN_TOOL_NAME, "read");
toolEffects.set(ENTER_PLAN_MODE_TOOL_NAME, "read");
}
return {
toolEffects,
egressTools,
irreversibleTools,
irreversibilityTier,
axisExplicitNegatives,
reversibilityProbes,
toolMarkSources,
};
}