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