@sema-agent/core
Version:
Stateless, task-oriented AI agent core
65 lines (64 loc) • 3.52 kB
JavaScript
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 };
}