Make CompactionEntry and CompactionResult generic with details field

- CompactionEntry<T> and CompactionResult<T> now have optional details?: T
- appendCompaction() accepts optional details parameter
- Hooks can return compaction.details to store custom data
- Enables structured compaction with ArtifactIndex (see #314)
- Fix CompactionResult export location (now from compaction.ts)
- Update plan with remaining compaction refactor items
This commit is contained in:
Mario Zechner 2025-12-26 22:05:03 +01:00
parent efb1036d8e
commit d96375b5e5
9 changed files with 54 additions and 24 deletions

View file

@ -18,7 +18,13 @@ import type { AssistantMessage, Message, Model, TextContent } from "@mariozechne
import { isContextOverflow, modelsAreEqual, supportsXhigh } from "@mariozechner/pi-ai";
import { getAuthPath } from "../config.js";
import { type BashResult, executeBash as executeBashCommand } from "./bash-executor.js";
import { calculateContextTokens, compact, prepareCompaction, shouldCompact } from "./compaction.js";
import {
type CompactionResult,
calculateContextTokens,
compact,
prepareCompaction,
shouldCompact,
} from "./compaction.js";
import type { LoadedCustomTool, SessionEvent as ToolSessionEvent } from "./custom-tools/index.js";
import { exportSessionToHtml } from "./export-html.js";
import type { HookRunner, SessionEventResult, TurnEndEvent, TurnStartEvent } from "./hooks/index.js";
@ -76,12 +82,6 @@ export interface ModelCycleResult {
isScoped: boolean;
}
/** Result from compact() or checkAutoCompaction() */
export interface CompactionResult {
tokensBefore: number;
summary: string;
}
/** Session statistics for /session command */
export interface SessionStats {
sessionFile: string | null;
@ -771,7 +771,7 @@ export class AgentSession {
}
}
let hookCompaction: { summary: string; firstKeptEntryId: string; tokensBefore: number } | undefined;
let hookCompaction: CompactionResult | undefined;
let fromHook = false;
if (this._hookRunner?.hasHandlers("session")) {
@ -806,12 +806,14 @@ export class AgentSession {
let summary: string;
let firstKeptEntryId: string;
let tokensBefore: number;
let details: unknown;
if (hookCompaction) {
// Hook provided compaction content
summary = hookCompaction.summary;
firstKeptEntryId = hookCompaction.firstKeptEntryId;
tokensBefore = hookCompaction.tokensBefore;
details = hookCompaction.details;
} else {
// Generate compaction result
const result = await compact(
@ -825,13 +827,14 @@ export class AgentSession {
summary = result.summary;
firstKeptEntryId = result.firstKeptEntryId;
tokensBefore = result.tokensBefore;
details = result.details;
}
if (this._compactionAbortController.signal.aborted) {
throw new Error("Compaction cancelled");
}
this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore);
this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details);
const newEntries = this.sessionManager.getEntries();
const sessionContext = this.sessionManager.buildSessionContext();
this.agent.replaceMessages(sessionContext.messages);
@ -855,8 +858,10 @@ export class AgentSession {
}
return {
tokensBefore,
summary,
firstKeptEntryId,
tokensBefore,
details,
};
} finally {
this._compactionAbortController = null;
@ -952,7 +957,7 @@ export class AgentSession {
}
}
let hookCompaction: { summary: string; firstKeptEntryId: string; tokensBefore: number } | undefined;
let hookCompaction: CompactionResult | undefined;
let fromHook = false;
if (this._hookRunner?.hasHandlers("session")) {
@ -988,12 +993,14 @@ export class AgentSession {
let summary: string;
let firstKeptEntryId: string;
let tokensBefore: number;
let details: unknown;
if (hookCompaction) {
// Hook provided compaction content
summary = hookCompaction.summary;
firstKeptEntryId = hookCompaction.firstKeptEntryId;
tokensBefore = hookCompaction.tokensBefore;
details = hookCompaction.details;
} else {
// Generate compaction result
const compactResult = await compact(
@ -1006,6 +1013,7 @@ export class AgentSession {
summary = compactResult.summary;
firstKeptEntryId = compactResult.firstKeptEntryId;
tokensBefore = compactResult.tokensBefore;
details = compactResult.details;
}
if (this._autoCompactionAbortController.signal.aborted) {
@ -1013,7 +1021,7 @@ export class AgentSession {
return;
}
this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore);
this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details);
const newEntries = this.sessionManager.getEntries();
const sessionContext = this.sessionManager.buildSessionContext();
this.agent.replaceMessages(sessionContext.messages);
@ -1037,8 +1045,10 @@ export class AgentSession {
}
const result: CompactionResult = {
tokensBefore,
summary,
firstKeptEntryId,
tokensBefore,
details,
};
this._emit({ type: "auto_compaction_end", result, aborted: false, willRetry });

View file

@ -12,10 +12,12 @@ import { messageTransformer } from "./messages.js";
import type { CompactionEntry, SessionEntry } from "./session-manager.js";
/** Result from compact() - SessionManager adds uuid/parentUuid when saving */
export interface CompactionResult {
export interface CompactionResult<T = unknown> {
summary: string;
firstKeptEntryId: string;
tokensBefore: number;
/** Hook-specific data (e.g., ArtifactIndex, version markers for structured compaction) */
details?: T;
}
// ============================================================================

View file

@ -7,12 +7,12 @@ export {
type AgentSessionConfig,
type AgentSessionEvent,
type AgentSessionEventListener,
type CompactionResult,
type ModelCycleResult,
type PromptOptions,
type SessionStats,
} from "./agent-session.js";
export { type BashExecutorOptions, type BashResult, executeBash } from "./bash-executor.js";
export type { CompactionResult } from "./compaction.js";
export {
type CustomAgentTool,
type CustomToolFactory,

View file

@ -49,11 +49,13 @@ export interface ModelChangeEntry extends SessionEntryBase {
modelId: string;
}
export interface CompactionEntry extends SessionEntryBase {
export interface CompactionEntry<T = unknown> extends SessionEntryBase {
type: "compaction";
summary: string;
firstKeptEntryId: string;
tokensBefore: number;
/** Hook-specific data (e.g., ArtifactIndex, version markers for structured compaction) */
details?: T;
}
export interface BranchSummaryEntry extends SessionEntryBase {
@ -592,8 +594,8 @@ export class SessionManager {
}
/** Append a compaction summary as child of current leaf, then advance leaf. Returns entry id. */
appendCompaction(summary: string, firstKeptEntryId: string, tokensBefore: number): string {
const entry: CompactionEntry = {
appendCompaction<T = unknown>(summary: string, firstKeptEntryId: string, tokensBefore: number, details?: T): string {
const entry: CompactionEntry<T> = {
type: "compaction",
id: generateId(this.byId),
parentId: this.leafId || null,
@ -601,6 +603,7 @@ export class SessionManager {
summary,
firstKeptEntryId,
tokensBefore,
details,
};
this._appendEntry(entry);
return entry.id;