UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

152 lines (151 loc) • 7.03 kB
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); }