File size: 3,399 Bytes
f500658
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
import type { SessionMetadata } from "@earendil-works/pi-agent-core";
import type { Usage } from "@earendil-works/pi-ai";
import { sql } from "../sql.ts";
import type { SqliteDatabase } from "../types.ts";

export interface SessionRow {
	id: string;
	created_at: number;
	parent_session_id: string | null;
	storage_version: number;
	metadata: string | null;
	message_count: number;
	usage_payload: string;
	next_seq: number;
}

export interface SqliteSessionMetadata extends SessionMetadata {
	/** SQLite container/shard path containing this session. */
	path: string;
}

function zeroUsage(): Usage {
	return {
		input: 0,
		output: 0,
		cacheRead: 0,
		cacheWrite: 0,
		totalTokens: 0,
		cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
	};
}

export function readSessionRow(db: SqliteDatabase, sessionId: string): SessionRow {
	const row = sql`SELECT id, created_at, parent_session_id, storage_version, metadata,
			message_count, usage_payload, next_seq
		FROM sessions
		WHERE id = ${sessionId}`.get<SessionRow>(db);
	if (row === undefined) throw new Error(`Unknown SQLite session: ${sessionId}`);
	return row;
}

export function readAllSessionRows(db: SqliteDatabase): SessionRow[] {
	return sql`SELECT id, created_at, parent_session_id, storage_version, metadata,
			message_count, usage_payload, next_seq
		FROM sessions`.all<SessionRow>(db);
}

export function hasSessionRow(db: SqliteDatabase, sessionId: string): boolean {
	return sql`SELECT id FROM sessions WHERE id = ${sessionId}`.get<{ id: string }>(db) !== undefined;
}

export function metadataFromSessionRow(
	path: string,
	row: SessionRow,
	currentStorageVersion: number,
): SqliteSessionMetadata {
	if (row.storage_version > currentStorageVersion) {
		throw new Error(`SQLite session storage version ${row.storage_version} is newer than ${currentStorageVersion}`);
	}
	if (row.storage_version < currentStorageVersion) {
		throw new Error(`SQLite session storage version ${row.storage_version} requires migrations`);
	}
	return {
		id: row.id,
		createdAt: row.created_at,
		storageVersion: row.storage_version,
		...(row.parent_session_id === null ? {} : { parentSessionId: row.parent_session_id }),
		path,
	};
}

export function insertSessionRow(
	db: SqliteDatabase,
	metadata: SqliteSessionMetadata,
	storageVersion: number,
	nextSeq: number,
): void {
	sql`INSERT INTO sessions
			(id, created_at, parent_session_id, storage_version, metadata, message_count, usage_payload, next_seq)
		VALUES (
			${metadata.id},
			${metadata.createdAt},
			${metadata.parentSessionId ?? null},
			${storageVersion},
			${null},
			${0},
			${JSON.stringify(zeroUsage())},
			${nextSeq}
		)`.run(db);
}

export function deleteSessionRows(db: SqliteDatabase, sessionId: string): void {
	sql`DELETE FROM entries WHERE session_id = ${sessionId}`.run(db);
	sql`DELETE FROM scalar_values WHERE session_id = ${sessionId}`.run(db);
	sql`DELETE FROM list_values WHERE session_id = ${sessionId}`.run(db);
	sql`DELETE FROM usage_ledger WHERE session_id = ${sessionId}`.run(db);
	sql`DELETE FROM branch_entries WHERE session_id = ${sessionId}`.run(db);
	sql`DELETE FROM branch_meta WHERE session_id = ${sessionId}`.run(db);
	const result = sql`DELETE FROM sessions WHERE id = ${sessionId}`.run(db);
	if (result.changes !== 1)
		throw new Error(`Expected to delete one SQLite session ${sessionId}, deleted ${result.changes}`);
}