pi-precompact 0.0.0-stage → 0.2.0
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.
- package/CHANGELOG.md +28 -0
- package/CONTRIBUTING.md +152 -0
- package/LICENSE +21 -0
- package/README.md +219 -2
- package/package.json +71 -4
- package/src/card.ts +194 -0
- package/src/controller.ts +446 -0
- package/src/files.ts +24 -0
- package/src/footer.ts +471 -0
- package/src/format.ts +20 -0
- package/src/index.ts +107 -0
- package/src/native.ts +47 -0
- package/src/validation.ts +82 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
buildSessionProjection,
|
|
4
|
+
type CompactionResult,
|
|
5
|
+
estimateTokens,
|
|
6
|
+
type SessionEntry,
|
|
7
|
+
SessionManager,
|
|
8
|
+
} from "@earendil-works/pi-coding-agent";
|
|
9
|
+
import type { Preparation } from "./native.ts";
|
|
10
|
+
|
|
11
|
+
export interface Snapshot {
|
|
12
|
+
sessionId: string;
|
|
13
|
+
identity: string;
|
|
14
|
+
entryCount: number;
|
|
15
|
+
branchHash: string;
|
|
16
|
+
preparation: Preparation;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function fingerprint(value: unknown): string {
|
|
20
|
+
return createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function snapshotValid(
|
|
24
|
+
snapshot: Snapshot,
|
|
25
|
+
sessionId: string,
|
|
26
|
+
identity: string,
|
|
27
|
+
branch: SessionEntry[],
|
|
28
|
+
): boolean {
|
|
29
|
+
if (
|
|
30
|
+
sessionId !== snapshot.sessionId ||
|
|
31
|
+
identity !== snapshot.identity ||
|
|
32
|
+
branch.length < snapshot.entryCount
|
|
33
|
+
)
|
|
34
|
+
return false;
|
|
35
|
+
if (fingerprint(branch.slice(0, snapshot.entryCount)) !== snapshot.branchHash) return false;
|
|
36
|
+
// Even an edit to a retained entry can change a split-turn summary. Fail closed.
|
|
37
|
+
return !branch
|
|
38
|
+
.slice(snapshot.entryCount)
|
|
39
|
+
.some(
|
|
40
|
+
(entry) =>
|
|
41
|
+
entry.type === "context_edit" ||
|
|
42
|
+
entry.type === "compaction" ||
|
|
43
|
+
entry.type === "branch_summary" ||
|
|
44
|
+
entry.type === "model_change",
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Preview exactly the entry Pi will append, preserving native messages and attachments. */
|
|
49
|
+
export function validateResult(
|
|
50
|
+
snapshot: Snapshot,
|
|
51
|
+
result: CompactionResult,
|
|
52
|
+
branch: SessionEntry[],
|
|
53
|
+
contextWindow: number,
|
|
54
|
+
tokensBefore: number,
|
|
55
|
+
): CompactionResult | undefined {
|
|
56
|
+
if (!result.summary.trim() || result.firstKeptEntryId !== snapshot.preparation.firstKeptEntryId) return;
|
|
57
|
+
if (!branch.some((entry) => entry.id === result.firstKeptEntryId)) return;
|
|
58
|
+
const ceiling = contextWindow - snapshot.preparation.settings.reserveTokens;
|
|
59
|
+
const tailLimit = Math.min(
|
|
60
|
+
Math.max(2 * snapshot.preparation.settings.keepRecentTokens, 30_000),
|
|
61
|
+
ceiling / 4,
|
|
62
|
+
);
|
|
63
|
+
const addedIds = new Set(branch.slice(snapshot.entryCount).map((entry) => entry.id));
|
|
64
|
+
const growth = buildSessionProjection(branch)
|
|
65
|
+
.entries.filter((entry) => addedIds.has(entry.sourceEntry.id))
|
|
66
|
+
.flatMap((entry) => entry.messages)
|
|
67
|
+
.reduce((sum, message) => sum + estimateTokens(message), 0);
|
|
68
|
+
if (growth > tailLimit) return;
|
|
69
|
+
const preview = SessionManager.inMemory(undefined, undefined, structuredClone(branch));
|
|
70
|
+
preview.appendCompaction(result.summary, result.firstKeptEntryId, tokensBefore, result.details, true);
|
|
71
|
+
const estimatedTokensAfter = preview
|
|
72
|
+
.buildSessionProjection()
|
|
73
|
+
.messages.reduce((sum, message) => sum + estimateTokens(message), 0);
|
|
74
|
+
// Token estimates are heuristic. Leave extra room in addition to Pi's native reserve.
|
|
75
|
+
if (
|
|
76
|
+
!Number.isFinite(estimatedTokensAfter) ||
|
|
77
|
+
estimatedTokensAfter * 1.15 >= ceiling ||
|
|
78
|
+
estimatedTokensAfter >= tokensBefore
|
|
79
|
+
)
|
|
80
|
+
return;
|
|
81
|
+
return { ...result, tokensBefore, estimatedTokensAfter };
|
|
82
|
+
}
|