Files
pi-context-manager/src/packet.test.ts
2026-04-10 23:06:52 +01:00

131 lines
3.9 KiB
TypeScript

import test from "node:test";
import assert from "node:assert/strict";
import { resolvePolicy } from "./config.ts";
import { buildContextPacket } from "./packet.ts";
import { createEmptyLedger, mergeCandidates, type MemoryCandidate } from "./ledger.ts";
const baseCandidate: Omit<MemoryCandidate, "kind" | "subject" | "text"> = {
scope: "session",
sourceEntryId: "seed",
sourceType: "user",
timestamp: 1,
confidence: 1,
};
function estimateTokens(text: string) {
return Math.ceil(text.length / 4);
}
function memory(candidate: Pick<MemoryCandidate, "kind" | "subject" | "text"> & Partial<Omit<MemoryCandidate, "kind" | "subject" | "text">>): MemoryCandidate {
return {
...baseCandidate,
...candidate,
sourceEntryId: candidate.sourceEntryId ?? candidate.subject,
};
}
function buildPolicy(packetTokenCap: number) {
return {
...resolvePolicy({ mode: "balanced", contextWindow: 200_000 }),
packetTokenCap,
};
}
test("buildContextPacket keeps top-ranked facts from a section when the cap is tight", () => {
const expected = [
"## Active goal",
"- Keep packets compact.",
"",
"## Constraints",
"- Preserve the highest-priority constraint.",
"",
"## Key decisions",
"- Render selected sections in stable order.",
].join("\n");
const policy = buildPolicy(estimateTokens(expected));
const ledger = mergeCandidates(createEmptyLedger(), [
memory({ kind: "goal", subject: "goal", text: "Keep packets compact." }),
memory({ kind: "constraint", subject: "constraint-a", text: "Preserve the highest-priority constraint.", confidence: 1, timestamp: 3 }),
memory({
kind: "constraint",
subject: "constraint-b",
text: "Avoid dropping every constraint just because one extra bullet is too large for a tight packet cap.",
confidence: 0.6,
timestamp: 2,
}),
memory({
kind: "decision",
subject: "decision-a",
text: "Render selected sections in stable order.",
confidence: 0.9,
timestamp: 4,
sourceType: "assistant",
}),
]);
const packet = buildContextPacket(ledger, policy);
assert.equal(packet.text, expected);
assert.equal(packet.estimatedTokens, policy.packetTokenCap);
});
test("buildContextPacket uses cross-kind weights when only one lower-priority section can fit", () => {
const expected = [
"## Active goal",
"- Keep the agent moving.",
"",
"## Current task",
"- Fix packet trimming.",
].join("\n");
const policy = buildPolicy(estimateTokens(expected));
const ledger = mergeCandidates(createEmptyLedger(), [
memory({ kind: "goal", subject: "goal", text: "Keep the agent moving." }),
memory({
kind: "decision",
subject: "decision-a",
text: "Keep logs concise.",
confidence: 1,
timestamp: 2,
sourceType: "assistant",
}),
memory({
kind: "activeTask",
subject: "task-a",
text: "Fix packet trimming.",
confidence: 1,
timestamp: 2,
sourceType: "assistant",
}),
]);
const packet = buildContextPacket(ledger, policy);
assert.equal(packet.text, expected);
assert.equal(packet.estimatedTokens, policy.packetTokenCap);
});
test("buildContextPacket keeps a goal ahead of newer low-priority facts at realistic timestamp scales", () => {
const expected = [
"## Active goal",
"- Keep the agent on track.",
].join("\n");
const policy = buildPolicy(estimateTokens(expected));
const ledger = mergeCandidates(createEmptyLedger(), [
memory({ kind: "goal", subject: "goal", text: "Keep the agent on track.", timestamp: 1_000_000 }),
memory({
kind: "relevantFile",
subject: "runtime-file",
text: "src/runtime.ts",
timestamp: 10_000_000,
confidence: 1,
sourceType: "assistant",
scope: "branch",
}),
]);
const packet = buildContextPacket(ledger, policy);
assert.equal(packet.text, expected);
assert.equal(packet.estimatedTokens, policy.packetTokenCap);
});