import fs from "node:fs/promises"; import path from "node:path"; import { type SessionMetadata, fromHex, toHex } from "@open-cursor/client"; interface BlobEntry { id: string; data: string; } interface BlobsFile { version: 1; blobs: BlobEntry[]; } interface MetaFile { version: 1; agentId: string; latestRootBlobId: string; name: string; createdAt: number; mode: string; lastUsedModel?: string; } const getSessionDir = (baseDir: string, sessionId: string): string => path.join(baseDir, "chats", sessionId); const getBlobsFilePath = (baseDir: string, sessionId: string): string => path.join(getSessionDir(baseDir, sessionId), "blobs.json"); const getMetaFilePath = (baseDir: string, sessionId: string): string => path.join(getSessionDir(baseDir, sessionId), "meta.json"); export const loadBlobsFromDisk = async ( baseDir: string, sessionId: string, ): Promise> => { try { const text = await fs.readFile(getBlobsFilePath(baseDir, sessionId), "utf-8"); // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- JSON.parse returns unknown const parsed = JSON.parse(text) as BlobsFile; if (parsed?.version !== 1 || !Array.isArray(parsed.blobs)) { return new Map(); } const map = new Map(); for (const entry of parsed.blobs) { if (!entry || typeof entry.id !== "string" || typeof entry.data !== "string") continue; try { map.set(entry.id, new Uint8Array(Buffer.from(entry.data, "base64"))); } catch { // skip corrupt entries } } return map; } catch { return new Map(); } }; export const saveBlobsToDisk = async ( baseDir: string, sessionId: string, blobs: Map, ): Promise => { const dir = getSessionDir(baseDir, sessionId); await fs.mkdir(dir, { recursive: true }); const file: BlobsFile = { version: 1, blobs: Array.from(blobs.entries()).map(([id, data]) => ({ id, data: Buffer.from(data).toString("base64"), })), }; const filePath = getBlobsFilePath(baseDir, sessionId); const tmpPath = `${filePath}.tmp`; await fs.writeFile(tmpPath, JSON.stringify(file), "utf-8"); await fs.rename(tmpPath, filePath); }; export const loadMetaFromDisk = async ( baseDir: string, sessionId: string, ): Promise => { try { const text = await fs.readFile(getMetaFilePath(baseDir, sessionId), "utf-8"); // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- JSON.parse returns unknown const parsed = JSON.parse(text) as MetaFile; if (parsed?.version !== 1 || typeof parsed.agentId !== "string") { return null; } return { agentId: parsed.agentId, latestRootBlobId: parsed.latestRootBlobId ? fromHex(parsed.latestRootBlobId) : new Uint8Array(), name: parsed.name ?? "New Agent", createdAt: parsed.createdAt ?? Date.now(), // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- narrowing parsed.mode to known union mode: (parsed.mode as SessionMetadata["mode"]) ?? "default", ...(parsed.lastUsedModel !== null && parsed.lastUsedModel !== undefined && { lastUsedModel: parsed.lastUsedModel, }), }; } catch { return null; } }; export const saveMetaToDisk = async ( baseDir: string, sessionId: string, metadata: SessionMetadata, ): Promise => { const dir = getSessionDir(baseDir, sessionId); await fs.mkdir(dir, { recursive: true }); const file: MetaFile = { version: 1, agentId: metadata.agentId, latestRootBlobId: toHex(metadata.latestRootBlobId), name: metadata.name, createdAt: metadata.createdAt, mode: metadata.mode, ...(metadata.lastUsedModel !== null && metadata.lastUsedModel !== undefined && { lastUsedModel: metadata.lastUsedModel, }), }; const filePath = getMetaFilePath(baseDir, sessionId); const tmpPath = `${filePath}.tmp`; await fs.writeFile(tmpPath, JSON.stringify(file), "utf-8"); await fs.rename(tmpPath, filePath); };