Files
pi_harness/packages/agent/src/harness/session/repo-utils.ts
T
Cristina Poncela Cubeiro 9e7582aa03 feat: sqlite session storage (#6594)
This PR:

- Adds retainedTail to compaction entries in the new agent harness so we don't have to walk up the tree for the 2000 tokens before compaction,
- Changes getPathToRoot to getPathToRootOrCompaction to only load until last compaction, as unnecessary to access all nodes where it is called,
- Adds a SQLite storage backend, in a separate packages/session-backend-sqlite, with a migration system and schemas as per on-site discussions: sessions to match session header messages (except for metadata, which I couldn't understand what it's used for or where it gets written, so I omitted it), session_entries for shared entry types as columns plus payload as a json for what remains, session_sequences to represent the append-only, serialized nature of the jsonl files, branch_entries to attribute nodes to branches (relationship one-to-many), and session_materialized with the session info (see /session in TUI) to act as a "cache" or quick-access for costs, message count, token info, labels, session name, and model-thinking-level config (e.g. for fast resume).
- This is compatible with the new agent harness Session abstraction.
2026-07-21 11:36:31 +02:00

52 lines
1.6 KiB
TypeScript

import { uuidv7 } from "@earendil-works/pi-ai";
import {
type FileError,
type Result,
SessionError,
type SessionMetadata,
type SessionStorage,
type SessionTreeEntry,
} from "../types.ts";
import { Session } from "./session.ts";
export function createSessionId(): string {
return uuidv7();
}
export function createTimestamp(): string {
return new Date().toISOString();
}
export function toSession<TMetadata extends SessionMetadata>(storage: SessionStorage<TMetadata>): Session<TMetadata> {
return new Session(storage);
}
export function getFileSystemResultOrThrow<TValue>(result: Result<TValue, FileError>, message: string): TValue {
if (!result.ok) {
const code = result.error.code === "not_found" ? "not_found" : "storage";
throw new SessionError(code, `${message}: ${result.error.message}`, result.error);
}
return result.value;
}
export async function getEntriesToFork(
storage: SessionStorage,
options: { entryId?: string; position?: "before" | "at" },
): Promise<SessionTreeEntry[]> {
if (!options.entryId) return storage.getEntries();
const target = await storage.getEntry(options.entryId);
if (!target) {
throw new SessionError("invalid_fork_target", `Entry ${options.entryId} not found`);
}
let effectiveLeafId: string | null;
if ((options.position ?? "before") === "at") {
effectiveLeafId = target.id;
} else {
if (target.type !== "message" || target.message.role !== "user") {
throw new SessionError("invalid_fork_target", `Entry ${options.entryId} is not a user message`);
}
effectiveLeafId = target.parentId;
}
return storage.getPathToRootOrCompaction(effectiveLeafId);
}