fix: align compaction cuts and native usage accounting

This commit is contained in:
amosblomqvist
2026-10-24 19:05:02 +03:00
parent 50a2db775d
commit 624c91a437
45 changed files with 775 additions and 2329 deletions
+32 -1
View File
@@ -32,7 +32,7 @@ const {
} = await import("../src/server/compaction.ts");
import type { CompactionDeps } from "../src/server/compaction.ts";
const { subscribe } = await import("../src/core/events.ts");
const { activeCompactionOf, newUserMessage, COMPACTED_HISTORY_HEADER } = await import("../src/core/index.ts");
const { activeCompactionOf, newUserMessage, measuredContextTokens, COMPACTED_HISTORY_HEADER } = await import("../src/core/index.ts");
import type { LhMessage } from "../src/core/index.ts";
after(() => {
@@ -204,6 +204,37 @@ describe("the compaction service", () => {
kickCompaction(() => null, chatId);
});
it("a usage-heavy zero cut does not spend or append a marker", async () => {
const chat = chatStore.createChat("no-progress fixture");
const assistant = { id: "measured", role: "assistant", content: [{ type: "text", text: "ok" }], stopReason: "stop", usage: { input: 120_000, output: 1, cacheRead: 0, cacheWrite: 0, totalTokens: 120_001 }, timestamp: 1 } as LhMessage;
chatStore.saveMessages(chat.id, [newUserMessage("tiny visible history"), assistant]);
const before = summarizeCalls;
await ensureCompacted(depsOf(chat.id, 40_000), chat.id);
assert.equal(summarizeCalls, before);
assert.equal(activeCompactionOf(chatStore.getChat(chat.id)!.messages), null);
assert.equal(measuredContextTokens(chatStore.getChat(chat.id)!.messages), 120_001);
});
it("starting/failing compaction preserves the meter; committing its marker clears it, regardless of summary usage", async () => {
const id = overThresholdChat("meter validity fixture");
const assistant = { id: "measured", role: "assistant", content: [{ type: "text", text: "ok" }], stopReason: "stop", usage: { input: 120_000, output: 1, cacheRead: 0, cacheWrite: 0, totalTokens: 120_001 }, timestamp: 1 } as LhMessage;
chatStore.saveMessages(id, [...chatStore.getChat(id)!.messages, assistant]);
let release!: () => void;
const gate = new Promise<void>((done) => { release = done; });
const deps = depsOf(id, 40_000);
const running = ensureCompacted({ ...deps, summarize: async () => {
await gate;
throw new Error("fixture failure");
} }, id);
assert.equal(measuredContextTokens(chatStore.getChat(id)!.messages), 120_001);
release(); await running;
assert.equal(measuredContextTokens(chatStore.getChat(id)!.messages), 120_001);
await ensureCompacted(deps, id);
const record = chatStore.getChat(id)!;
assert.ok(record.turns!.at(-1)!.steps[0]!.inputTokens! > 0);
assert.equal(measuredContextTokens(record.messages), null);
});
it("a summarizer failure leaves the record untouched and the gate re-arms", async () => {
const chatId = overThresholdChat("compact: fail");
const before = summarizeCalls;