UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

65 lines (64 loc) • 3.52 kB
import { assertContainmentFaceValue } from "../tools/fs/containment-face.js"; import { assertContextSettingsValue } from "./context-settings.js"; export const SWAPPABLE_DEP_SEATS = Object.freeze(["models", "tiers", "readFace", "writeFace", "contextSettings"]); const SWAPPABLE_DEP_SEAT_SET = new Set(SWAPPABLE_DEP_SEATS); export function isSwappableDepSeat(k) { return SWAPPABLE_DEP_SEAT_SET.has(k); } export const SWAP_SEAT_NOTICE = { models: "config.models_swapped", tiers: "config.models_swapped", readFace: "config.read_face_swapped", writeFace: "config.write_face_swapped", contextSettings: "config.context_settings_swapped", }; export function screenSwappableDeps(next) { if (next === null || typeof next !== "object" || Array.isArray(next)) { throw new Error(`swapDeps: the candidate must be a plain object naming the seats to swap (got ${next === null ? "null" : Array.isArray(next) ? "array" : typeof next}) — the current deps stay in force`); } const keys = Object.keys(next); const unknown = keys.filter((k) => !isSwappableDepSeat(k)); if (unknown.length > 0) { throw new Error(`swapDeps: ${unknown.map((k) => JSON.stringify(k)).join(", ")} is not a hot-swappable seat (the closed set is ${SWAPPABLE_DEP_SEATS.join(" | ")}) — the current deps stay in force`); } const present = SWAPPABLE_DEP_SEATS.filter((seat) => Object.hasOwn(next, seat)); if (present.length === 0) { throw new Error(`swapDeps: the candidate names no seat (an empty swap is a caller defect, not a no-op) — the current deps stay in force`); } const raw = next; const candidate = {}; for (const seat of present) { const v = raw[seat]; switch (seat) { case "models": if (v === null || typeof v !== "object" || Array.isArray(v)) { throw new Error(`swapDeps: models must be a plain Record<string, Model> (got ${v === null ? "null" : Array.isArray(v) ? "array" : typeof v}) — the current generation stays in force`); } candidate.models = { ...v }; break; case "tiers": if (!present.includes("models")) { throw new Error(`swapDeps: tiers is a binding over models — pass the catalog it binds in the same swap (\`{ models, tiers }\`); the current generation stays in force`); } if (v !== undefined && (v === null || typeof v !== "object" || Array.isArray(v))) { throw new Error(`swapDeps: tiers must be a plain Record<string, ModelRef> or undefined (got ${v === null ? "null" : Array.isArray(v) ? "array" : typeof v}) — the current generation stays in force`); } candidate.tiers = v === undefined ? undefined : { ...v }; break; case "readFace": candidate.readFace = assertContainmentFaceValue(v, "read", "deps"); break; case "writeFace": candidate.writeFace = assertContainmentFaceValue(v, "write", "deps"); break; case "contextSettings": candidate.contextSettings = assertContextSettingsValue(v, "swapDeps"); break; default: { const _exhaustive = seat; throw new Error(`swapDeps: unhandled seat ${String(_exhaustive)}`); } } } return { seats: present, candidate: candidate }; }