UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

138 lines (137 loc) • 4.2 kB
import { SessionError, } from "../harness/types.js"; import { uuidv7 } from "./uuid.js"; function updateLabelCache(labelsById, entry) { if (entry.type !== "label") { return; } const label = entry.label?.trim(); if (label) { labelsById.set(entry.targetId, label); } else { labelsById.delete(entry.targetId); } } function buildLabelsById(entries) { const labelsById = new Map(); for (const entry of entries) { updateLabelCache(labelsById, entry); } return labelsById; } function generateEntryId(byId) { for (let i = 0; i < 100; i++) { const id = uuidv7().slice(-8); if (!byId.has(id)) { return id; } } return uuidv7(); } export function leafIdAfterEntry(entry) { return entry.type === "leaf" ? entry.targetId : entry.id; } function resolveLeafId(entries) { let leafId = null; for (const entry of entries) { leafId = leafIdAfterEntry(entry); } return leafId; } export class BaseSessionStorage { metadata; entries; byId; labelsById; leafId; floorId; constructor(metadata, entries, leafId = resolveLeafId(entries), opts) { this.metadata = metadata; this.entries = entries; this.byId = new Map(entries.map((entry) => [entry.id, entry])); this.labelsById = buildLabelsById(entries); this.leafId = leafId; this.floorId = opts?.floorEntryId ?? null; if (this.leafId !== null && !this.byId.has(this.leafId)) { throw new SessionError("invalid_session", `Entry ${this.leafId} not found`); } if (this.floorId !== null && !this.byId.has(this.floorId)) { throw new SessionError("invalid_session", `Floor entry ${this.floorId} not loaded`); } } getFloorId() { return this.floorId; } async getMetadata() { return this.metadata; } async getLeafId() { if (this.leafId !== null && !this.byId.has(this.leafId)) { throw new SessionError("invalid_session", `Entry ${this.leafId} not found`); } return this.leafId; } createLeafEntry(leafId) { if (leafId !== null && !this.byId.has(leafId)) { throw new SessionError("not_found", `Entry ${leafId} not found`); } return { type: "leaf", id: generateEntryId(this.byId), parentId: this.leafId, timestamp: new Date().toISOString(), targetId: leafId, }; } async createEntryId() { return generateEntryId(this.byId); } assertExpectedLeaf(opts) { if (opts?.expectedLeafId !== undefined && opts.expectedLeafId !== this.leafId) { throw new SessionError("conflict", `Stale write: expected leaf ${opts.expectedLeafId ?? "(empty)"}, but current leaf is ${this.leafId ?? "(empty)"}`); } } recordEntry(entry) { this.entries.push(entry); this.byId.set(entry.id, entry); updateLabelCache(this.labelsById, entry); this.leafId = leafIdAfterEntry(entry); } async getEntry(id) { return this.byId.get(id); } async findEntries(type) { return this.entries.filter((entry) => entry.type === type); } async getLabel(id) { return this.labelsById.get(id); } async getPathToRoot(leafId) { if (leafId === null) { return []; } const path = []; let current = this.byId.get(leafId); if (!current) { throw new SessionError("not_found", `Entry ${leafId} not found`); } while (current) { path.unshift(current); if (current.id === this.floorId) { break; } if (!current.parentId) { break; } const parent = this.byId.get(current.parentId); if (!parent) { throw new SessionError("invalid_session", `Entry ${current.parentId} not found`); } current = parent; } return path; } async getEntries() { return [...this.entries]; } }