@sema-agent/core
Version:
Stateless, task-oriented AI agent core
222 lines (221 loc) • 11.5 kB
JavaScript
import { createHash } from "node:crypto";
import { isBandId } from "./bands.js";
import { SEMA_DEFAULT_CATALOG } from "./catalog/index.js";
import { OVERLAY_OPS, PROMPT_TRAITS, isWordOf } from "./catalog/schema.js";
import { canonical } from "./epoch.js";
import { codePointsOver, sectionOfMessage, validateOverlay } from "./overlay.js";
export const ARTIFACT_LIMITS = Object.freeze({
maxOverlays: 64,
maxOverlayTextBytes: 32_000,
maxTotalTextBytes: 131_072,
maxIntentChars: 2_000,
maxRefs: 32,
maxRefChars: 500,
maxSourceRevisionChars: 500,
});
const DIGEST_RE = /^sha256:[0-9a-f]{64}$/;
const MESSAGE_IDS = new Set(SEMA_DEFAULT_CATALOG.messages.map((m) => m.id));
const SECTION_IDS = new Set(SEMA_DEFAULT_CATALOG.sections.map((s) => s.id));
function sha256Hex(text) {
return `sha256:${createHash("sha256").update(text, "utf8").digest("hex")}`;
}
export function computeArtifactDigest(payload) {
return sha256Hex(canonical(payload));
}
export function requiresOf(overlays) {
const targets = new Map();
const need = (id, kind) => {
targets.set(`${kind}:${id}`, { id, kind });
};
const ops = new Set();
const traits = new Set();
const bindings = new Set();
for (const o of overlays) {
ops.add(o.op);
need(o.target, o.op === "add" ? "band" : o.op === "drop" ? "section" : "message");
if (o.message !== undefined)
need(o.message, "message");
for (const t of [...(o.when?.traits?.allOf ?? []), ...(o.when?.traits?.noneOf ?? [])])
traits.add(t);
for (const b of [...(o.when?.binding?.anyOf ?? []), ...(o.when?.binding?.noneOf ?? [])])
bindings.add(b);
}
const byText = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
return {
targets: [...targets.values()].sort((a, b) => byText(`${a.kind}:${a.id}`, `${b.kind}:${b.id}`)),
ops: OVERLAY_OPS.filter((op) => ops.has(op)),
traits: PROMPT_TRAITS.filter((t) => traits.has(t)),
bindings: [...bindings].sort(byText),
};
}
export function unmetRequirements(requires) {
const unmet = [];
for (const t of requires.targets) {
const has = t.kind === "message" ? MESSAGE_IDS.has(t.id) : t.kind === "section" ? SECTION_IDS.has(t.id) : isBandId(t.id);
if (!has)
unmet.push(`${t.kind} "${t.id}"`);
}
for (const op of requires.ops)
if (!isWordOf(OVERLAY_OPS, op))
unmet.push(`op "${String(op)}"`);
for (const tr of requires.traits)
if (!isWordOf(PROMPT_TRAITS, tr))
unmet.push(`trait "${String(tr)}"`);
return unmet;
}
function isRecord(v) {
return v !== null && typeof v === "object" && !Array.isArray(v);
}
function requiresShaped(v) {
if (!isRecord(v))
return false;
const strings = (a) => Array.isArray(a) && a.every((s) => typeof s === "string");
return (Object.keys(v).every((k) => k === "targets" || k === "ops" || k === "traits" || k === "bindings") &&
Array.isArray(v.targets) &&
v.targets.every((t) => isRecord(t) && typeof t.id === "string" && (t.kind === "message" || t.kind === "section" || t.kind === "band") && Object.keys(t).every((k) => k === "id" || k === "kind")) &&
strings(v.ops) &&
strings(v.traits) &&
strings(v.bindings));
}
const bindingListOf = (b) => {
if (b === undefined)
return undefined;
const names = b.anyOf !== undefined ? b.anyOf : b.noneOf;
return { anyOf: b.anyOf !== undefined, names, set: new Set(names) };
};
function bindingsMeet(a, b) {
if (a === undefined || b === undefined)
return true;
if (a.anyOf && b.anyOf)
return a.names.some((x) => b.set.has(x));
if (a.anyOf)
return a.names.some((x) => !b.set.has(x));
if (b.anyOf)
return b.names.some((x) => !a.set.has(x));
return true;
}
function replaceConflict(overlays) {
const groups = new Map();
for (const o of overlays) {
if (o.op !== "replace")
continue;
const { binding, ...rest } = o.when ?? {};
const key = canonical({ target: o.target, when: rest });
groups.set(key, [...(groups.get(key) ?? []), { o, list: bindingListOf(binding) }]);
}
for (const group of groups.values()) {
for (let i = 0; i < group.length; i++) {
for (let j = i + 1; j < group.length; j++) {
const [{ o: p, list: pl }, { o: q, list: ql }] = [group[i], group[j]];
if (bindingsMeet(pl, ql)) {
return `overlays "${p.id}" and "${q.id}" both replace "${p.target}" under one condition that can hold in the same run (write exclusive binding conditions: "noneOf": [x] beside "anyOf": [x])`;
}
}
}
}
return undefined;
}
export function verifyPromptArtifact(raw) {
if (!isRecord(raw))
return { ok: false, code: "shape", reason: "the published artifact is not an object" };
if (typeof raw.sourceRevision !== "string" || raw.sourceRevision.length === 0 || codePointsOver(raw.sourceRevision, ARTIFACT_LIMITS.maxSourceRevisionChars)) {
return { ok: false, code: "shape", reason: `sourceRevision missing or over ${ARTIFACT_LIMITS.maxSourceRevisionChars} characters (code points)` };
}
const v = verifyPromptArtifactBody(raw);
return v.ok ? { ok: true, artifact: { ...v.artifact, sourceRevision: raw.sourceRevision } } : v;
}
export function verifyPromptArtifactBody(raw) {
const fail = (code, reason) => ({ ok: false, code, reason });
if (!isRecord(raw))
return fail("shape", "the artifact is not an object");
for (const k of Object.keys(raw))
if (k !== "payload" && k !== "artifactDigest" && k !== "sourceRevision")
return fail("shape", `the artifact carries ${JSON.stringify(k).slice(0, 60)}`);
if (typeof raw.artifactDigest !== "string" || !DIGEST_RE.test(raw.artifactDigest))
return fail("shape", "artifactDigest malformed");
const payload = raw.payload;
if (!isRecord(payload))
return fail("shape", "payload is not an object");
if (payload.schemaVersion !== 3) {
const republish = payload.schemaVersion === 2 ? `; republish under schemaVersion 3 — an overlay's "scope": { "binding": b } becomes "when": { "binding": { "anyOf": [b] } }` : "";
return fail("schema", `unsupported schemaVersion ${JSON.stringify(payload.schemaVersion)?.slice(0, 40)} (this engine reads schemaVersion 3 only${republish})`);
}
for (const k of Object.keys(payload))
if (k !== "schemaVersion" && k !== "requires" && k !== "overlays" && k !== "evidence")
return fail("shape", `payload carries ${JSON.stringify(k).slice(0, 60)}`);
if (!Array.isArray(payload.overlays))
return fail("shape", "overlays is not an array");
if (payload.overlays.length === 0)
return fail("shape", "overlays is empty — publish at least one overlay, or disable the source instead");
if (payload.overlays.length > ARTIFACT_LIMITS.maxOverlays)
return fail("limits", `${payload.overlays.length} overlays > max ${ARTIFACT_LIMITS.maxOverlays}`);
const requires = payload.requires;
if (!requiresShaped(requires))
return fail("shape", "requires is not { targets: { id, kind }[], ops: string[], traits: string[], bindings: string[] }");
const unmet = unmetRequirements(requires);
if (unmet.length > 0)
return fail("requires", `this engine does not meet the artifact's requirements: ${unmet.slice(0, 6).join(", ")}${unmet.length > 6 ? ` and ${unmet.length - 6} more` : ""}`);
const overlays = [];
let totalBytes = 0;
for (const rawOverlay of payload.overlays) {
const v = validateOverlay(rawOverlay, { source: "center" });
if ("violation" in v)
return fail("overlay", v.violation);
const text = v.overlay.text;
if (text !== undefined) {
const bytes = Buffer.byteLength(text, "utf8");
if (bytes > ARTIFACT_LIMITS.maxOverlayTextBytes)
return fail("limits", `overlay "${v.overlay.id}" text ${bytes} bytes > max ${ARTIFACT_LIMITS.maxOverlayTextBytes}`);
totalBytes += bytes;
}
overlays.push(v.overlay);
}
if (totalBytes > ARTIFACT_LIMITS.maxTotalTextBytes)
return fail("limits", `overlay text ${totalBytes} bytes > max ${ARTIFACT_LIMITS.maxTotalTextBytes}`);
if (canonical(requiresOf(overlays)) !== canonical(requires))
return fail("requires", "requires differs from what the overlays require (build the artifact with buildPromptArtifact)");
const conflict = replaceConflict(overlays);
if (conflict !== undefined)
return fail("overlay", conflict);
const evidence = payload.evidence;
if (!isRecord(evidence) || typeof evidence.intent !== "string" || evidence.intent.trim() === "" || codePointsOver(evidence.intent, ARTIFACT_LIMITS.maxIntentChars)) {
return fail("evidence", `evidence.intent is a non-blank string of at most ${ARTIFACT_LIMITS.maxIntentChars} characters (code points)`);
}
if (!Array.isArray(evidence.refs) || evidence.refs.length > ARTIFACT_LIMITS.maxRefs || evidence.refs.some((r) => typeof r !== "string" || r.trim() === "" || codePointsOver(r, ARTIFACT_LIMITS.maxRefChars))) {
return fail("evidence", `evidence.refs is a list of at most ${ARTIFACT_LIMITS.maxRefs} non-blank references of at most ${ARTIFACT_LIMITS.maxRefChars} characters (code points) each`);
}
for (const k of Object.keys(evidence))
if (k !== "intent" && k !== "refs")
return fail("shape", `evidence carries ${JSON.stringify(k).slice(0, 60)}`);
const behaviorReplace = overlays.find((o) => o.op === "replace" && sectionOfMessage(o.target)?.band === "core-behavior");
if (behaviorReplace !== undefined && evidence.refs.length === 0) {
return fail("evidence", `overlay "${behaviorReplace.id}" replaces core-behavior text; evidence.refs names at least one evaluation or golden`);
}
const normalized = {
schemaVersion: 3,
requires: requiresOf(overlays),
overlays,
evidence: { intent: evidence.intent, refs: [...evidence.refs] },
};
if (computeArtifactDigest(normalized) !== raw.artifactDigest)
return fail("digest", "artifactDigest does not match canonical(payload)");
return { ok: true, artifact: { payload: normalized, artifactDigest: raw.artifactDigest } };
}
const CENTER_ORIGIN = Object.freeze({ source: "center", by: "center" });
export function centerOverlaysOf(adoption) {
if (adoption === undefined)
return [];
return adoption.artifact.payload.overlays.map((overlay) => ({ overlay, origin: CENTER_ORIGIN }));
}
export function adoptCenter(artifact, sourceRevision) {
const v = verifyPromptArtifactBody(artifact);
return v.ok ? { artifact: v.artifact, ...(sourceRevision !== undefined ? { sourceRevision } : {}) } : undefined;
}
export function buildPromptArtifact(opts) {
const overlays = opts.overlays.map((o) => {
const v = validateOverlay(o, { source: "center" });
return "overlay" in v ? v.overlay : o;
});
const payload = JSON.parse(JSON.stringify({ schemaVersion: 3, requires: requiresOf(overlays), overlays, evidence: opts.evidence }));
return verifyPromptArtifact({ payload, artifactDigest: sha256Hex(canonical(payload)), sourceRevision: opts.sourceRevision });
}