@sema-agent/core
Version:
Stateless, task-oriented AI agent core
152 lines (151 loc) • 7.03 kB
JavaScript
import { createHash } from "node:crypto";
import { redactSecrets } from "../untrusted-egress.js";
import { inlineUntrusted } from "../untrusted-text.js";
import { thrownText } from "../thrown-value.js";
export const ANNOUNCE_ONCE_MAX_DIGESTS = 200;
const DROPPED_TOOL_MODEL_LANE = "mcp_dropped_tools";
export function announceDigest(lane, text) {
return createHash("sha256").update(lane).update("\n").update(text).digest("hex");
}
export function warningDigest(err) {
const e = err;
const code = typeof e?.code === "string" ? e.code : "";
const message = thrownText(err);
return announceDigest(code, redactSecrets(message));
}
export function droppedToolDigest(d) {
return announceDigest(DROPPED_TOOL_MODEL_LANE, `${d.server}\n${d.tool}\n${d.reason}`);
}
export function withAnnounceDigests(existing, add) {
const out = [...(existing ?? [])];
const seen = new Set(out);
for (const d of add) {
if (seen.has(d))
continue;
seen.add(d);
out.push(d);
}
return out.length > ANNOUNCE_ONCE_MAX_DIGESTS ? out.slice(out.length - ANNOUNCE_ONCE_MAX_DIGESTS) : out;
}
function sameFailure(a, b) {
return a.name === b.name && a.error === b.error;
}
export async function flushOwedAnnouncedSnapshot(prepared) {
if (!prepared.frameReceipts.listingSnapshotOwed)
return;
prepared.frameReceipts.listingSnapshotOwed = false;
if (!prepared.announcedSnapshotRecovered)
return;
await prepared.session.appendAnnouncedListing(prepared.announcedListingsRef).catch(() => undefined);
}
export async function loadAnnounceOnceLedger(session, checkpointMirror, onError) {
let recovered = true;
const entry = await session.getAnnouncedListing().catch(() => {
recovered = false;
return undefined;
});
const seed = checkpointMirror?.mcpFailed !== undefined || checkpointMirror?.advisories !== undefined ? checkpointMirror : entry;
const listingSrc = checkpointMirror?.agents !== undefined || checkpointMirror?.skills !== undefined || checkpointMirror?.models !== undefined ? checkpointMirror : entry;
let mcpFailed = (seed?.mcpFailed ?? []).map((f) => ({ ...f }));
const advisories = new Set(seed?.advisories ?? []);
let dirty = false;
return {
entry,
recovered,
onError: (err, ctx) => {
if (onError === undefined)
return;
const digest = warningDigest(err);
if (advisories.has(digest))
return;
onError(err, ctx);
advisories.add(digest);
dirty = true;
},
mcpFailuresToAnnounce: (statuses) => {
const current = statuses
.filter((s) => s.status === "failed")
.map((s) => ({ name: inlineUntrusted(s.name, 160), ...(s.error !== undefined ? { error: inlineUntrusted(s.error, 240) } : {}) }));
const carried = mcpFailed.filter((r) => current.some((c) => sameFailure(c, r)));
if (carried.length !== mcpFailed.length)
dirty = true;
mcpFailed = carried;
return current.filter((c) => !carried.some((r) => sameFailure(c, r)));
},
pruneDroppedTools: (dropped) => {
for (let i = dropped.length - 1; i >= 0; i--) {
if (advisories.has(droppedToolDigest(dropped[i])))
dropped.splice(i, 1);
}
},
arms: () => ({
...(listingSrc?.agents !== undefined ? { agents: [...listingSrc.agents] } : {}),
...(listingSrc?.skills !== undefined ? { skills: [...listingSrc.skills] } : {}),
...(listingSrc?.models !== undefined ? { models: [...listingSrc.models] } : {}),
...(mcpFailed.length > 0 ? { mcpFailed: mcpFailed.map((f) => ({ ...f })) } : {}),
...(advisories.size > 0 ? { advisories: withAnnounceDigests(undefined, [...advisories]) } : {}),
}),
settle: async (s, ref) => {
if (advisories.size > 0)
ref.advisories = withAnnounceDigests(undefined, [...advisories]);
if (!dirty || !recovered)
return;
await s
.appendAnnouncedListing({
...(ref.agents !== undefined ? { agents: ref.agents } : {}),
...(ref.skills !== undefined ? { skills: ref.skills } : {}),
...(ref.models !== undefined ? { models: ref.models } : {}),
...(ref.mcpFailed !== undefined && ref.mcpFailed.length > 0 ? { mcpFailed: ref.mcpFailed } : {}),
...(ref.advisories !== undefined && ref.advisories.length > 0 ? { advisories: ref.advisories } : {}),
})
.catch(() => undefined);
},
};
}
export function serializeAnnouncedListings(ref) {
const out = {
...(ref.agents !== undefined ? { agents: [...ref.agents] } : {}),
...(ref.skills !== undefined ? { skills: [...ref.skills] } : {}),
...(ref.models !== undefined ? { models: [...ref.models] } : {}),
...(ref.mcpFailed !== undefined ? { mcpFailed: ref.mcpFailed.map((f) => ({ ...f })) } : {}),
...(ref.advisories !== undefined ? { advisories: [...ref.advisories] } : {}),
};
return Object.keys(out).length > 0 ? out : undefined;
}
export const DATE_ANCHOR_CUSTOM_TYPE = "engine:date-anchor";
const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
function normalizeDateAnchor(v) {
if (typeof v !== "object" || v === null)
return undefined;
const { prefixDate, announcedDate, segment } = v;
if (typeof prefixDate !== "string" || !ISO_DATE.test(prefixDate) || typeof announcedDate !== "string" || !ISO_DATE.test(announcedDate))
return undefined;
if (segment !== null && (typeof segment !== "string" || segment.length > 200))
return undefined;
return { prefixDate, announcedDate, segment };
}
export async function readDateAnchor(session) {
let branch;
try {
branch = await session.getBranch();
}
catch {
return undefined;
}
let found;
let i = branch.length - 1;
for (; i >= 0 && branch[i].type !== "compaction"; i--) {
const e = branch[i];
if (found === undefined && e.type === "custom" && e.customType === DATE_ANCHOR_CUSTOM_TYPE)
found = normalizeDateAnchor(e.data);
}
const segment = i >= 0 ? branch[i].id : null;
return { current: found !== undefined && found.segment === segment ? { prefixDate: found.prefixDate, announcedDate: found.announcedDate } : undefined, segment };
}
export function owedDateAnchor(session, seat, state) {
const read = seat?.anchor;
if (read === undefined || state === undefined || read.current?.announcedDate === state.announcedDate)
return undefined;
const data = { prefixDate: seat.legDate, announcedDate: state.announcedDate, segment: read.segment };
return () => session.appendCustomEntry(DATE_ANCHOR_CUSTOM_TYPE, data).then(() => undefined, () => undefined);
}