UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

340 lines (339 loc) • 16.1 kB
import { types } from "node:util"; import { describeFault, isPlainRecord, receive } from "../core/mods/kernel/receive.js"; import { egressText } from "../core/untrusted-egress.js"; import { bandOf, isBandId } from "./bands.js"; import { SEMA_DEFAULT_CATALOG, catalogMessage } from "./catalog/index.js"; import { HOST_FORMS, HOST_ROLES, OVERLAY_OPS, PROMPT_FACT_NAMES, PROMPT_TRAITS, isWordOf, } from "./catalog/schema.js"; import { RENDERERS } from "./renderers.js"; import { canonicalWhen } from "./select.js"; export const OVERLAY_SOURCE_ORDER = Object.freeze(["bundled", "task", "center", "deploy"]); export const ABSENT_THIS_RUN = "(absent this run)"; const SECTION_BY_ID = new Map(SEMA_DEFAULT_CATALOG.sections.map((s) => [s.id, s])); const MESSAGE_TARGETS = (() => { const m = new Map(); for (const s of SEMA_DEFAULT_CATALOG.sections) { const ids = "message" in s.body ? [s.body.message] : "lines" in s.body ? s.body.lines.map((l) => l.message) : (RENDERERS[s.body.renderer].messages ?? []); for (const id of ids) m.set(id, { section: s }); } return m; })(); const MESSAGE_IDS = new Set(SEMA_DEFAULT_CATALOG.messages.map((m) => m.id)); export function sectionOfMessage(id) { return MESSAGE_TARGETS.get(id)?.section; } const LOCKED = Object.freeze(SEMA_DEFAULT_CATALOG.sections .filter((s) => s.band === "constitution" && !("renderer" in s.body)) .flatMap((s) => ("message" in s.body ? [s.body.message] : "lines" in s.body ? s.body.lines.map((l) => l.message) : [])) .flatMap((id) => catalogMessage(id).variants.map((v) => Object.freeze({ message: id, variant: v.id, text: v.text })))); export function lockedEntries() { return LOCKED; } export function lockedTextIn(text) { const hits = []; for (const e of LOCKED) if (text.includes(e.text)) hits.push(`${e.message}#${e.variant}`); return hits; } export function assembledRoleRefused(field, hits) { const named = [...new Set(hits.map((h) => h.split("#")[0]))]; return Object.assign(new Error(`prompt.assembled_role_refused: ${field} contains, verbatim, the locked text of ${named.slice(0, 4).join(", ")}${named.length > 4 ? ` and ${named.length - 4} more` : ""} — the engine composes the locked sections itself; supply only the role layer (an already-assembled prompt is refused, never passed through)`), { code: "prompt.assembled_role_refused" }); } const ID_GRAMMAR = /^[a-z0-9][a-z0-9/._-]*$/; const OVERLAY_KEYS = new Set(["id", "op", "target", "text", "message", "when"]); const OPS = new Set(OVERLAY_OPS); const PROMPT_DATA = Object.freeze({ accessors: "refuse", depth: 4, others: "refuse", freeze: true, isProxy: (v) => types.isProxy(v) }); export function receivePromptData(raw) { const r = receive(raw, PROMPT_DATA); return r.ok ? { value: r.value } : { notData: describeFault(r.fault) }; } export const PROMPT_BINDING_MAX_CHARS = 128; export const PROMPT_BINDING_LIST_MAX = 64; export function codePointsOver(s, max) { if (s.length <= max) return false; if (s.length > 2 * max) return true; let n = 0; for (const _ of s) if (++n > max) return true; return false; } function bindingProblem(v) { return typeof v !== "string" || v.trim() === "" || codePointsOver(v, PROMPT_BINDING_MAX_CHARS) ? `${str(v)} is not a non-blank string of at most ${PROMPT_BINDING_MAX_CHARS} characters (code points)` : undefined; } function bindingConditionProblem(b) { if (!isPlainRecord(b)) return `"when.binding" is ${Array.isArray(b) ? "an array" : str(b)}, not { anyOf: [...] } or { noneOf: [...] }`; const keys = Object.keys(b); const k = keys[0]; if (keys.length !== 1 || (k !== "anyOf" && k !== "noneOf")) return `"when.binding" carries ${keys.length === 0 ? "no list" : keys.map((x) => str(x)).join(", ")} (exactly one of anyOf, noneOf)`; const list = b[k]; if (!Array.isArray(list) || list.length === 0) return `"when.binding.${k}" is ${Array.isArray(list) ? "empty" : str(list)}, not a list naming at least one binding`; if (list.length > PROMPT_BINDING_LIST_MAX) return `"when.binding.${k}" names ${list.length} bindings (at most ${PROMPT_BINDING_LIST_MAX})`; for (const name of list) { const why = bindingProblem(name); if (why !== undefined) return `"when.binding.${k}": ${why}`; } if (new Set(list).size !== list.length) return `"when.binding.${k}" names a binding twice`; return undefined; } export function resolvePromptBinding(task) { if (task.promptBinding === undefined) return undefined; const why = bindingProblem(task.promptBinding); if (why !== undefined) throw Object.assign(new Error(`TaskSpec.promptBinding ${why} (or absent)`), { code: "config.prompt_selector_invalid" }); return task.promptBinding; } function str(v, max = 160) { if (typeof v !== "string") return typeof v; const e = egressText("same-principal", v); return JSON.stringify(e.length > max ? `${e.slice(0, max)}…` : e); } function whenViolation(when) { if (typeof when !== "object" || when === null || Array.isArray(when)) return `"when" is ${str(when)}, not an object`; for (const k of Object.keys(when)) if (k !== "traits" && k !== "host" && k !== "facts" && k !== "binding") return `"when" carries ${str(k)} (traits, host, facts, binding only)`; const w = when; if (w["traits"] !== undefined) { const t = w["traits"]; if (typeof t !== "object" || t === null || Array.isArray(t)) return `"when.traits" is not an object`; for (const [k, list] of Object.entries(t)) { if (k !== "allOf" && k !== "noneOf") return `"when.traits" carries ${str(k)} (allOf, noneOf only)`; if (!Array.isArray(list)) return `"when.traits.${k}" is not an array`; for (const name of list) if (!isWordOf(PROMPT_TRAITS, name)) return `"when.traits.${k}" names ${str(name)}, not a trait (${PROMPT_TRAITS.join(", ")})`; } } if (w["host"] !== undefined) { const h = w["host"]; if (typeof h !== "object" || h === null || Array.isArray(h)) return `"when.host" is not an object`; for (const [k, v] of Object.entries(h)) { if (k === "role" ? !isWordOf(HOST_ROLES, v) : k === "form" ? !isWordOf(HOST_FORMS, v) : true) { return `"when.host" carries ${str(k)} = ${str(v)} (role: ${HOST_ROLES.join(" | ")}; form: ${HOST_FORMS.join(" | ")})`; } } } if (w["facts"] !== undefined) { const f = w["facts"]; if (typeof f !== "object" || f === null || Array.isArray(f)) return `"when.facts" is not an object`; for (const [k, v] of Object.entries(f)) { if (!isWordOf(PROMPT_FACT_NAMES, k)) return `"when.facts" names ${str(k)}, not a fact`; if (typeof v !== "boolean") return `"when.facts.${k}" is ${str(v)}, not a boolean`; } } if (w["binding"] !== undefined) return bindingConditionProblem(w["binding"]); return undefined; } function textsOf(o) { if (o.text !== undefined) return [o.text]; if (o.message !== undefined && MESSAGE_IDS.has(o.message)) return catalogMessage(o.message).variants.map((v) => v.text); return []; } export function validateOverlay(raw, ctx) { const fail = (why) => ({ violation: why }); const received = receivePromptData(raw); if ("notData" in received) return fail(`an overlay is data (plain objects, arrays, strings, booleans); it holds ${received.notData}`); const r = received.value; if (!isPlainRecord(r)) return fail(`an overlay is an object, got ${Array.isArray(r) ? "an array" : str(r)}`); for (const k of Object.keys(r)) { if (OVERLAY_KEYS.has(k)) continue; const rewrite = k === "scope" ? `; an overlay names its binding as "when": { "binding": { "anyOf": [<binding>] } }` : ""; return fail(`an overlay carries ${str(k)} (id, op, target, text, message, when only)${rewrite}`); } const { id, op, target, text, message, when } = r; if (typeof id !== "string" || id.length === 0 || codePointsOver(id, 128) || !ID_GRAMMAR.test(id)) return fail(`overlay id ${str(id)} is not ^[a-z0-9][a-z0-9/._-]*$ of at most 128 characters`); if (id.startsWith("core/") && ctx.sugar !== true) return fail(`overlay id ${str(id)}: the core/ namespace is the engine's own`); if (typeof op !== "string" || !OPS.has(op)) return fail(`overlay "${id}": op ${str(op)} is not one of ${OVERLAY_OPS.join(", ")}`); if (typeof target !== "string" || target.length === 0) return fail(`overlay "${id}": target ${str(target)} is not an id`); const carries = op !== "drop"; if (!carries && (text !== undefined || message !== undefined)) return fail(`overlay "${id}": a drop carries no text or message`); if (carries && (text === undefined) === (message === undefined)) return fail(`overlay "${id}": a ${op} carries exactly one of text, message`); if (text !== undefined && (typeof text !== "string" || text.trim() === "")) return fail(`overlay "${id}": text is ${typeof text === "string" ? "blank" : str(text)}`); if (message !== undefined && (typeof message !== "string" || !MESSAGE_IDS.has(message))) return fail(`overlay "${id}": message ${str(message)} is not a catalog message`); if (when !== undefined) { const why = whenViolation(when); if (why !== undefined) return fail(`overlay "${id}": ${why}`); } let band; let where; if (op === "add") { if (!isBandId(target)) return fail(`overlay "${id}": an add targets a band; ${str(target)} is not one`); if (bandOf(target).addRank === undefined) return fail(`overlay "${id}": band "${target}" takes no additions`); if (SECTION_BY_ID.has(id) || MESSAGE_IDS.has(id)) return fail(`overlay "${id}": an added entry cannot take a catalog id`); band = target; where = `band "${target}"`; } else if (op === "drop") { const section = SECTION_BY_ID.get(target); const added = ctx.lowerAdds?.get(target); if (section !== undefined) band = section.band; else if (added !== undefined) band = added; else return fail(`overlay "${id}": drop target ${str(target)} is neither a catalog section nor an entry a lower source added`); where = `"${target}" (band "${band}")`; } else { const placed = MESSAGE_TARGETS.get(target); if (placed === undefined) return fail(`overlay "${id}": ${op} target ${str(target)} is not a message the catalog places`); band = placed.section.band; where = `"${target}" (band "${band}")`; } const row = bandOf(band); if (!row.ops[ctx.source].includes(op)) { return fail(`overlay "${id}": a ${ctx.source} overlay may not ${op} ${where} (the band grants ${ctx.source} ${row.ops[ctx.source].length > 0 ? row.ops[ctx.source].join(", ") : "nothing"})`); } if (ctx.event !== undefined && row.modsEvent !== ctx.event) return fail(`overlay "${id}": ${where} is written through ${row.modsEvent ?? "no mods event"}, not ${ctx.event}`); const normalWhen = canonicalWhen(when); const overlay = Object.freeze({ id, op: op, target, ...(text !== undefined ? { text: text } : {}), ...(message !== undefined ? { message: message } : {}), ...(normalWhen !== undefined ? { when: normalWhen } : {}), }); for (const t of textsOf(overlay)) { const hits = lockedTextIn(t); if (hits.length > 0) return fail(`overlay "${id}": its text contains, verbatim, the locked text of ${[...new Set(hits.map((h) => h.split("#")[0]))].join(", ")}`); } return { overlay }; } export function foldOverlays(ordered, selectedText, applies, overlayText) { const messageText = new Map(); const writers = new Map(); const dropped = new Map(); const added = new Map(); const addedBy = new Map(); const addCount = new Map(); const superseded = []; const chains = new Map(); const supersede = (w, target, by) => { superseded.push(Object.freeze({ id: w.id, source: w.origin.source, by: w.origin.by, target, supersededBy: by })); if (w.link !== undefined) w.link.superseded = true; }; const applied = (key, o, origin) => { const link = { source: origin.source, overlay: o.id, op: o.op }; chains.set(key, [...(chains.get(key) ?? []), link]); return { id: o.id, origin, link }; }; const sectionMessages = (sectionId) => [...MESSAGE_TARGETS.entries()].filter(([, t]) => t.section.id === sectionId).map(([m]) => m); for (const { overlay: o, origin } of ordered) { if (!applies(o)) continue; const me = { id: o.id, origin }; const text = o.op === "drop" ? "" : overlayText(o); if (text === undefined) { supersede(me, o.target, ABSENT_THIS_RUN); continue; } if (o.op === "add") { const prior = addedBy.get(o.id); if (prior !== undefined) supersede(prior, o.target, o.id); const band = o.target; const k = addCount.get(band) ?? 0; addCount.set(band, k + 1); added.set(o.id, Object.freeze({ id: o.id, band, rank: bandOf(band).addRank + k * 0.001, text, origin })); addedBy.set(o.id, applied(o.id, o, origin)); continue; } if (o.op === "drop") { const entry = addedBy.get(o.target); if (entry !== undefined) { supersede(entry, o.target, o.id); added.delete(o.target); addedBy.delete(o.target); continue; } if (!SECTION_BY_ID.has(o.target)) { supersede(me, o.target, ABSENT_THIS_RUN); continue; } for (const m of sectionMessages(o.target)) { for (const w of writers.get(m) ?? []) supersede(w, m, o.id); writers.delete(m); messageText.delete(m); } dropped.set(o.target, applied(o.target, o, origin)); continue; } const section = MESSAGE_TARGETS.get(o.target).section; const drop = dropped.get(section.id); if (o.op === "append" && drop !== undefined) { supersede(me, o.target, drop.id); continue; } if (selectedText(o.target) === undefined && !messageText.has(o.target)) { supersede(me, o.target, ABSENT_THIS_RUN); continue; } if (drop !== undefined) { supersede(drop, section.id, o.id); dropped.delete(section.id); } const writer = applied(section.id, o, origin); if (o.op === "replace") { for (const w of writers.get(o.target) ?? []) supersede(w, o.target, o.id); writers.set(o.target, [writer]); messageText.set(o.target, { text, origin, replaced: true }); } else { const base = messageText.get(o.target)?.text ?? selectedText(o.target); writers.set(o.target, [...(writers.get(o.target) ?? []), writer]); messageText.set(o.target, { text: `${base}\n\n${text}`, origin, replaced: messageText.get(o.target)?.replaced ?? false }); } } return { messageText, dropped: new Set(dropped.keys()), added: [...added.values()], superseded, chains: new Map([...chains].map(([k, links]) => [k, Object.freeze(links.map((l) => Object.freeze({ ...l })))])), }; }