@averyyy/pi-server 0.80.3-piclient.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,158 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { exportSessionState, restoreSessionState, } from "./session-store.js";
5
+ const PERSISTED_SESSION_VERSION = 1;
6
+ const REPLACE_RETRY_DELAYS_MS = [25, 50, 100, 200, 400];
7
+ function sessionFileName(sessionId) {
8
+ return `${createHash("sha256").update(sessionId).digest("hex")}.json`;
9
+ }
10
+ function sessionPath(sessionStoreDir, sessionId) {
11
+ return join(sessionStoreDir, sessionFileName(sessionId));
12
+ }
13
+ function isRetryableRenameError(error) {
14
+ if (!(error instanceof Error))
15
+ return false;
16
+ const code = error.code;
17
+ return process.platform === "win32" && code === "EPERM";
18
+ }
19
+ function sleepSync(ms) {
20
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
21
+ }
22
+ function replaceFileSync(sourcePath, targetPath) {
23
+ for (let attempt = 0; attempt <= REPLACE_RETRY_DELAYS_MS.length; attempt++) {
24
+ try {
25
+ renameSync(sourcePath, targetPath);
26
+ return;
27
+ }
28
+ catch (error) {
29
+ if (!isRetryableRenameError(error) || attempt === REPLACE_RETRY_DELAYS_MS.length) {
30
+ throw error;
31
+ }
32
+ sleepSync(REPLACE_RETRY_DELAYS_MS[attempt]);
33
+ }
34
+ }
35
+ }
36
+ function assertRecord(value, path) {
37
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
38
+ throw new Error(`${path} must be an object`);
39
+ }
40
+ }
41
+ function assertString(value, path) {
42
+ if (typeof value !== "string") {
43
+ throw new Error(`${path} must be a string`);
44
+ }
45
+ }
46
+ function assertNumber(value, path) {
47
+ if (typeof value !== "number" || !Number.isFinite(value)) {
48
+ throw new Error(`${path} must be a finite number`);
49
+ }
50
+ }
51
+ function assertNullableString(value, path) {
52
+ if (value !== null && typeof value !== "string") {
53
+ throw new Error(`${path} must be a string or null`);
54
+ }
55
+ }
56
+ function assertStaticContext(value) {
57
+ if (value === undefined)
58
+ return;
59
+ assertRecord(value, "session.staticContext");
60
+ const systemPrompt = value.systemPrompt;
61
+ if (systemPrompt !== undefined && typeof systemPrompt !== "string") {
62
+ throw new Error("session.staticContext.systemPrompt must be a string");
63
+ }
64
+ const tools = value.tools;
65
+ if (tools !== undefined) {
66
+ if (!Array.isArray(tools)) {
67
+ throw new Error("session.staticContext.tools must be an array");
68
+ }
69
+ for (const [index, tool] of tools.entries()) {
70
+ assertRecord(tool, `session.staticContext.tools[${index}]`);
71
+ assertString(tool.name, `session.staticContext.tools[${index}].name`);
72
+ assertString(tool.description, `session.staticContext.tools[${index}].description`);
73
+ if (tool.parameters === undefined) {
74
+ throw new Error(`session.staticContext.tools[${index}].parameters is required`);
75
+ }
76
+ }
77
+ }
78
+ }
79
+ function assertSessionTreeEntries(value) {
80
+ if (!Array.isArray(value)) {
81
+ throw new Error("session.entries must be an array");
82
+ }
83
+ for (const [index, entry] of value.entries()) {
84
+ assertRecord(entry, `session.entries[${index}]`);
85
+ assertString(entry.type, `session.entries[${index}].type`);
86
+ assertString(entry.id, `session.entries[${index}].id`);
87
+ assertNullableString(entry.parentId, `session.entries[${index}].parentId`);
88
+ assertString(entry.timestamp, `session.entries[${index}].timestamp`);
89
+ if (entry.type === "message") {
90
+ assertRecord(entry.message, `session.entries[${index}].message`);
91
+ }
92
+ }
93
+ }
94
+ function assertPersistedSessionState(value) {
95
+ assertRecord(value, "session");
96
+ assertString(value.sessionId, "session.sessionId");
97
+ assertStaticContext(value.staticContext);
98
+ assertSessionTreeEntries(value.entries);
99
+ assertNullableString(value.leafId, "session.leafId");
100
+ assertNumber(value.revision, "session.revision");
101
+ assertNumber(value.createdAt, "session.createdAt");
102
+ assertNumber(value.updatedAt, "session.updatedAt");
103
+ }
104
+ function parsePersistedSessionFile(raw, sourcePath) {
105
+ let parsed;
106
+ try {
107
+ parsed = JSON.parse(raw);
108
+ }
109
+ catch (error) {
110
+ throw new Error(`Persisted session file contains invalid JSON: ${sourcePath}`, { cause: error });
111
+ }
112
+ assertRecord(parsed, "persisted session file");
113
+ if (parsed.version !== PERSISTED_SESSION_VERSION) {
114
+ throw new Error(`Unsupported persisted session version in ${sourcePath}`);
115
+ }
116
+ assertPersistedSessionState(parsed.session);
117
+ return {
118
+ version: PERSISTED_SESSION_VERSION,
119
+ session: parsed.session,
120
+ };
121
+ }
122
+ export function loadPersistedSessions(sessionStoreDir) {
123
+ if (!existsSync(sessionStoreDir))
124
+ return;
125
+ const entries = readdirSync(sessionStoreDir, { withFileTypes: true });
126
+ for (const entry of entries) {
127
+ if (!entry.isFile() || !entry.name.endsWith(".json"))
128
+ continue;
129
+ const filePath = join(sessionStoreDir, entry.name);
130
+ const persisted = parsePersistedSessionFile(readFileSync(filePath, "utf-8"), filePath);
131
+ const expectedFileName = sessionFileName(persisted.session.sessionId);
132
+ if (entry.name !== expectedFileName) {
133
+ throw new Error(`Persisted session file name does not match sessionId: ${filePath}`);
134
+ }
135
+ restoreSessionState(persisted.session);
136
+ }
137
+ }
138
+ export function savePersistedSession(sessionStoreDir, session) {
139
+ mkdirSync(sessionStoreDir, { recursive: true });
140
+ const filePath = sessionPath(sessionStoreDir, session.sessionId);
141
+ const tempPath = `${filePath}.${randomUUID()}.tmp`;
142
+ const body = {
143
+ version: PERSISTED_SESSION_VERSION,
144
+ session: exportSessionState(session),
145
+ };
146
+ try {
147
+ writeFileSync(tempPath, JSON.stringify(body), "utf-8");
148
+ replaceFileSync(tempPath, filePath);
149
+ }
150
+ catch (error) {
151
+ rmSync(tempPath, { force: true });
152
+ throw error;
153
+ }
154
+ }
155
+ export function deletePersistedSession(sessionStoreDir, sessionId) {
156
+ rmSync(sessionPath(sessionStoreDir, sessionId), { force: true });
157
+ }
158
+ //# sourceMappingURL=session-persistence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-persistence.js","sourceRoot":"","sources":["../src/session-persistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash, randomUUID } from \"node:crypto\";\nimport { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { SessionTreeEntry } from \"@earendil-works/pi-agent-core\";\nimport {\n\texportSessionState,\n\ttype PersistedSessionState,\n\trestoreSessionState,\n\ttype SessionState,\n\ttype SessionStaticContext,\n} from \"./session-store.ts\";\n\nconst PERSISTED_SESSION_VERSION = 1;\nconst REPLACE_RETRY_DELAYS_MS = [25, 50, 100, 200, 400];\n\ninterface PersistedSessionFile {\n\tversion: 1;\n\tsession: PersistedSessionState;\n}\n\nfunction sessionFileName(sessionId: string): string {\n\treturn `${createHash(\"sha256\").update(sessionId).digest(\"hex\")}.json`;\n}\n\nfunction sessionPath(sessionStoreDir: string, sessionId: string): string {\n\treturn join(sessionStoreDir, sessionFileName(sessionId));\n}\n\nfunction isRetryableRenameError(error: unknown): boolean {\n\tif (!(error instanceof Error)) return false;\n\tconst code = (error as NodeJS.ErrnoException).code;\n\treturn process.platform === \"win32\" && code === \"EPERM\";\n}\n\nfunction sleepSync(ms: number): void {\n\tAtomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);\n}\n\nfunction replaceFileSync(sourcePath: string, targetPath: string): void {\n\tfor (let attempt = 0; attempt <= REPLACE_RETRY_DELAYS_MS.length; attempt++) {\n\t\ttry {\n\t\t\trenameSync(sourcePath, targetPath);\n\t\t\treturn;\n\t\t} catch (error) {\n\t\t\tif (!isRetryableRenameError(error) || attempt === REPLACE_RETRY_DELAYS_MS.length) {\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tsleepSync(REPLACE_RETRY_DELAYS_MS[attempt]);\n\t\t}\n\t}\n}\n\nfunction assertRecord(value: unknown, path: string): asserts value is Record<string, unknown> {\n\tif (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n\t\tthrow new Error(`${path} must be an object`);\n\t}\n}\n\nfunction assertString(value: unknown, path: string): asserts value is string {\n\tif (typeof value !== \"string\") {\n\t\tthrow new Error(`${path} must be a string`);\n\t}\n}\n\nfunction assertNumber(value: unknown, path: string): asserts value is number {\n\tif (typeof value !== \"number\" || !Number.isFinite(value)) {\n\t\tthrow new Error(`${path} must be a finite number`);\n\t}\n}\n\nfunction assertNullableString(value: unknown, path: string): asserts value is string | null {\n\tif (value !== null && typeof value !== \"string\") {\n\t\tthrow new Error(`${path} must be a string or null`);\n\t}\n}\n\nfunction assertStaticContext(value: unknown): asserts value is SessionStaticContext | undefined {\n\tif (value === undefined) return;\n\tassertRecord(value, \"session.staticContext\");\n\tconst systemPrompt = value.systemPrompt;\n\tif (systemPrompt !== undefined && typeof systemPrompt !== \"string\") {\n\t\tthrow new Error(\"session.staticContext.systemPrompt must be a string\");\n\t}\n\tconst tools = value.tools;\n\tif (tools !== undefined) {\n\t\tif (!Array.isArray(tools)) {\n\t\t\tthrow new Error(\"session.staticContext.tools must be an array\");\n\t\t}\n\t\tfor (const [index, tool] of tools.entries()) {\n\t\t\tassertRecord(tool, `session.staticContext.tools[${index}]`);\n\t\t\tassertString(tool.name, `session.staticContext.tools[${index}].name`);\n\t\t\tassertString(tool.description, `session.staticContext.tools[${index}].description`);\n\t\t\tif (tool.parameters === undefined) {\n\t\t\t\tthrow new Error(`session.staticContext.tools[${index}].parameters is required`);\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunction assertSessionTreeEntries(value: unknown): asserts value is SessionTreeEntry[] {\n\tif (!Array.isArray(value)) {\n\t\tthrow new Error(\"session.entries must be an array\");\n\t}\n\tfor (const [index, entry] of value.entries()) {\n\t\tassertRecord(entry, `session.entries[${index}]`);\n\t\tassertString(entry.type, `session.entries[${index}].type`);\n\t\tassertString(entry.id, `session.entries[${index}].id`);\n\t\tassertNullableString(entry.parentId, `session.entries[${index}].parentId`);\n\t\tassertString(entry.timestamp, `session.entries[${index}].timestamp`);\n\t\tif (entry.type === \"message\") {\n\t\t\tassertRecord(entry.message, `session.entries[${index}].message`);\n\t\t}\n\t}\n}\n\nfunction assertPersistedSessionState(value: unknown): asserts value is PersistedSessionState {\n\tassertRecord(value, \"session\");\n\tassertString(value.sessionId, \"session.sessionId\");\n\tassertStaticContext(value.staticContext);\n\tassertSessionTreeEntries(value.entries);\n\tassertNullableString(value.leafId, \"session.leafId\");\n\tassertNumber(value.revision, \"session.revision\");\n\tassertNumber(value.createdAt, \"session.createdAt\");\n\tassertNumber(value.updatedAt, \"session.updatedAt\");\n}\n\nfunction parsePersistedSessionFile(raw: string, sourcePath: string): PersistedSessionFile {\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(raw);\n\t} catch (error) {\n\t\tthrow new Error(`Persisted session file contains invalid JSON: ${sourcePath}`, { cause: error });\n\t}\n\tassertRecord(parsed, \"persisted session file\");\n\tif (parsed.version !== PERSISTED_SESSION_VERSION) {\n\t\tthrow new Error(`Unsupported persisted session version in ${sourcePath}`);\n\t}\n\tassertPersistedSessionState(parsed.session);\n\treturn {\n\t\tversion: PERSISTED_SESSION_VERSION,\n\t\tsession: parsed.session,\n\t};\n}\n\nexport function loadPersistedSessions(sessionStoreDir: string): void {\n\tif (!existsSync(sessionStoreDir)) return;\n\tconst entries = readdirSync(sessionStoreDir, { withFileTypes: true });\n\tfor (const entry of entries) {\n\t\tif (!entry.isFile() || !entry.name.endsWith(\".json\")) continue;\n\t\tconst filePath = join(sessionStoreDir, entry.name);\n\t\tconst persisted = parsePersistedSessionFile(readFileSync(filePath, \"utf-8\"), filePath);\n\t\tconst expectedFileName = sessionFileName(persisted.session.sessionId);\n\t\tif (entry.name !== expectedFileName) {\n\t\t\tthrow new Error(`Persisted session file name does not match sessionId: ${filePath}`);\n\t\t}\n\t\trestoreSessionState(persisted.session);\n\t}\n}\n\nexport function savePersistedSession(sessionStoreDir: string, session: SessionState): void {\n\tmkdirSync(sessionStoreDir, { recursive: true });\n\tconst filePath = sessionPath(sessionStoreDir, session.sessionId);\n\tconst tempPath = `${filePath}.${randomUUID()}.tmp`;\n\tconst body: PersistedSessionFile = {\n\t\tversion: PERSISTED_SESSION_VERSION,\n\t\tsession: exportSessionState(session),\n\t};\n\ttry {\n\t\twriteFileSync(tempPath, JSON.stringify(body), \"utf-8\");\n\t\treplaceFileSync(tempPath, filePath);\n\t} catch (error) {\n\t\trmSync(tempPath, { force: true });\n\t\tthrow error;\n\t}\n}\n\nexport function deletePersistedSession(sessionStoreDir: string, sessionId: string): void {\n\trmSync(sessionPath(sessionStoreDir, sessionId), { force: true });\n}\n"]}
@@ -0,0 +1,65 @@
1
+ import { type SessionTreeEntry } from "@earendil-works/pi-agent-core";
2
+ import type { Message, Tool } from "@earendil-works/pi-ai";
3
+ export interface SessionStaticContext {
4
+ systemPrompt?: string;
5
+ tools?: Tool[];
6
+ }
7
+ export interface SessionState {
8
+ sessionId: string;
9
+ staticContext: SessionStaticContext | undefined;
10
+ staticContextHash: string;
11
+ entries: SessionTreeEntry[];
12
+ leafId: string | null;
13
+ messages: Message[];
14
+ revision: number;
15
+ createdAt: number;
16
+ updatedAt: number;
17
+ }
18
+ export interface SessionSummary {
19
+ sessionId: string;
20
+ staticContextHash: string;
21
+ treeHash: string;
22
+ messageCount: number;
23
+ entryCount: number;
24
+ leafId: string | null;
25
+ revision: number;
26
+ createdAt: number;
27
+ updatedAt: number;
28
+ }
29
+ export interface PersistedSessionState {
30
+ sessionId: string;
31
+ staticContext: SessionStaticContext | undefined;
32
+ entries: SessionTreeEntry[];
33
+ leafId: string | null;
34
+ revision: number;
35
+ createdAt: number;
36
+ updatedAt: number;
37
+ }
38
+ export declare function getOrCreateSession(sessionId: string): SessionState;
39
+ export declare function getSession(sessionId: string): SessionState | undefined;
40
+ export declare function hashSessionEntries(entries: SessionTreeEntry[]): string;
41
+ export declare function listSessions(): SessionSummary[];
42
+ export declare function exportSessionState(session: SessionState): PersistedSessionState;
43
+ export declare function restoreSessionState(persisted: PersistedSessionState): SessionState;
44
+ export declare function setStaticContext(sessionId: string, context: SessionStaticContext): SessionState;
45
+ export declare function getSessionBranch(session: SessionState): SessionTreeEntry[];
46
+ export declare function replaceSessionTree(sessionId: string, entries: SessionTreeEntry[], leafId: string | null): SessionState;
47
+ export declare function appendSessionEntries(sessionId: string, entries: SessionTreeEntry[], leafId: string | null): SessionState;
48
+ export declare function switchSessionLeaf(sessionId: string, leafId: string | null): SessionState;
49
+ export declare function appendCompactionEntry(sessionId: string, compaction: {
50
+ summary: string;
51
+ firstKeptEntryId: string;
52
+ tokensBefore: number;
53
+ details?: unknown;
54
+ }): {
55
+ session: SessionState;
56
+ entry: SessionTreeEntry;
57
+ };
58
+ export declare function getActiveMessages(sessionId: string): Message[];
59
+ export declare function appendMessages(sessionId: string, delta: Message[]): SessionState;
60
+ export declare function appendAssistantResponse(sessionId: string, message: Message): SessionState;
61
+ export declare function replaceMessages(sessionId: string, messages: Message[]): SessionState;
62
+ export declare function dropLastAssistantError(sessionId: string): boolean;
63
+ export declare function deleteSession(sessionId: string): boolean;
64
+ export declare function clearAllSessions(): void;
65
+ //# sourceMappingURL=session-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-store.d.ts","sourceRoot":"","sources":["../src/session-store.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash, randomUUID } from \"node:crypto\";\nimport { isDeepStrictEqual } from \"node:util\";\nimport { buildSessionContext, convertToLlm, type SessionTreeEntry } from \"@earendil-works/pi-agent-core\";\nimport type { Message, Tool } from \"@earendil-works/pi-ai\";\n\nexport interface SessionStaticContext {\n\tsystemPrompt?: string;\n\ttools?: Tool[];\n}\n\nexport interface SessionState {\n\tsessionId: string;\n\tstaticContext: SessionStaticContext | undefined;\n\tstaticContextHash: string;\n\tentries: SessionTreeEntry[];\n\tleafId: string | null;\n\tmessages: Message[];\n\trevision: number;\n\tcreatedAt: number;\n\tupdatedAt: number;\n}\n\nexport interface SessionSummary {\n\tsessionId: string;\n\tstaticContextHash: string;\n\ttreeHash: string;\n\tmessageCount: number;\n\tentryCount: number;\n\tleafId: string | null;\n\trevision: number;\n\tcreatedAt: number;\n\tupdatedAt: number;\n}\n\nexport interface PersistedSessionState {\n\tsessionId: string;\n\tstaticContext: SessionStaticContext | undefined;\n\tentries: SessionTreeEntry[];\n\tleafId: string | null;\n\trevision: number;\n\tcreatedAt: number;\n\tupdatedAt: number;\n}\n\nconst sessions = new Map<string, SessionState>();\n\nfunction hashStaticContext(ctx: SessionStaticContext | undefined): string {\n\tif (!ctx) return \"\";\n\tconst parts: string[] = [];\n\tif (ctx.systemPrompt !== undefined) parts.push(`sp:${ctx.systemPrompt}`);\n\tif (ctx.tools) {\n\t\tparts.push(\n\t\t\t`t:${JSON.stringify(ctx.tools.map((t) => ({ name: t.name, description: t.description, parameters: t.parameters })))}`,\n\t\t);\n\t}\n\treturn parts.join(\"|\");\n}\n\nexport function getOrCreateSession(sessionId: string): SessionState {\n\tlet session = sessions.get(sessionId);\n\tif (!session) {\n\t\tconst now = Date.now();\n\t\tsession = {\n\t\t\tsessionId,\n\t\t\tstaticContext: undefined,\n\t\t\tstaticContextHash: \"\",\n\t\t\tentries: [],\n\t\t\tleafId: null,\n\t\t\tmessages: [],\n\t\t\trevision: 0,\n\t\t\tcreatedAt: now,\n\t\t\tupdatedAt: now,\n\t\t};\n\t\tsessions.set(sessionId, session);\n\t}\n\treturn session;\n}\n\nexport function getSession(sessionId: string): SessionState | undefined {\n\treturn sessions.get(sessionId);\n}\n\nexport function hashSessionEntries(entries: SessionTreeEntry[]): string {\n\treturn createHash(\"sha256\").update(JSON.stringify(entries)).digest(\"hex\");\n}\n\nexport function listSessions(): SessionSummary[] {\n\treturn Array.from(sessions.values())\n\t\t.map((session) => ({\n\t\t\tsessionId: session.sessionId,\n\t\t\tstaticContextHash: session.staticContextHash,\n\t\t\ttreeHash: hashSessionEntries(session.entries),\n\t\t\tmessageCount: session.messages.length,\n\t\t\tentryCount: session.entries.length,\n\t\t\tleafId: session.leafId,\n\t\t\trevision: session.revision,\n\t\t\tcreatedAt: session.createdAt,\n\t\t\tupdatedAt: session.updatedAt,\n\t\t}))\n\t\t.sort((a, b) => b.updatedAt - a.updatedAt || a.sessionId.localeCompare(b.sessionId));\n}\n\nexport function exportSessionState(session: SessionState): PersistedSessionState {\n\treturn {\n\t\tsessionId: session.sessionId,\n\t\tstaticContext: session.staticContext,\n\t\tentries: session.entries.map((entry) => ({ ...entry })),\n\t\tleafId: session.leafId,\n\t\trevision: session.revision,\n\t\tcreatedAt: session.createdAt,\n\t\tupdatedAt: session.updatedAt,\n\t};\n}\n\nexport function restoreSessionState(persisted: PersistedSessionState): SessionState {\n\tassertValidLeaf(persisted.entries, persisted.leafId);\n\tconst session: SessionState = {\n\t\tsessionId: persisted.sessionId,\n\t\tstaticContext: persisted.staticContext,\n\t\tstaticContextHash: hashStaticContext(persisted.staticContext),\n\t\tentries: persisted.entries.map((entry) => ({ ...entry })),\n\t\tleafId: persisted.leafId,\n\t\tmessages: [],\n\t\trevision: persisted.revision,\n\t\tcreatedAt: persisted.createdAt,\n\t\tupdatedAt: persisted.updatedAt,\n\t};\n\trefreshActiveMessages(session);\n\tsessions.set(session.sessionId, session);\n\treturn session;\n}\n\nexport function setStaticContext(sessionId: string, context: SessionStaticContext): SessionState {\n\tconst session = getOrCreateSession(sessionId);\n\tconst newHash = hashStaticContext(context);\n\tsession.staticContext = context;\n\tsession.staticContextHash = newHash;\n\tsession.updatedAt = Date.now();\n\treturn session;\n}\n\nfunction entryToMessageEntry(message: Message, parentId: string | null): SessionTreeEntry {\n\treturn {\n\t\ttype: \"message\",\n\t\tid: randomUUID(),\n\t\tparentId,\n\t\ttimestamp: new Date(message.timestamp).toISOString(),\n\t\tmessage,\n\t};\n}\n\nexport function getSessionBranch(session: SessionState): SessionTreeEntry[] {\n\tconst byId = new Map(session.entries.map((entry) => [entry.id, entry]));\n\tconst branch: SessionTreeEntry[] = [];\n\tlet current = session.leafId ? byId.get(session.leafId) : undefined;\n\tconst seen = new Set<string>();\n\twhile (current) {\n\t\tif (seen.has(current.id)) {\n\t\t\tthrow new Error(`session tree contains a parent cycle at entry ${current.id}`);\n\t\t}\n\t\tseen.add(current.id);\n\t\tbranch.unshift(current);\n\t\tcurrent = current.parentId ? byId.get(current.parentId) : undefined;\n\t}\n\treturn branch;\n}\n\nfunction deriveActiveMessages(session: SessionState): Message[] {\n\tconst branch = getSessionBranch(session);\n\treturn convertToLlm(buildSessionContext(branch).messages);\n}\n\nfunction refreshActiveMessages(session: SessionState): void {\n\tsession.messages = deriveActiveMessages(session);\n}\n\nfunction assertValidLeaf(entries: SessionTreeEntry[], leafId: string | null): void {\n\tif (leafId === null) return;\n\tif (!entries.some((entry) => entry.id === leafId)) {\n\t\tthrow new Error(`leafId ${leafId} does not exist in session tree`);\n\t}\n}\n\nexport function replaceSessionTree(\n\tsessionId: string,\n\tentries: SessionTreeEntry[],\n\tleafId: string | null,\n): SessionState {\n\tassertValidLeaf(entries, leafId);\n\tconst session = getOrCreateSession(sessionId);\n\tsession.entries = entries.map((entry) => ({ ...entry }));\n\tsession.leafId = leafId;\n\tsession.revision++;\n\tsession.updatedAt = Date.now();\n\trefreshActiveMessages(session);\n\treturn session;\n}\n\nexport function appendSessionEntries(\n\tsessionId: string,\n\tentries: SessionTreeEntry[],\n\tleafId: string | null,\n): SessionState {\n\tconst session = getOrCreateSession(sessionId);\n\tconst knownEntries = new Map(session.entries.map((entry) => [entry.id, entry]));\n\tconst entriesToAppend: SessionTreeEntry[] = [];\n\tfor (const entry of entries) {\n\t\tconst knownEntry = knownEntries.get(entry.id);\n\t\tif (knownEntry) {\n\t\t\tif (!isDeepStrictEqual(knownEntry, entry)) {\n\t\t\t\tthrow new Error(`entry ${entry.id} already exists`);\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tif (entry.parentId !== null && !knownEntries.has(entry.parentId)) {\n\t\t\tthrow new Error(`parent entry ${entry.parentId} does not exist`);\n\t\t}\n\t\tconst entryToAppend = { ...entry };\n\t\tentriesToAppend.push(entryToAppend);\n\t\tknownEntries.set(entryToAppend.id, entryToAppend);\n\t}\n\tif (leafId !== null && !knownEntries.has(leafId)) {\n\t\tthrow new Error(`leafId ${leafId} does not exist in session tree`);\n\t}\n\tif (entriesToAppend.length === 0 && session.leafId === leafId) {\n\t\treturn session;\n\t}\n\tsession.entries.push(...entriesToAppend);\n\tassertValidLeaf(session.entries, leafId);\n\tsession.leafId = leafId;\n\tsession.revision++;\n\tsession.updatedAt = Date.now();\n\trefreshActiveMessages(session);\n\treturn session;\n}\n\nexport function switchSessionLeaf(sessionId: string, leafId: string | null): SessionState {\n\tconst session = getOrCreateSession(sessionId);\n\tassertValidLeaf(session.entries, leafId);\n\tsession.leafId = leafId;\n\tsession.revision++;\n\tsession.updatedAt = Date.now();\n\trefreshActiveMessages(session);\n\treturn session;\n}\n\nexport function appendCompactionEntry(\n\tsessionId: string,\n\tcompaction: { summary: string; firstKeptEntryId: string; tokensBefore: number; details?: unknown },\n): { session: SessionState; entry: SessionTreeEntry } {\n\tconst session = getOrCreateSession(sessionId);\n\tconst entry: SessionTreeEntry = {\n\t\ttype: \"compaction\",\n\t\tid: randomUUID(),\n\t\tparentId: session.leafId,\n\t\ttimestamp: new Date().toISOString(),\n\t\tsummary: compaction.summary,\n\t\tfirstKeptEntryId: compaction.firstKeptEntryId,\n\t\ttokensBefore: compaction.tokensBefore,\n\t\tdetails: compaction.details,\n\t};\n\tsession.entries.push(entry);\n\tsession.leafId = entry.id;\n\tsession.revision++;\n\tsession.updatedAt = Date.now();\n\trefreshActiveMessages(session);\n\treturn { session, entry };\n}\n\nexport function getActiveMessages(sessionId: string): Message[] {\n\tconst session = getSession(sessionId);\n\treturn session ? [...session.messages] : [];\n}\n\nexport function appendMessages(sessionId: string, delta: Message[]): SessionState {\n\tconst session = getOrCreateSession(sessionId);\n\tconst entries = delta.map((message) => {\n\t\tconst entry = entryToMessageEntry(message, session.leafId);\n\t\tsession.leafId = entry.id;\n\t\treturn entry;\n\t});\n\tsession.entries.push(...entries);\n\tsession.revision++;\n\tsession.updatedAt = Date.now();\n\trefreshActiveMessages(session);\n\treturn session;\n}\n\nexport function appendAssistantResponse(sessionId: string, message: Message): SessionState {\n\tconst session = getOrCreateSession(sessionId);\n\tsession.messages.push(message);\n\tsession.updatedAt = Date.now();\n\treturn session;\n}\n\nexport function replaceMessages(sessionId: string, messages: Message[]): SessionState {\n\tconst session = getOrCreateSession(sessionId);\n\tlet parentId: string | null = null;\n\tsession.entries = messages.map((message) => {\n\t\tconst entry = entryToMessageEntry(message, parentId);\n\t\tparentId = entry.id;\n\t\treturn entry;\n\t});\n\tsession.leafId = parentId;\n\tsession.revision++;\n\tsession.updatedAt = Date.now();\n\trefreshActiveMessages(session);\n\treturn session;\n}\n\nexport function dropLastAssistantError(sessionId: string): boolean {\n\tconst session = getSession(sessionId);\n\tif (!session) return false;\n\tconst leaf = session.leafId ? session.entries.find((entry) => entry.id === session.leafId) : undefined;\n\tif (leaf?.type !== \"message\" || leaf.message.role !== \"assistant\" || leaf.message.stopReason !== \"error\") {\n\t\treturn false;\n\t}\n\tsession.entries = session.entries.filter((entry) => entry.id !== leaf.id);\n\tsession.leafId = leaf.parentId;\n\tsession.revision++;\n\tsession.updatedAt = Date.now();\n\trefreshActiveMessages(session);\n\treturn true;\n}\n\nexport function deleteSession(sessionId: string): boolean {\n\treturn sessions.delete(sessionId);\n}\n\nexport function clearAllSessions(): void {\n\tsessions.clear();\n}\n"]}
@@ -0,0 +1,259 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { isDeepStrictEqual } from "node:util";
3
+ import { buildSessionContext, convertToLlm } from "@earendil-works/pi-agent-core";
4
+ const sessions = new Map();
5
+ function hashStaticContext(ctx) {
6
+ if (!ctx)
7
+ return "";
8
+ const parts = [];
9
+ if (ctx.systemPrompt !== undefined)
10
+ parts.push(`sp:${ctx.systemPrompt}`);
11
+ if (ctx.tools) {
12
+ parts.push(`t:${JSON.stringify(ctx.tools.map((t) => ({ name: t.name, description: t.description, parameters: t.parameters })))}`);
13
+ }
14
+ return parts.join("|");
15
+ }
16
+ export function getOrCreateSession(sessionId) {
17
+ let session = sessions.get(sessionId);
18
+ if (!session) {
19
+ const now = Date.now();
20
+ session = {
21
+ sessionId,
22
+ staticContext: undefined,
23
+ staticContextHash: "",
24
+ entries: [],
25
+ leafId: null,
26
+ messages: [],
27
+ revision: 0,
28
+ createdAt: now,
29
+ updatedAt: now,
30
+ };
31
+ sessions.set(sessionId, session);
32
+ }
33
+ return session;
34
+ }
35
+ export function getSession(sessionId) {
36
+ return sessions.get(sessionId);
37
+ }
38
+ export function hashSessionEntries(entries) {
39
+ return createHash("sha256").update(JSON.stringify(entries)).digest("hex");
40
+ }
41
+ export function listSessions() {
42
+ return Array.from(sessions.values())
43
+ .map((session) => ({
44
+ sessionId: session.sessionId,
45
+ staticContextHash: session.staticContextHash,
46
+ treeHash: hashSessionEntries(session.entries),
47
+ messageCount: session.messages.length,
48
+ entryCount: session.entries.length,
49
+ leafId: session.leafId,
50
+ revision: session.revision,
51
+ createdAt: session.createdAt,
52
+ updatedAt: session.updatedAt,
53
+ }))
54
+ .sort((a, b) => b.updatedAt - a.updatedAt || a.sessionId.localeCompare(b.sessionId));
55
+ }
56
+ export function exportSessionState(session) {
57
+ return {
58
+ sessionId: session.sessionId,
59
+ staticContext: session.staticContext,
60
+ entries: session.entries.map((entry) => ({ ...entry })),
61
+ leafId: session.leafId,
62
+ revision: session.revision,
63
+ createdAt: session.createdAt,
64
+ updatedAt: session.updatedAt,
65
+ };
66
+ }
67
+ export function restoreSessionState(persisted) {
68
+ assertValidLeaf(persisted.entries, persisted.leafId);
69
+ const session = {
70
+ sessionId: persisted.sessionId,
71
+ staticContext: persisted.staticContext,
72
+ staticContextHash: hashStaticContext(persisted.staticContext),
73
+ entries: persisted.entries.map((entry) => ({ ...entry })),
74
+ leafId: persisted.leafId,
75
+ messages: [],
76
+ revision: persisted.revision,
77
+ createdAt: persisted.createdAt,
78
+ updatedAt: persisted.updatedAt,
79
+ };
80
+ refreshActiveMessages(session);
81
+ sessions.set(session.sessionId, session);
82
+ return session;
83
+ }
84
+ export function setStaticContext(sessionId, context) {
85
+ const session = getOrCreateSession(sessionId);
86
+ const newHash = hashStaticContext(context);
87
+ session.staticContext = context;
88
+ session.staticContextHash = newHash;
89
+ session.updatedAt = Date.now();
90
+ return session;
91
+ }
92
+ function entryToMessageEntry(message, parentId) {
93
+ return {
94
+ type: "message",
95
+ id: randomUUID(),
96
+ parentId,
97
+ timestamp: new Date(message.timestamp).toISOString(),
98
+ message,
99
+ };
100
+ }
101
+ export function getSessionBranch(session) {
102
+ const byId = new Map(session.entries.map((entry) => [entry.id, entry]));
103
+ const branch = [];
104
+ let current = session.leafId ? byId.get(session.leafId) : undefined;
105
+ const seen = new Set();
106
+ while (current) {
107
+ if (seen.has(current.id)) {
108
+ throw new Error(`session tree contains a parent cycle at entry ${current.id}`);
109
+ }
110
+ seen.add(current.id);
111
+ branch.unshift(current);
112
+ current = current.parentId ? byId.get(current.parentId) : undefined;
113
+ }
114
+ return branch;
115
+ }
116
+ function deriveActiveMessages(session) {
117
+ const branch = getSessionBranch(session);
118
+ return convertToLlm(buildSessionContext(branch).messages);
119
+ }
120
+ function refreshActiveMessages(session) {
121
+ session.messages = deriveActiveMessages(session);
122
+ }
123
+ function assertValidLeaf(entries, leafId) {
124
+ if (leafId === null)
125
+ return;
126
+ if (!entries.some((entry) => entry.id === leafId)) {
127
+ throw new Error(`leafId ${leafId} does not exist in session tree`);
128
+ }
129
+ }
130
+ export function replaceSessionTree(sessionId, entries, leafId) {
131
+ assertValidLeaf(entries, leafId);
132
+ const session = getOrCreateSession(sessionId);
133
+ session.entries = entries.map((entry) => ({ ...entry }));
134
+ session.leafId = leafId;
135
+ session.revision++;
136
+ session.updatedAt = Date.now();
137
+ refreshActiveMessages(session);
138
+ return session;
139
+ }
140
+ export function appendSessionEntries(sessionId, entries, leafId) {
141
+ const session = getOrCreateSession(sessionId);
142
+ const knownEntries = new Map(session.entries.map((entry) => [entry.id, entry]));
143
+ const entriesToAppend = [];
144
+ for (const entry of entries) {
145
+ const knownEntry = knownEntries.get(entry.id);
146
+ if (knownEntry) {
147
+ if (!isDeepStrictEqual(knownEntry, entry)) {
148
+ throw new Error(`entry ${entry.id} already exists`);
149
+ }
150
+ continue;
151
+ }
152
+ if (entry.parentId !== null && !knownEntries.has(entry.parentId)) {
153
+ throw new Error(`parent entry ${entry.parentId} does not exist`);
154
+ }
155
+ const entryToAppend = { ...entry };
156
+ entriesToAppend.push(entryToAppend);
157
+ knownEntries.set(entryToAppend.id, entryToAppend);
158
+ }
159
+ if (leafId !== null && !knownEntries.has(leafId)) {
160
+ throw new Error(`leafId ${leafId} does not exist in session tree`);
161
+ }
162
+ if (entriesToAppend.length === 0 && session.leafId === leafId) {
163
+ return session;
164
+ }
165
+ session.entries.push(...entriesToAppend);
166
+ assertValidLeaf(session.entries, leafId);
167
+ session.leafId = leafId;
168
+ session.revision++;
169
+ session.updatedAt = Date.now();
170
+ refreshActiveMessages(session);
171
+ return session;
172
+ }
173
+ export function switchSessionLeaf(sessionId, leafId) {
174
+ const session = getOrCreateSession(sessionId);
175
+ assertValidLeaf(session.entries, leafId);
176
+ session.leafId = leafId;
177
+ session.revision++;
178
+ session.updatedAt = Date.now();
179
+ refreshActiveMessages(session);
180
+ return session;
181
+ }
182
+ export function appendCompactionEntry(sessionId, compaction) {
183
+ const session = getOrCreateSession(sessionId);
184
+ const entry = {
185
+ type: "compaction",
186
+ id: randomUUID(),
187
+ parentId: session.leafId,
188
+ timestamp: new Date().toISOString(),
189
+ summary: compaction.summary,
190
+ firstKeptEntryId: compaction.firstKeptEntryId,
191
+ tokensBefore: compaction.tokensBefore,
192
+ details: compaction.details,
193
+ };
194
+ session.entries.push(entry);
195
+ session.leafId = entry.id;
196
+ session.revision++;
197
+ session.updatedAt = Date.now();
198
+ refreshActiveMessages(session);
199
+ return { session, entry };
200
+ }
201
+ export function getActiveMessages(sessionId) {
202
+ const session = getSession(sessionId);
203
+ return session ? [...session.messages] : [];
204
+ }
205
+ export function appendMessages(sessionId, delta) {
206
+ const session = getOrCreateSession(sessionId);
207
+ const entries = delta.map((message) => {
208
+ const entry = entryToMessageEntry(message, session.leafId);
209
+ session.leafId = entry.id;
210
+ return entry;
211
+ });
212
+ session.entries.push(...entries);
213
+ session.revision++;
214
+ session.updatedAt = Date.now();
215
+ refreshActiveMessages(session);
216
+ return session;
217
+ }
218
+ export function appendAssistantResponse(sessionId, message) {
219
+ const session = getOrCreateSession(sessionId);
220
+ session.messages.push(message);
221
+ session.updatedAt = Date.now();
222
+ return session;
223
+ }
224
+ export function replaceMessages(sessionId, messages) {
225
+ const session = getOrCreateSession(sessionId);
226
+ let parentId = null;
227
+ session.entries = messages.map((message) => {
228
+ const entry = entryToMessageEntry(message, parentId);
229
+ parentId = entry.id;
230
+ return entry;
231
+ });
232
+ session.leafId = parentId;
233
+ session.revision++;
234
+ session.updatedAt = Date.now();
235
+ refreshActiveMessages(session);
236
+ return session;
237
+ }
238
+ export function dropLastAssistantError(sessionId) {
239
+ const session = getSession(sessionId);
240
+ if (!session)
241
+ return false;
242
+ const leaf = session.leafId ? session.entries.find((entry) => entry.id === session.leafId) : undefined;
243
+ if (leaf?.type !== "message" || leaf.message.role !== "assistant" || leaf.message.stopReason !== "error") {
244
+ return false;
245
+ }
246
+ session.entries = session.entries.filter((entry) => entry.id !== leaf.id);
247
+ session.leafId = leaf.parentId;
248
+ session.revision++;
249
+ session.updatedAt = Date.now();
250
+ refreshActiveMessages(session);
251
+ return true;
252
+ }
253
+ export function deleteSession(sessionId) {
254
+ return sessions.delete(sessionId);
255
+ }
256
+ export function clearAllSessions() {
257
+ sessions.clear();
258
+ }
259
+ //# sourceMappingURL=session-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-store.js","sourceRoot":"","sources":["../src/session-store.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash, randomUUID } from \"node:crypto\";\nimport { isDeepStrictEqual } from \"node:util\";\nimport { buildSessionContext, convertToLlm, type SessionTreeEntry } from \"@earendil-works/pi-agent-core\";\nimport type { Message, Tool } from \"@earendil-works/pi-ai\";\n\nexport interface SessionStaticContext {\n\tsystemPrompt?: string;\n\ttools?: Tool[];\n}\n\nexport interface SessionState {\n\tsessionId: string;\n\tstaticContext: SessionStaticContext | undefined;\n\tstaticContextHash: string;\n\tentries: SessionTreeEntry[];\n\tleafId: string | null;\n\tmessages: Message[];\n\trevision: number;\n\tcreatedAt: number;\n\tupdatedAt: number;\n}\n\nexport interface SessionSummary {\n\tsessionId: string;\n\tstaticContextHash: string;\n\ttreeHash: string;\n\tmessageCount: number;\n\tentryCount: number;\n\tleafId: string | null;\n\trevision: number;\n\tcreatedAt: number;\n\tupdatedAt: number;\n}\n\nexport interface PersistedSessionState {\n\tsessionId: string;\n\tstaticContext: SessionStaticContext | undefined;\n\tentries: SessionTreeEntry[];\n\tleafId: string | null;\n\trevision: number;\n\tcreatedAt: number;\n\tupdatedAt: number;\n}\n\nconst sessions = new Map<string, SessionState>();\n\nfunction hashStaticContext(ctx: SessionStaticContext | undefined): string {\n\tif (!ctx) return \"\";\n\tconst parts: string[] = [];\n\tif (ctx.systemPrompt !== undefined) parts.push(`sp:${ctx.systemPrompt}`);\n\tif (ctx.tools) {\n\t\tparts.push(\n\t\t\t`t:${JSON.stringify(ctx.tools.map((t) => ({ name: t.name, description: t.description, parameters: t.parameters })))}`,\n\t\t);\n\t}\n\treturn parts.join(\"|\");\n}\n\nexport function getOrCreateSession(sessionId: string): SessionState {\n\tlet session = sessions.get(sessionId);\n\tif (!session) {\n\t\tconst now = Date.now();\n\t\tsession = {\n\t\t\tsessionId,\n\t\t\tstaticContext: undefined,\n\t\t\tstaticContextHash: \"\",\n\t\t\tentries: [],\n\t\t\tleafId: null,\n\t\t\tmessages: [],\n\t\t\trevision: 0,\n\t\t\tcreatedAt: now,\n\t\t\tupdatedAt: now,\n\t\t};\n\t\tsessions.set(sessionId, session);\n\t}\n\treturn session;\n}\n\nexport function getSession(sessionId: string): SessionState | undefined {\n\treturn sessions.get(sessionId);\n}\n\nexport function hashSessionEntries(entries: SessionTreeEntry[]): string {\n\treturn createHash(\"sha256\").update(JSON.stringify(entries)).digest(\"hex\");\n}\n\nexport function listSessions(): SessionSummary[] {\n\treturn Array.from(sessions.values())\n\t\t.map((session) => ({\n\t\t\tsessionId: session.sessionId,\n\t\t\tstaticContextHash: session.staticContextHash,\n\t\t\ttreeHash: hashSessionEntries(session.entries),\n\t\t\tmessageCount: session.messages.length,\n\t\t\tentryCount: session.entries.length,\n\t\t\tleafId: session.leafId,\n\t\t\trevision: session.revision,\n\t\t\tcreatedAt: session.createdAt,\n\t\t\tupdatedAt: session.updatedAt,\n\t\t}))\n\t\t.sort((a, b) => b.updatedAt - a.updatedAt || a.sessionId.localeCompare(b.sessionId));\n}\n\nexport function exportSessionState(session: SessionState): PersistedSessionState {\n\treturn {\n\t\tsessionId: session.sessionId,\n\t\tstaticContext: session.staticContext,\n\t\tentries: session.entries.map((entry) => ({ ...entry })),\n\t\tleafId: session.leafId,\n\t\trevision: session.revision,\n\t\tcreatedAt: session.createdAt,\n\t\tupdatedAt: session.updatedAt,\n\t};\n}\n\nexport function restoreSessionState(persisted: PersistedSessionState): SessionState {\n\tassertValidLeaf(persisted.entries, persisted.leafId);\n\tconst session: SessionState = {\n\t\tsessionId: persisted.sessionId,\n\t\tstaticContext: persisted.staticContext,\n\t\tstaticContextHash: hashStaticContext(persisted.staticContext),\n\t\tentries: persisted.entries.map((entry) => ({ ...entry })),\n\t\tleafId: persisted.leafId,\n\t\tmessages: [],\n\t\trevision: persisted.revision,\n\t\tcreatedAt: persisted.createdAt,\n\t\tupdatedAt: persisted.updatedAt,\n\t};\n\trefreshActiveMessages(session);\n\tsessions.set(session.sessionId, session);\n\treturn session;\n}\n\nexport function setStaticContext(sessionId: string, context: SessionStaticContext): SessionState {\n\tconst session = getOrCreateSession(sessionId);\n\tconst newHash = hashStaticContext(context);\n\tsession.staticContext = context;\n\tsession.staticContextHash = newHash;\n\tsession.updatedAt = Date.now();\n\treturn session;\n}\n\nfunction entryToMessageEntry(message: Message, parentId: string | null): SessionTreeEntry {\n\treturn {\n\t\ttype: \"message\",\n\t\tid: randomUUID(),\n\t\tparentId,\n\t\ttimestamp: new Date(message.timestamp).toISOString(),\n\t\tmessage,\n\t};\n}\n\nexport function getSessionBranch(session: SessionState): SessionTreeEntry[] {\n\tconst byId = new Map(session.entries.map((entry) => [entry.id, entry]));\n\tconst branch: SessionTreeEntry[] = [];\n\tlet current = session.leafId ? byId.get(session.leafId) : undefined;\n\tconst seen = new Set<string>();\n\twhile (current) {\n\t\tif (seen.has(current.id)) {\n\t\t\tthrow new Error(`session tree contains a parent cycle at entry ${current.id}`);\n\t\t}\n\t\tseen.add(current.id);\n\t\tbranch.unshift(current);\n\t\tcurrent = current.parentId ? byId.get(current.parentId) : undefined;\n\t}\n\treturn branch;\n}\n\nfunction deriveActiveMessages(session: SessionState): Message[] {\n\tconst branch = getSessionBranch(session);\n\treturn convertToLlm(buildSessionContext(branch).messages);\n}\n\nfunction refreshActiveMessages(session: SessionState): void {\n\tsession.messages = deriveActiveMessages(session);\n}\n\nfunction assertValidLeaf(entries: SessionTreeEntry[], leafId: string | null): void {\n\tif (leafId === null) return;\n\tif (!entries.some((entry) => entry.id === leafId)) {\n\t\tthrow new Error(`leafId ${leafId} does not exist in session tree`);\n\t}\n}\n\nexport function replaceSessionTree(\n\tsessionId: string,\n\tentries: SessionTreeEntry[],\n\tleafId: string | null,\n): SessionState {\n\tassertValidLeaf(entries, leafId);\n\tconst session = getOrCreateSession(sessionId);\n\tsession.entries = entries.map((entry) => ({ ...entry }));\n\tsession.leafId = leafId;\n\tsession.revision++;\n\tsession.updatedAt = Date.now();\n\trefreshActiveMessages(session);\n\treturn session;\n}\n\nexport function appendSessionEntries(\n\tsessionId: string,\n\tentries: SessionTreeEntry[],\n\tleafId: string | null,\n): SessionState {\n\tconst session = getOrCreateSession(sessionId);\n\tconst knownEntries = new Map(session.entries.map((entry) => [entry.id, entry]));\n\tconst entriesToAppend: SessionTreeEntry[] = [];\n\tfor (const entry of entries) {\n\t\tconst knownEntry = knownEntries.get(entry.id);\n\t\tif (knownEntry) {\n\t\t\tif (!isDeepStrictEqual(knownEntry, entry)) {\n\t\t\t\tthrow new Error(`entry ${entry.id} already exists`);\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tif (entry.parentId !== null && !knownEntries.has(entry.parentId)) {\n\t\t\tthrow new Error(`parent entry ${entry.parentId} does not exist`);\n\t\t}\n\t\tconst entryToAppend = { ...entry };\n\t\tentriesToAppend.push(entryToAppend);\n\t\tknownEntries.set(entryToAppend.id, entryToAppend);\n\t}\n\tif (leafId !== null && !knownEntries.has(leafId)) {\n\t\tthrow new Error(`leafId ${leafId} does not exist in session tree`);\n\t}\n\tif (entriesToAppend.length === 0 && session.leafId === leafId) {\n\t\treturn session;\n\t}\n\tsession.entries.push(...entriesToAppend);\n\tassertValidLeaf(session.entries, leafId);\n\tsession.leafId = leafId;\n\tsession.revision++;\n\tsession.updatedAt = Date.now();\n\trefreshActiveMessages(session);\n\treturn session;\n}\n\nexport function switchSessionLeaf(sessionId: string, leafId: string | null): SessionState {\n\tconst session = getOrCreateSession(sessionId);\n\tassertValidLeaf(session.entries, leafId);\n\tsession.leafId = leafId;\n\tsession.revision++;\n\tsession.updatedAt = Date.now();\n\trefreshActiveMessages(session);\n\treturn session;\n}\n\nexport function appendCompactionEntry(\n\tsessionId: string,\n\tcompaction: { summary: string; firstKeptEntryId: string; tokensBefore: number; details?: unknown },\n): { session: SessionState; entry: SessionTreeEntry } {\n\tconst session = getOrCreateSession(sessionId);\n\tconst entry: SessionTreeEntry = {\n\t\ttype: \"compaction\",\n\t\tid: randomUUID(),\n\t\tparentId: session.leafId,\n\t\ttimestamp: new Date().toISOString(),\n\t\tsummary: compaction.summary,\n\t\tfirstKeptEntryId: compaction.firstKeptEntryId,\n\t\ttokensBefore: compaction.tokensBefore,\n\t\tdetails: compaction.details,\n\t};\n\tsession.entries.push(entry);\n\tsession.leafId = entry.id;\n\tsession.revision++;\n\tsession.updatedAt = Date.now();\n\trefreshActiveMessages(session);\n\treturn { session, entry };\n}\n\nexport function getActiveMessages(sessionId: string): Message[] {\n\tconst session = getSession(sessionId);\n\treturn session ? [...session.messages] : [];\n}\n\nexport function appendMessages(sessionId: string, delta: Message[]): SessionState {\n\tconst session = getOrCreateSession(sessionId);\n\tconst entries = delta.map((message) => {\n\t\tconst entry = entryToMessageEntry(message, session.leafId);\n\t\tsession.leafId = entry.id;\n\t\treturn entry;\n\t});\n\tsession.entries.push(...entries);\n\tsession.revision++;\n\tsession.updatedAt = Date.now();\n\trefreshActiveMessages(session);\n\treturn session;\n}\n\nexport function appendAssistantResponse(sessionId: string, message: Message): SessionState {\n\tconst session = getOrCreateSession(sessionId);\n\tsession.messages.push(message);\n\tsession.updatedAt = Date.now();\n\treturn session;\n}\n\nexport function replaceMessages(sessionId: string, messages: Message[]): SessionState {\n\tconst session = getOrCreateSession(sessionId);\n\tlet parentId: string | null = null;\n\tsession.entries = messages.map((message) => {\n\t\tconst entry = entryToMessageEntry(message, parentId);\n\t\tparentId = entry.id;\n\t\treturn entry;\n\t});\n\tsession.leafId = parentId;\n\tsession.revision++;\n\tsession.updatedAt = Date.now();\n\trefreshActiveMessages(session);\n\treturn session;\n}\n\nexport function dropLastAssistantError(sessionId: string): boolean {\n\tconst session = getSession(sessionId);\n\tif (!session) return false;\n\tconst leaf = session.leafId ? session.entries.find((entry) => entry.id === session.leafId) : undefined;\n\tif (leaf?.type !== \"message\" || leaf.message.role !== \"assistant\" || leaf.message.stopReason !== \"error\") {\n\t\treturn false;\n\t}\n\tsession.entries = session.entries.filter((entry) => entry.id !== leaf.id);\n\tsession.leafId = leaf.parentId;\n\tsession.revision++;\n\tsession.updatedAt = Date.now();\n\trefreshActiveMessages(session);\n\treturn true;\n}\n\nexport function deleteSession(sessionId: string): boolean {\n\treturn sessions.delete(sessionId);\n}\n\nexport function clearAllSessions(): void {\n\tsessions.clear();\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@averyyy/pi-server",
3
+ "version": "0.80.3-piclient.2",
4
+ "description": "Proxy server for pi-client that stores session state and forwards incremental requests to upstream LLM providers",
5
+ "type": "module",
6
+ "bin": {
7
+ "pi-server": "dist/cli.js"
8
+ },
9
+ "main": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "README.md",
20
+ "CHANGELOG.md"
21
+ ],
22
+ "scripts": {
23
+ "clean": "shx rm -rf dist",
24
+ "build": "tsgo -p tsconfig.build.json && shx chmod +x dist/cli.js",
25
+ "test": "vitest --run",
26
+ "prepublishOnly": "npm run clean && npm run build"
27
+ },
28
+ "dependencies": {
29
+ "@earendil-works/pi-ai": "npm:@averyyy/pi-ai@0.80.3-piclient.2",
30
+ "@earendil-works/pi-agent-core": "npm:@averyyy/pi-agent-core@0.80.3-piclient.2"
31
+ },
32
+ "keywords": [
33
+ "pi-server",
34
+ "proxy",
35
+ "llm",
36
+ "agent"
37
+ ],
38
+ "author": "Pi Contributors",
39
+ "license": "MIT",
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "git+https://github.com/earendil-works/pi.git",
43
+ "directory": "packages/pi-server"
44
+ },
45
+ "engines": {
46
+ "node": ">=22.19.0"
47
+ },
48
+ "devDependencies": {
49
+ "@types/node": "24.12.4",
50
+ "shx": "0.4.0",
51
+ "typescript": "5.9.3",
52
+ "vitest": "4.1.9"
53
+ }
54
+ }