@sema-agent/core
Version:
Stateless, task-oriented AI agent core
166 lines (165 loc) • 6.61 kB
JavaScript
import { catalogMessage } from "./catalog/index.js";
import { foldOverlays } from "./overlay.js";
import { RENDERERS, runRenderer } from "./renderers.js";
import { factsHold, selectVariant, selectorsOf, whenHolds } from "./select.js";
function joinParts(parts) {
return parts.filter((p) => Boolean(p && p.trim())).join("\n\n");
}
function basesOf(catalog, inputs, s) {
const bases = new Map();
for (const section of catalog.sections) {
if (!factsHold(section.admit, s.facts))
continue;
if ("renderer" in section.body) {
const out = runRenderer(section.body.renderer, inputs, s);
if (out !== undefined && out.text.trim())
bases.set(section.id, { kind: "renderer", text: out.text, ...(out.variant !== undefined ? { variant: out.variant } : {}) });
continue;
}
const lines = "message" in section.body ? [{ message: section.body.message }] : section.body.lines;
const picked = [];
for (const line of lines) {
if ("admit" in line && !factsHold(line.admit, s.facts))
continue;
const v = selectVariant(catalogMessage(line.message, catalog), s);
if (v !== undefined)
picked.push({ message: line.message, text: v.text, variant: v.id });
}
bases.set(section.id, { kind: "lines", lines: picked });
}
return bases;
}
function selectedTextOf(catalog, bases) {
const sectionOf = new Map();
for (const sec of catalog.sections) {
const ids = "message" in sec.body ? [sec.body.message] : "lines" in sec.body ? sec.body.lines.map((l) => l.message) : (RENDERERS[sec.body.renderer].messages ?? []);
for (const id of ids)
sectionOf.set(id, sec);
}
return (messageId) => {
const sec = sectionOf.get(messageId);
const base = sec === undefined ? undefined : bases.get(sec.id);
if (base === undefined)
return undefined;
if (base.kind === "renderer")
return base.text;
return base.lines.find((l) => l.message === messageId)?.text;
};
}
function renderSection(section, base, fold) {
let text;
let variant;
if (base.kind === "renderer") {
const m = "renderer" in section.body ? RENDERERS[section.body.renderer].messages?.[0] : undefined;
const over = m !== undefined ? fold.messageText.get(m) : undefined;
text = over?.text ?? base.text;
variant = base.variant;
}
else {
const parts = base.lines.map((l) => fold.messageText.get(l.message)?.text ?? l.text);
if (parts.length === 0)
return undefined;
const joiner = "message" in section.body ? "" : "lines" in section.body ? section.body.joiner : "";
text = parts.join(joiner);
variant = base.lines.map((l) => l.variant).join("+");
}
if (!text.trim())
return undefined;
return {
id: section.id,
band: section.band,
rank: section.rank,
...(variant !== undefined ? { variant } : {}),
text,
blockId: section.block.id,
blockJoiner: section.block.joiner ?? "\n\n",
chain: [{ source: "bundled" }, ...(fold.chains.get(section.id) ?? [])],
...("renderer" in section.body ? { renderer: { id: section.body.renderer, version: RENDERERS[section.body.renderer].version } } : {}),
};
}
function renderAdded(e, fold) {
if (!e.text.trim())
return undefined;
return {
id: e.id,
band: e.band,
rank: e.rank,
text: e.text,
blockId: e.id.startsWith("core/") ? e.id.slice("core/".length) : e.id,
blockJoiner: "\n\n",
chain: fold.chains.get(e.id) ?? [],
};
}
export function composeCatalog(plan, inputs) {
const s = selectorsOf(inputs.facts, plan.host, plan.binding);
const bases = basesOf(plan.catalog, inputs, s);
const selectedText = selectedTextOf(plan.catalog, bases);
const overlayText = (o) => {
if (o.text !== undefined)
return o.text;
if (o.message === undefined)
return undefined;
return selectVariant(catalogMessage(o.message, plan.catalog), s)?.text;
};
const fold = foldOverlays(plan.overlays ?? [], selectedText, (o) => whenHolds(o.when, s), overlayText);
const rows = [];
for (const section of plan.catalog.sections) {
if (fold.dropped.has(section.id))
continue;
const base = bases.get(section.id);
if (base === undefined)
continue;
rows.push({ rank: section.rank, id: section.id, render: () => renderSection(section, base, fold) });
}
for (const e of fold.added)
rows.push({ rank: e.rank, id: e.id, render: () => renderAdded(e, fold) });
rows.sort((a, b) => a.rank - b.rank);
const ranks = new Set();
const ids = new Set();
for (const r of rows) {
if (ids.has(r.id))
throw new Error(`prompt composition: duplicate section id "${r.id}"`);
if (ranks.has(r.rank))
throw new Error(`prompt composition: duplicate rank ${r.rank} ("${r.id}")`);
ids.add(r.id);
ranks.add(r.rank);
}
const sections = [];
for (const r of rows) {
const compiled = r.render();
if (compiled)
sections.push(compiled);
}
const text = joinParts(sections.map((x) => x.text));
const blocks = [];
for (const x of sections) {
const last = blocks[blocks.length - 1];
if (last && last.id === x.blockId)
last.text = `${last.text}${x.blockJoiner}${x.text}`;
else
blocks.push({ id: x.blockId, text: x.text });
}
return { text, sections, blocks, superseded: fold.superseded };
}
export const SINGLE_BLOCK_LAYOUT = { groups: [{ groupId: "body", untilRank: Number.POSITIVE_INFINITY, cacheControlBoundary: true }] };
export function layoutSystemBlocks(composed, layout = SINGLE_BLOCK_LAYOUT) {
const blocks = [];
let g = 0;
let bucket = [];
const flush = () => {
if (bucket.length > 0) {
const grp = layout.groups[Math.min(g, layout.groups.length - 1)];
blocks.push({ groupId: grp.groupId, text: bucket.join("\n\n"), cacheControlBoundary: grp.cacheControlBoundary });
bucket = [];
}
};
for (const s of composed.sections) {
while (g < layout.groups.length - 1 && s.rank > layout.groups[g].untilRank) {
flush();
g++;
}
bucket.push(s.text);
}
flush();
return blocks;
}