feat: implemented token tracking and metadata options for compaction summaries

- Added `CompactionSummaryMessageOptions` interface to support optional fields and metadata in compaction summary messages.
- Included compaction method labels and before-after amount badge rendering in compaction views.
- Updated session maintenance and compaction methods to track and compute post-compaction token counts.
- Updated calls across agent and coding-agent packages to use the new compaction options object.
This commit is contained in:
can1357
2026-08-20 07:18:34 +02:00
parent 6740f146b0
commit d820cf75bb
16 changed files with 205 additions and 83 deletions
@@ -160,9 +160,10 @@ describe("AgentSession snapcompact frame dead-end rescue", () => {
"stale snapcompact archive",
userEntryId,
150_000,
{ readFiles: ["src/a.ts"], modifiedFiles: ["src/b.ts"] },
false,
makeArchivePreserveData(options.frameCount),
{
details: { readFiles: ["src/a.ts"], modifiedFiles: ["src/b.ts"] },
preserveData: makeArchivePreserveData(options.frameCount),
},
);
}
@@ -20,7 +20,7 @@ const SUMMARY = "Earlier the user fixed the login TTL bug.";
function makeComponent(images?: ImageContent[]): CompactionSummaryMessageComponent {
return new CompactionSummaryMessageComponent(
createCompactionSummaryMessage(SUMMARY, 84000, new Date().toISOString(), undefined, undefined, images),
createCompactionSummaryMessage(SUMMARY, 84000, new Date().toISOString(), { images }),
);
}
@@ -36,6 +36,29 @@ describe("CompactionSummaryMessageComponent", () => {
expect(rule).not.toContain(SUMMARY);
});
it("names the compaction method and the before → after amounts on the divider", () => {
const component = new CompactionSummaryMessageComponent(
createCompactionSummaryMessage(SUMMARY, 256_000, new Date().toISOString(), {
method: "remote",
tokensAfter: 20_000,
}),
);
const rule = Bun.stripANSI(component.render(80)[1]);
expect(rule).toContain("remote-compacted");
expect(rule).toContain("256K→20K");
expect(rule).toContain("ctrl+o");
});
it("labels a handoff-method compaction as handed-off", () => {
const component = new CompactionSummaryMessageComponent(
createCompactionSummaryMessage(SUMMARY, 84_000, new Date().toISOString(), { method: "handoff" }),
);
const rule = Bun.stripANSI(component.render(80)[1]);
expect(rule).toContain("handed-off");
// No tokensAfter recorded → no amount badge.
expect(rule).not.toContain("→");
});
it("expanded: reveals the summary (and snapcompact frame count) below the divider", () => {
const component = makeComponent([{ type: "image", data: "ZmFrZQ==", mimeType: "image/png" }]);
component.setExpanded(true);
@@ -39,7 +39,7 @@ describe("handoff summary divider", () => {
expect(component).toBeInstanceOf(HandoffSummaryMessageComponent);
const collapsed = Bun.stripANSI(component!.render(80).join("\n"));
expect(collapsed).toContain("handoff");
expect(collapsed).toContain("handed-off");
expect(collapsed).toContain("ctrl+o");
expect(collapsed).not.toContain("[handoff]");
expect(collapsed).not.toContain("Continue the resize fix");
@@ -82,15 +82,9 @@ describe("large session memory guards", () => {
textTail: "frame",
},
};
const firstCompactionId = session.appendCompaction(
firstSummary,
undefined,
firstKeptEntryId,
1000,
undefined,
undefined,
firstPreserve,
);
const firstCompactionId = session.appendCompaction(firstSummary, undefined, firstKeptEntryId, 1000, {
preserveData: firstPreserve,
});
const rewindId = session.appendMessage({ role: "user", content: "between compactions", timestamp: 3 });
session.appendCompaction(secondSummary, undefined, rewindId, 2000);
await session.flush();
@@ -188,15 +182,9 @@ describe("large session memory guards", () => {
const branchACompactionSummary = `branch-a-${"x".repeat(1024)}`;
const branchAPreserve = { openaiRemoteCompaction: { provider: "anthropic", replacementHistory: [] } };
session.appendCompaction(
branchACompactionSummary,
undefined,
rootId,
1000,
undefined,
undefined,
branchAPreserve,
);
session.appendCompaction(branchACompactionSummary, undefined, rootId, 1000, {
preserveData: branchAPreserve,
});
const branchACompactionId = session.getLeafId();
if (!branchACompactionId) throw new Error("Expected branch A compaction id");
+7 -4
View File
@@ -219,10 +219,13 @@ describe("AgentSession shake", () => {
seedHeavyToolResult("X".repeat(20_000));
const firstKeptEntryId = sessionManager.getBranch()[0]?.id;
if (!firstKeptEntryId) throw new Error("Expected seeded branch");
sessionManager.appendCompaction("remote summary", undefined, firstKeptEntryId, 10_000, {}, false, {
openaiRemoteCompaction: {
provider: "openai",
replacementHistory: [],
sessionManager.appendCompaction("remote summary", undefined, firstKeptEntryId, 10_000, {
details: {},
preserveData: {
openaiRemoteCompaction: {
provider: "openai",
replacementHistory: [],
},
},
});
sessionManager.appendMessage({