UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

166 lines (165 loc) • 6.61 kB
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; }