@sema-agent/core
Version:
Stateless, task-oriented AI agent core
666 lines (665 loc) • 32.8 kB
JavaScript
import { CROSS_SESSION_MESSAGE_TAG } from "../agents/cross-session-envelope.js";
import { DEFAULT_CHARS_PER_TOKEN, DEFAULT_CLAMP_TOLERANCE, DEFAULT_COMPACTION_SETTINGS, CompactionError, compact, computeFileLists, dryRunSummarizationClamp, estimateContextTokens, estimateTokens, formatFileOperations, formatPersistedOutputRefs, prepareCompaction, shouldCompact, uuidv7, } from "../internal/harness.js";
import { fileArgPath } from "./tool-registry.js";
import { contextEditFrontier, resolveTriggerWindow } from "./context-edit.js";
import { selectCompactionEpoch } from "../prompt-assembly/epoch.js";
import { brainToRuntime } from "./runtime.js";
import { defuseFenceMarkers, sanitizeUntrustedText } from "./untrusted-text.js";
import { createSafeNotifier, observeThenableRejection } from "./safe-notify.js";
export const DEFAULT_COMPACTION_INSTRUCTIONS = "Preserve verbatim every code, identifier, name, number, file path, error message, credential " +
"reference, and explicit user instruction or decision mentioned anywhere in the conversation. " +
"When in doubt, keep the specific detail rather than generalizing it.";
const FALLBACK_RESERVE_FRACTION = 0.15;
export const COMPACTION_INSTRUCTIONS_MAX_CHARS = 2048;
export function sanitizeCompactionInstructions(text) {
const defused = defuseFenceMarkers(sanitizeUntrustedText(text));
const cps = [...defused];
return cps.length > COMPACTION_INSTRUCTIONS_MAX_CHARS
? cps.slice(0, COMPACTION_INSTRUCTIONS_MAX_CHARS).join("")
: defused;
}
const WORKING_FILE_BREAKOUT_TAGS = ["working-file", "summary"];
function escapeWorkingFilePathAttr(path) {
return path.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/[\r\n]+/g, " ");
}
const COMPACTION_MANUAL_CANCEL_FLAG = "semaCompactionManualCancel";
export function isCompactionManualCancel(err) {
return (typeof err === "object" && err !== null && err[COMPACTION_MANUAL_CANCEL_FLAG] === true);
}
function summarizationCharsCapacity(model) {
return model.contextWindow > 0
? model.contextWindow * (model.charsPerToken ?? DEFAULT_CHARS_PER_TOKEN)
: Number.POSITIVE_INFINITY;
}
export const STALE_ANCHOR_STRUCTURAL_MARGIN = 2;
export function sanitizeCompactionSettings(settings, contextWindow) {
const wide = settings;
const reserveInvalid = !Number.isFinite(wide.reserveTokens);
const keepRecentInvalid = !Number.isFinite(wide.keepRecentTokens);
const clampToleranceInvalid = wide.clampTolerance !== undefined && !Number.isFinite(wide.clampTolerance);
const summaryBudgetInvalid = wide.summaryOutputBudgetTokens !== undefined && !Number.isFinite(wide.summaryOutputBudgetTokens);
const base = reserveInvalid || keepRecentInvalid || clampToleranceInvalid || summaryBudgetInvalid
? {
...settings,
...(reserveInvalid ? { reserveTokens: DEFAULT_COMPACTION_SETTINGS.reserveTokens } : {}),
...(keepRecentInvalid ? { keepRecentTokens: DEFAULT_COMPACTION_SETTINGS.keepRecentTokens } : {}),
...(clampToleranceInvalid ? { clampTolerance: DEFAULT_CLAMP_TOLERANCE } : {}),
...(summaryBudgetInvalid ? { summaryOutputBudgetTokens: undefined } : {}),
}
: settings;
if (!contextWindow || !Number.isFinite(contextWindow) || contextWindow <= 0) {
return base;
}
let reserveTokens = base.reserveTokens;
if (contextWindow - reserveTokens <= 0) {
reserveTokens = Math.ceil(contextWindow * FALLBACK_RESERVE_FRACTION);
}
const editAt = contextEditFrontier(contextWindow);
if (contextWindow - reserveTokens > editAt) {
reserveTokens = contextWindow - editAt;
}
const threshold = contextWindow - reserveTokens;
let keepRecentTokens = base.keepRecentTokens;
if (keepRecentTokens >= threshold) {
keepRecentTokens = Math.max(1, Math.floor(threshold / 2));
}
if (reserveTokens === base.reserveTokens && keepRecentTokens === base.keepRecentTokens) {
return base;
}
return { ...base, reserveTokens, keepRecentTokens };
}
export async function maybeCompact(opts) {
const compactStartAt = Date.now();
const rawSettings = opts.settings ?? DEFAULT_COMPACTION_SETTINGS;
if (!rawSettings.enabled) {
return { compacted: false, disabled: true };
}
const ctx = await opts.session.buildContext();
const cpt = opts.model.charsPerToken ?? DEFAULT_CHARS_PER_TOKEN;
const est = estimateContextTokens(ctx.messages, cpt);
const overhead = opts.overheadTokens ?? 0;
const structuralTokens = ctx.messages.reduce((sum, m) => sum + estimateTokens(m, cpt), 0);
const anchorStale = est.usageTokens > 0 && est.tokens > structuralTokens * STALE_ANCHOR_STRUCTURAL_MARGIN + overhead;
const tokens = anchorStale || est.usageTokens === 0 ? structuralTokens + overhead : est.tokens;
const window = resolveTriggerWindow(opts.model, opts.contextOverlay).window;
const windowKnown = Number.isFinite(window) && window > 0;
const windowField = (opts.contextOverlay?.autoCompactTokens ?? opts.model.autoCompactTokens) != null
? "autoCompactTokens"
: (opts.contextOverlay?.contextTokens ?? opts.model.contextTokens) != null
? "contextTokens"
: "contextWindow";
const settings = sanitizeCompactionSettings(rawSettings, window);
const contextUsage = windowKnown
? { usedTokens: tokens, windowTokens: window, compactAtTokens: window - settings.reserveTokens }
: undefined;
const wantsCompact = shouldCompact(tokens, window, settings);
const naturalTrigger = wantsCompact && tokens >= (opts.minTokens ?? 0);
const force = opts.force || (opts.forceUnderThreshold === true && !naturalTrigger);
if (!force && !naturalTrigger) {
if (wantsCompact) {
return { contextUsage, compacted: false, suppressedByFloor: { estTokens: tokens, floor: opts.minTokens ?? 0 } };
}
return {
contextUsage,
compacted: false,
...(windowKnown
? {}
: {
unevaluableWindow: {
estTokens: tokens,
windowField,
...(typeof window === "number" ? { windowValue: window } : {}),
...(opts.model.id !== undefined ? { modelId: opts.model.id } : {}),
},
}),
};
}
const branch = await opts.session.getBranch();
const prep = prepareCompaction(branch, settings, cpt, window, opts.contextInstructionFiles);
if (!prep.ok) {
throw prep.error;
}
if (!prep.value) {
return { contextUsage, compacted: false, noop: true };
}
const trigger = opts.trigger ?? "auto";
const notifier = createSafeNotifier(opts.onNotifyError !== undefined ? { onError: opts.onNotifyError } : undefined);
let hookInstructions;
if (opts.preCompact) {
let pre;
try {
pre =
(await opts.preCompact({
trigger,
...(opts.customInstructions !== undefined ? { customInstructions: opts.customInstructions } : {}),
})) ?? undefined;
}
catch {
}
if (pre?.block !== undefined && pre.block !== "") {
if (trigger !== "forced") {
return { contextUsage, compacted: false, blocked: true };
}
}
if (pre?.additionalInstructions) {
hookInstructions = pre.additionalInstructions;
}
}
const specInstructions = opts.customInstructions !== undefined ? sanitizeCompactionInstructions(opts.customInstructions) : undefined;
const hookSanitized = hookInstructions !== undefined ? sanitizeCompactionInstructions(hookInstructions) : undefined;
const effectiveInstructions = hookSanitized
? specInstructions
? `${specInstructions}\n\n${hookSanitized}`
: hookSanitized
: specInstructions;
const signalAtCommit = opts.signal;
if (signalAtCommit?.aborted === true) {
throw Object.assign(new CompactionError("aborted", "compaction cancelled before the pass committed — nothing was summarized or persisted"), opts.manualCancelSignal?.aborted === true ? { [COMPACTION_MANUAL_CANCEL_FLAG]: true } : {});
}
const pass = uuidv7();
notifier.notify(() => observeThenableRejection(opts.onStarted?.({ pass, trigger, startedAt: Date.now() }), notifier, "compaction.onStarted"), "compaction.onStarted");
const maxReuse = opts.maxConsecutiveProviderReuse ?? 3;
const forceRealSummary = maxReuse > 0 && (opts.consecutiveProviderReuse ?? 0) >= maxReuse;
let providerSummary;
if (opts.summaryProvider && !forceRealSummary && !prep.value.isSplitTurn) {
const providerModifiedFiles = computeFileLists(prep.value.fileOps).modifiedFilesByRecency;
const signal = opts.signal;
const cut = Symbol("cut");
let release = () => undefined;
const cutByAbort = new Promise((resolve) => {
if (signal === undefined || signal.aborted)
return;
const onAbort = () => resolve(cut);
signal.addEventListener("abort", onAbort, { once: true });
release = () => signal.removeEventListener("abort", onAbort);
});
let consulted;
try {
consulted =
signal?.aborted === true
? cut
: await Promise.race([
Promise.resolve(opts.summaryProvider({
messagesToSummarize: prep.value.messagesToSummarize,
previousSummary: prep.value.previousSummary,
modifiedFiles: providerModifiedFiles,
})),
cutByAbort,
]);
const trimmed = typeof consulted === "string" ? consulted.trim() : "";
if (trimmed) {
providerSummary = trimmed;
}
}
catch {
}
finally {
release();
}
if (consulted === cut) {
throw Object.assign(new CompactionError("aborted", "compaction cancelled while the summary provider was answering — nothing was persisted"), opts.manualCancelSignal?.aborted === true ? { [COMPACTION_MANUAL_CANCEL_FLAG]: true } : {});
}
}
let summary;
let firstKeptEntryId;
let summaryTokensBefore;
let details;
let modelFallback = false;
let clampedRatio;
let clampReason;
let summaryStartAt;
let summaryEndAt;
let ptlRetries = 0;
const reused = providerSummary !== undefined;
if (providerSummary !== undefined) {
summary = providerSummary;
firstKeptEntryId = prep.value.firstKeptEntryId;
summaryTokensBefore = prep.value.tokensBefore;
const providerFileLists = computeFileLists(prep.value.fileOps);
details = providerFileLists;
summary += formatFileOperations(providerFileLists.readFiles, providerFileLists.modifiedFiles);
if (prep.value.invokedSkills.length > 0) {
details.invokedSkills = prep.value.invokedSkills;
}
const reusedRefs = prep.value.persistedOutputRefs ?? [];
if (reusedRefs.length > 0) {
details.persistedOutputRefs = reusedRefs;
summary += formatPersistedOutputRefs(reusedRefs);
}
if (prep.value.elidedMessages !== undefined && prep.value.elidedMessages > 0) {
details.elidedMessages = prep.value.elidedMessages;
}
if (prep.value.carriedUnsummarizedMessages !== undefined && prep.value.carriedUnsummarizedMessages > 0) {
details.unsummarizedMessages = prep.value.carriedUnsummarizedMessages;
}
}
else {
let summaryModel = opts.compactionModel ?? opts.model;
const wantsWindowSafety = opts.compactionModel !== undefined &&
summarizationCharsCapacity(opts.compactionModel) < summarizationCharsCapacity(opts.model);
let dry;
if (wantsWindowSafety) {
dry = dryRunSummarizationClamp(prep.value, summaryModel, effectiveInstructions);
}
const mainSection = dry !== undefined && prep.value.contextInstructionFiles !== undefined
? dryRunSummarizationClamp(prep.value, opts.model, effectiveInstructions).instructionFilesChars
: 0;
const contentTokens = dry !== undefined
? Math.ceil((dry.conversationChars + mainSection + (prep.value.previousSummary?.length ?? 0)) / cpt)
: 0;
const observeWindowSafety = (info) => {
notifier.notify(() => opts.onWindowSafety?.(info), "compaction.onWindowSafety");
};
if (wantsWindowSafety && dry !== undefined) {
const tolerance = settings.clampTolerance ?? DEFAULT_CLAMP_TOLERANCE;
const smallCpt = summaryModel.charsPerToken ?? DEFAULT_CHARS_PER_TOKEN;
const headroomTokens = Math.ceil(dry.keptChars / smallCpt);
if (dry.truncationRatio > tolerance) {
let gate;
let estCostMicroUsd;
const bg = opts.fallbackBudget;
if (bg?.mainInputPer1M !== undefined) {
estCostMicroUsd = Math.round(contentTokens * bg.mainInputPer1M);
if (bg.spentMicroUsd() + estCostMicroUsd > bg.capMicroUsd) {
gate = "budget";
}
}
if (gate === undefined) {
summaryModel = opts.model;
modelFallback = true;
observeWindowSafety({
kind: "fallback",
truncationRatio: dry.truncationRatio,
contentTokens,
headroomTokens,
...(estCostMicroUsd !== undefined ? { estCostMicroUsd } : {}),
});
}
else {
clampedRatio = dry.truncationRatio;
clampReason = gate;
observeWindowSafety({
kind: "clamp_disclosure",
reason: gate,
truncationRatio: dry.truncationRatio,
contentTokens,
headroomTokens,
...(estCostMicroUsd !== undefined ? { estCostMicroUsd } : {}),
});
}
}
else if (dry.truncationRatio > 0) {
clampedRatio = dry.truncationRatio;
clampReason = "tolerance";
observeWindowSafety({
kind: "clamp_disclosure",
reason: "tolerance",
truncationRatio: dry.truncationRatio,
contentTokens,
headroomTokens,
});
}
}
const auth = await opts.getApiKeyAndHeaders?.(summaryModel);
const summarySignal = opts.signal;
const runtime = brainToRuntime(opts.brain);
const forkCandidate = opts.compactionModel === undefined ? opts.forkContext?.() : undefined;
const forkContext = forkCandidate !== undefined && (forkCandidate.modelId === undefined || forkCandidate.modelId === summaryModel.id) ? forkCandidate : undefined;
let res;
summaryStartAt = Date.now();
try {
res = await compact(prep.value, summaryModel, auth?.apiKey, auth?.headers, effectiveInstructions, summarySignal, opts.thinking, undefined, runtime, undefined, opts.onInputTruncated, () => {
ptlRetries += 1;
}, forkContext);
}
finally {
summaryEndAt = Date.now();
}
if (!res.ok) {
if (opts.manualCancelSignal?.aborted === true &&
res.error instanceof CompactionError &&
res.error.code === "aborted") {
res.error[COMPACTION_MANUAL_CANCEL_FLAG] = true;
}
throw res.error;
}
summary = (res.value.summary ?? "").replace(/<analysis>[\s\S]*?<\/analysis>\s*/gi, "").trim();
if (!summary) {
throw new Error("compaction produced an empty summary (reasoning may have consumed the output budget; history left untouched)");
}
if (Math.ceil(summary.length / cpt) > res.value.tokensBefore * 1.5) {
throw new Error(`compaction produced an oversized summary (≈${Math.ceil(summary.length / cpt)} tokens vs ` +
`${res.value.tokensBefore} before compaction; history left untouched)`);
}
firstKeptEntryId = res.value.firstKeptEntryId;
summaryTokensBefore = res.value.tokensBefore;
details = res.value.details;
}
const persistStartAt = summaryEndAt ?? Date.now();
let summaryWithAttachments = summary;
const attachedFiles = [];
const attachedComplete = [];
const att = opts.workingFileAttachments;
const recentlyRead = att?.recentlyReadFiles?.() ?? [];
const rawCandidateFiles = recentlyRead.length > 0 ? recentlyRead : (details?.modifiedFilesByRecency ?? details?.modifiedFiles);
const maxFilesForSelection = Math.max(1, att?.maxFiles ?? 3);
const excludedReadStatePreserveKeys = [];
const candidateFiles = att !== undefined && Array.isArray(rawCandidateFiles) && rawCandidateFiles.length > 0
? await (async () => {
const keptTailReadPathsRaw = new Set();
const firstKeptIdx = branch.findIndex((e) => e.id === firstKeptEntryId);
if (firstKeptIdx >= 0) {
const dedupStub = att?.isDedupStubResult;
for (let i = firstKeptIdx; i < branch.length; i++) {
const entry = branch[i];
if (entry.type !== "message" || entry.message.role !== "assistant")
continue;
const msg = entry.message;
if (!("content" in msg) || !Array.isArray(msg.content))
continue;
const pendingReads = new Map();
for (const block of msg.content) {
if (typeof block !== "object" || block === null)
continue;
if (!("type" in block) || block.type !== "toolCall")
continue;
const tc = block;
if (typeof tc.name !== "string" || typeof tc.id !== "string" || !tc.arguments)
continue;
if (tc.name !== "read" && tc.name !== "Read" && tc.name !== "read_file")
continue;
const path = fileArgPath(tc.arguments);
if (path)
pendingReads.set(tc.id, path);
}
if (pendingReads.size === 0)
continue;
for (let j = i + 1; j < branch.length && pendingReads.size > 0; j++) {
const next = branch[j];
if (next.type !== "message")
continue;
if (next.message.role === "assistant")
break;
if (next.message.role !== "toolResult")
continue;
const tr = next.message;
const path = pendingReads.get(tr.toolCallId);
if (path === undefined)
continue;
pendingReads.delete(tr.toolCallId);
if (tr.isError)
continue;
if (dedupStub) {
const text = tr.content.map((c) => ("text" in c ? c.text : "")).join("");
if (dedupStub(text))
continue;
}
keptTailReadPathsRaw.add(path);
}
}
}
const instructionPathsRaw = new Set(att?.instructionSourcePaths ?? []);
if (keptTailReadPathsRaw.size === 0 && instructionPathsRaw.size === 0)
return rawCandidateFiles;
const normalize = att?.normalizePath;
if (!normalize) {
excludedReadStatePreserveKeys.push(...keptTailReadPathsRaw, ...instructionPathsRaw);
return rawCandidateFiles.filter((p) => !keptTailReadPathsRaw.has(p) && !instructionPathsRaw.has(p));
}
const normalizeSafe = async (p) => {
try {
return await normalize(p);
}
catch {
return p;
}
};
const normalizeCap = Math.max(maxFilesForSelection * 4, 12);
const boundedCandidates = rawCandidateFiles.slice(0, normalizeCap);
const overflowCandidates = rawCandidateFiles.slice(normalizeCap);
const [normBounded, normKeptTail, normInstruction] = await Promise.all([
Promise.all(boundedCandidates.map(normalizeSafe)),
Promise.all([...keptTailReadPathsRaw].map(normalizeSafe)),
Promise.all([...instructionPathsRaw].map(normalizeSafe)),
]);
excludedReadStatePreserveKeys.push(...normKeptTail, ...normInstruction);
const excluded = new Set([...normKeptTail, ...normInstruction]);
const filteredBounded = boundedCandidates.filter((_, i) => !excluded.has(normBounded[i]));
return [...filteredBounded, ...overflowCandidates];
})()
: rawCandidateFiles;
if (att && Array.isArray(candidateFiles) && candidateFiles.length > 0) {
const maxFiles = maxFilesForSelection;
const perFileCap = Math.max(200, att.maxCharsPerFile ?? 16_000);
const windowShareChars = windowKnown ? Math.floor(window * 0.15) * cpt : Number.POSITIVE_INFINITY;
let remaining = Math.min(maxFiles * perFileCap, windowShareChars);
const blocks = [];
for (const path of candidateFiles.slice(0, maxFiles)) {
if (remaining <= 0)
break;
let content = null;
let withheldPattern;
try {
const r = await att.readFile(path);
if (typeof r === "string")
content = r;
else if (r !== null && typeof r === "object")
withheldPattern = r.withheld.pattern;
}
catch {
content = null;
}
if (withheldPattern !== undefined) {
blocks.push(`<working-file path="${escapeWorkingFilePathAttr(path)}" withheld="content withheld: matches the sensitive-path read deny list (pattern ${escapeWorkingFilePathAttr(withheldPattern)})"/>`);
continue;
}
if (content === null)
continue;
const cap = Math.min(perFileCap, remaining);
const wasClipped = content.length > cap;
const clipped = wasClipped
? `${content.slice(0, cap)}\n[... ${content.length - cap} more characters truncated — read the file for the rest]`
: content;
remaining -= clipped.length;
blocks.push(`<working-file path="${escapeWorkingFilePathAttr(path)}">\n${sanitizeUntrustedText(clipped, WORKING_FILE_BREAKOUT_TAGS)}\n</working-file>`);
attachedFiles.push({ path, chars: Math.min(content.length, cap), truncated: wasClipped });
if (!wasClipped)
attachedComplete.push({ path, content });
}
if (blocks.length > 0) {
summaryWithAttachments +=
"\n\nCurrent on-disk contents of files touched in the summarized work (re-read at compaction time — trust these over any excerpts in the summary above):\n" +
blocks.join("\n");
}
}
let preChars = 0;
for (const m of ctx.messages)
preChars += estimateTokens(m, cpt);
const ca = opts.centerAdoption;
const stored = await opts.session.getPromptEpoch?.();
const current = stored?.kind === "pin" ? stored.descriptor : undefined;
const identityNow = ca?.promptIdentity ?? opts.promptIdentity ?? (current !== undefined ? { layers: current.layers, ...(current.sourceRevision !== undefined ? { sourceRevision: current.sourceRevision } : {}) } : undefined);
const restatedEpoch = identityNow !== undefined ? selectCompactionEpoch(current, identityNow.layers, identityNow.sourceRevision) : undefined;
const restatedListings = await opts.session.getAnnouncedListing().catch(() => undefined);
if (opts.signal?.aborted === true) {
throw Object.assign(new Error("compaction cancelled before commit — the abort signal fired during the summary call; nothing was persisted"), opts.manualCancelSignal?.aborted === true ? { [COMPACTION_MANUAL_CANCEL_FLAG]: true } : {});
}
await opts.session.appendCompaction(summaryWithAttachments, firstKeptEntryId, summaryTokensBefore, {
...(details ?? {}),
...(restatedEpoch !== undefined ? { promptEpoch: restatedEpoch } : {}),
...(restatedListings !== undefined ? { announcedListings: restatedListings } : {}),
...(opts.activeTools !== undefined && opts.activeTools.length > 0 ? { activeTools: [...opts.activeTools] } : {}),
...(opts.gitRestate !== undefined ? { gitAnnouncement: { kind: opts.gitRestate.pending.kind, hash: opts.gitRestate.pending.hash, pending: true } } : {}),
}, false);
if (opts.onCommitted !== undefined) {
const firstKeptIdx = branch.findIndex((e) => e.id === firstKeptEntryId);
const effect = firstKeptIdx >= 0
? { summarizedEntryIds: new Set(branch.slice(0, firstKeptIdx).map((e) => e.id)) }
: { summarizedEntryIds: "unknown" };
try {
opts.onCommitted(effect);
}
catch {
}
}
if (opts.gitRestate !== undefined) {
try {
await opts.gitRestate.land();
}
catch {
}
}
if (opts.postureRestate !== undefined) {
try {
await opts.postureRestate();
}
catch {
}
}
try {
opts.onApplied?.(attachedComplete, excludedReadStatePreserveKeys);
}
catch {
}
if (ca !== undefined && restatedEpoch !== undefined) {
try {
ca.apply(restatedEpoch.identity);
}
catch {
}
}
let freedTokens;
let postTriggerTokens;
try {
const post = (await opts.session.buildContext()).messages;
let postChars = 0;
for (const m of post)
postChars += estimateTokens(m, cpt);
freedTokens = Math.max(0, preChars - postChars);
postTriggerTokens = postChars + (ca !== undefined ? ca.overheadTokensAfter : (opts.overheadTokens ?? 0));
}
catch {
}
const messagesCompacted = prep.value.messagesToSummarize.length;
notifier.notify(() => opts.onCompaction?.({
messagesCompacted,
freedTokens,
tokensBefore: summaryTokensBefore,
reused,
}), "compaction.onCompaction");
if (opts.postCompact) {
await notifier.notifyAsync(() => opts.postCompact?.({
trigger,
summary,
tokensBefore: summaryTokensBefore,
...(freedTokens !== undefined ? { freedTokens } : {}),
...(postTriggerTokens !== undefined ? { tokensAfter: postTriggerTokens } : {}),
}), "compaction.postCompact");
}
const endAt = Date.now();
const summarizedCrossSessionMessages = countSummarizedCrossSessionMessages(branch, firstKeptEntryId);
return {
contextUsage,
compacted: true,
pass,
tokensBefore: summaryTokensBefore,
freedTokens,
triggerTokens: tokens,
postTriggerTokens,
firstKeptEntryId,
...(summarizedCrossSessionMessages > 0 ? { summarizedCrossSessionMessages } : {}),
reused,
naturalTrigger,
durationMs: endAt - compactStartAt,
phaseDurations: {
prepareMs: (summaryStartAt ?? persistStartAt) - compactStartAt,
...(summaryStartAt !== undefined && summaryEndAt !== undefined
? { summaryMs: summaryEndAt - summaryStartAt }
: {}),
persistMs: endAt - persistStartAt,
ptlRetries,
},
...(postTriggerTokens !== undefined
? { postOverThreshold: shouldCompact(postTriggerTokens, window, settings) }
: {}),
...(attachedFiles.length > 0 ? { attachedFiles } : {}),
...(modelFallback ? { modelFallback: true, fallbackReason: "window" } : {}),
...(clampedRatio !== undefined ? { clampedRatio } : {}),
...(clampReason !== undefined ? { clampReason } : {}),
};
}
export function mintCompactedEvent(comp, trigger, identity) {
return {
type: "compacted",
trigger,
...(comp.pass !== undefined ? { pass: comp.pass } : {}),
tokensBefore: comp.tokensBefore ?? 0,
...(comp.freedTokens !== undefined ? { freedTokens: comp.freedTokens } : {}),
...(comp.postTriggerTokens !== undefined ? { tokensAfter: comp.postTriggerTokens } : {}),
...(comp.triggerTokens !== undefined ? { triggerTokensBefore: comp.triggerTokens } : {}),
...(comp.durationMs !== undefined ? { durationMs: comp.durationMs } : {}),
...(comp.phaseDurations !== undefined ? { phaseDurations: comp.phaseDurations } : {}),
...(comp.firstKeptEntryId !== undefined ? { preserved_segment: { firstKeptEntryId: comp.firstKeptEntryId } } : {}),
...(comp.attachedFiles !== undefined ? { attachedFiles: comp.attachedFiles } : {}),
...(comp.modelFallback ? { modelFallback: true } : {}),
...(comp.fallbackReason !== undefined ? { fallbackReason: comp.fallbackReason } : {}),
...(comp.clampedRatio !== undefined ? { clampedRatio: comp.clampedRatio } : {}),
...(comp.clampReason !== undefined ? { clampReason: comp.clampReason } : {}),
...(comp.summarizedCrossSessionMessages !== undefined ? { summarizedCrossSessionMessages: comp.summarizedCrossSessionMessages } : {}),
...identity,
};
}
export function mintCompactionStartedEvent(start, identity) {
return { type: "compaction_started", trigger: start.trigger, pass: start.pass, startedAt: start.startedAt, ...identity };
}
export function compactionPassReport(sink, ident) {
let pass;
return {
onStarted: (start) => {
sink.push(mintCompactionStartedEvent(start, ident()));
pass = start.pass;
},
get pass() {
return pass;
},
get end() {
return pass !== undefined ? { pass } : {};
},
};
}
export function countSummarizedCrossSessionMessages(branch, firstKeptEntryId) {
const firstKeptIdx = branch.findIndex((e) => e.id === firstKeptEntryId);
const summarized = firstKeptIdx >= 0 ? branch.slice(0, firstKeptIdx) : branch;
let n = 0;
for (const e of summarized) {
if (e.type !== "message" || e.message === undefined || e.message.role !== "user" || e.message.provenance !== "engine-note")
continue;
const c = e.message.content;
const text = typeof c === "string" ? c : Array.isArray(c) ? c.map((b) => (b !== null && typeof b === "object" && b.type === "text" ? String(b.text ?? "") : "")).join("") : "";
if (text.startsWith(`<${CROSS_SESSION_MESSAGE_TAG} `) || text.startsWith(`<${CROSS_SESSION_MESSAGE_TAG}>`))
n += 1;
}
return n;
}
export function nextTrimForceBackoff(prev, comp, trimForced = false) {
if (trimForced && comp.naturalTrigger !== true && comp.compacted && comp.freedTokens !== undefined && comp.freedTokens <= 0) {
return true;
}
if (!comp.compacted || comp.postOverThreshold === undefined) {
return prev;
}
return comp.postOverThreshold;
}
export function createRapidRefillState() {
return { lastCompactionTurn: Number.NEGATIVE_INFINITY, count: 0 };
}
export function recordCompactionAndCheckRapidRefill(state, turn, minGapTurns = 3, tripAt = 3) {
if (turn - state.lastCompactionTurn < minGapTurns) {
state.count += 1;
}
else {
state.count = 0;
}
state.lastCompactionTurn = turn;
return state.count >= tripAt;
}