9e7582aa03
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.
98 lines
2.8 KiB
TypeScript
98 lines
2.8 KiB
TypeScript
import type { SQLInputValue } from "node:sqlite";
|
|
import { DatabaseSync } from "node:sqlite";
|
|
import type { SqliteDatabase, SqliteDatabaseFactory, SqliteRunResult, SqliteStatement } from "./sqlite/types.ts";
|
|
|
|
function isNamedParameters(value: unknown): value is Record<string, SQLInputValue> {
|
|
if (value === null || typeof value !== "object") return false;
|
|
if (Array.isArray(value) || ArrayBuffer.isView(value)) return false;
|
|
return true;
|
|
}
|
|
|
|
class NodeSqliteStatement implements SqliteStatement {
|
|
private readonly statement: ReturnType<DatabaseSync["prepare"]>;
|
|
|
|
constructor(statement: ReturnType<DatabaseSync["prepare"]>) {
|
|
this.statement = statement;
|
|
}
|
|
|
|
async run(...params: unknown[]): Promise<SqliteRunResult> {
|
|
const [first, ...rest] = params;
|
|
const result = isNamedParameters(first)
|
|
? this.statement.run(first, ...(rest as SQLInputValue[]))
|
|
: this.statement.run(...(params as SQLInputValue[]));
|
|
return {
|
|
changes: Number(result.changes),
|
|
lastInsertRowid: result.lastInsertRowid === undefined ? undefined : Number(result.lastInsertRowid),
|
|
};
|
|
}
|
|
|
|
async get<TRow extends object>(...params: unknown[]): Promise<TRow | undefined> {
|
|
const [first, ...rest] = params;
|
|
return (
|
|
isNamedParameters(first)
|
|
? this.statement.get(first, ...(rest as SQLInputValue[]))
|
|
: this.statement.get(...(params as SQLInputValue[]))
|
|
) as TRow | undefined;
|
|
}
|
|
|
|
async all<TRow extends object>(...params: unknown[]): Promise<TRow[]> {
|
|
const [first, ...rest] = params;
|
|
return (
|
|
isNamedParameters(first)
|
|
? this.statement.all(first, ...(rest as SQLInputValue[]))
|
|
: this.statement.all(...(params as SQLInputValue[]))
|
|
) as TRow[];
|
|
}
|
|
}
|
|
|
|
class NodeSqliteDatabase implements SqliteDatabase {
|
|
private readonly db: DatabaseSync;
|
|
|
|
constructor(db: DatabaseSync) {
|
|
this.db = db;
|
|
}
|
|
|
|
async exec(sql: string): Promise<void> {
|
|
this.db.exec(sql);
|
|
}
|
|
|
|
prepare(sql: string): SqliteStatement {
|
|
return new NodeSqliteStatement(this.db.prepare(sql));
|
|
}
|
|
|
|
async transaction<T>(fn: () => Promise<T>): Promise<T> {
|
|
this.db.exec("BEGIN");
|
|
try {
|
|
const result = await fn();
|
|
this.db.exec("COMMIT");
|
|
return result;
|
|
} catch (error) {
|
|
try {
|
|
this.db.exec("ROLLBACK");
|
|
} catch {
|
|
// Ignore rollback errors to rethrow original error.
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async close(): Promise<void> {
|
|
this.db.close();
|
|
}
|
|
}
|
|
|
|
export function wrapNodeSqliteDatabase(db: DatabaseSync): SqliteDatabase {
|
|
return new NodeSqliteDatabase(db);
|
|
}
|
|
|
|
export function createNodeSqliteFactory(): SqliteDatabaseFactory {
|
|
return {
|
|
async open(path: string): Promise<SqliteDatabase> {
|
|
return new NodeSqliteDatabase(new DatabaseSync(path));
|
|
},
|
|
};
|
|
}
|
|
|
|
// Re-export the SQLite session storage backend and types so this package is a complete node-sqlite backend.
|
|
export * from "./sqlite/index.ts";
|