refactor(agent): isolate node filesystem session dependencies
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
import type { SessionMetadata, SessionStorage, SessionTreeEntry } from "../types.js";
|
||||
import { uuidv7 } from "./uuid.js";
|
||||
|
||||
function updateLabelCache(labelsById: Map<string, string>, entry: SessionTreeEntry): void {
|
||||
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: SessionTreeEntry[]): Map<string, string> {
|
||||
const labelsById = new Map<string, string>();
|
||||
for (const entry of entries) {
|
||||
updateLabelCache(labelsById, entry);
|
||||
}
|
||||
return labelsById;
|
||||
}
|
||||
|
||||
function generateEntryId(byId: { has(id: string): boolean }): string {
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const id = uuidv7().slice(0, 8);
|
||||
if (!byId.has(id)) return id;
|
||||
}
|
||||
return uuidv7();
|
||||
}
|
||||
|
||||
export class InMemorySessionStorage implements SessionStorage {
|
||||
private readonly metadata: SessionMetadata;
|
||||
private entries: SessionTreeEntry[];
|
||||
private byId: Map<string, SessionTreeEntry>;
|
||||
private labelsById: Map<string, string>;
|
||||
private leafId: string | null;
|
||||
|
||||
constructor(options?: { entries?: SessionTreeEntry[]; leafId?: string | null; metadata?: SessionMetadata }) {
|
||||
this.entries = options?.entries ? [...options.entries] : [];
|
||||
this.byId = new Map(this.entries.map((entry) => [entry.id, entry]));
|
||||
this.labelsById = buildLabelsById(this.entries);
|
||||
this.leafId = options?.leafId ?? this.entries[this.entries.length - 1]?.id ?? null;
|
||||
if (this.leafId !== null && !this.byId.has(this.leafId)) {
|
||||
throw new Error(`Entry ${this.leafId} not found`);
|
||||
}
|
||||
this.metadata = options?.metadata ?? { id: uuidv7(), createdAt: new Date().toISOString() };
|
||||
}
|
||||
|
||||
async getMetadata(): Promise<SessionMetadata> {
|
||||
return this.metadata;
|
||||
}
|
||||
|
||||
async getLeafId(): Promise<string | null> {
|
||||
return this.leafId;
|
||||
}
|
||||
|
||||
async setLeafId(leafId: string | null): Promise<void> {
|
||||
if (leafId !== null && !this.byId.has(leafId)) {
|
||||
throw new Error(`Entry ${leafId} not found`);
|
||||
}
|
||||
this.leafId = leafId;
|
||||
}
|
||||
|
||||
async createEntryId(): Promise<string> {
|
||||
return generateEntryId(this.byId);
|
||||
}
|
||||
|
||||
async appendEntry(entry: SessionTreeEntry): Promise<void> {
|
||||
this.entries.push(entry);
|
||||
this.byId.set(entry.id, entry);
|
||||
updateLabelCache(this.labelsById, entry);
|
||||
this.leafId = entry.id;
|
||||
}
|
||||
|
||||
async getEntry(id: string): Promise<SessionTreeEntry | undefined> {
|
||||
return this.byId.get(id);
|
||||
}
|
||||
|
||||
async findEntries<TType extends SessionTreeEntry["type"]>(
|
||||
type: TType,
|
||||
): Promise<Array<Extract<SessionTreeEntry, { type: TType }>>> {
|
||||
return this.entries.filter((entry): entry is Extract<SessionTreeEntry, { type: TType }> => entry.type === type);
|
||||
}
|
||||
|
||||
async getLabel(id: string): Promise<string | undefined> {
|
||||
return this.labelsById.get(id);
|
||||
}
|
||||
|
||||
async getPathToRoot(leafId: string | null): Promise<SessionTreeEntry[]> {
|
||||
if (leafId === null) return [];
|
||||
const path: SessionTreeEntry[] = [];
|
||||
let current = this.byId.get(leafId);
|
||||
while (current) {
|
||||
path.unshift(current);
|
||||
current = current.parentId ? this.byId.get(current.parentId) : undefined;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
async getEntries(): Promise<SessionTreeEntry[]> {
|
||||
return [...this.entries];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user