feat(compact-basic): baseline compaction backend (squashed from compact-basic)
Collapses the per-round review churn of the prior compact-basic branch into a single clean baseline on top of compact-interface, so the upcoming retention refactor lands as fresh, well-scoped commits rather than stacking on a history of fixes that are being superseded.
This commit is contained in:
@@ -19,7 +19,7 @@ Both methods are **abstract** — the backend owns the entire strategy (token es
|
||||
| Member | Semantics |
|
||||
|---|---|
|
||||
| `compactIfNeeded(session, systemPrompt?, model?, signal?)` | Estimate the history size; if over the backend's threshold, compact an older range via `compactRegion`, keeping recent context intact. Returns the `CompactionResult`, or `null` if nothing needed compacting. |
|
||||
| `compactRegion(session, start, end, model, signal?)` | Forcibly summarize surface nodes `[start, end]` (inclusive seqs) into a single replacement node. **Throws** if a compaction is already in progress, if `start`/`end` aren't surface nodes, or if `start > end`. |
|
||||
| `compactRegion(session, start, end, model, signal?)` | Forcibly summarize surface nodes `[start, end]` (inclusive seqs) into a single replacement node. **Throws** if a compaction is already in progress, if `start`/`end` aren't surface nodes, or if `start` is positioned after `end` on the surface. The range is a SURFACE-POSITION span, not a numeric seq interval — after a prior replace lands a fresh high-seq summary node at the shadowed range's position, surface order no longer tracks seq order. |
|
||||
|
||||
Both methods take an optional `signal: AbortSignal`. A backend that summarizes via `ctx.llm.stream()` **must** forward it into the call's `GenerateOptions.signal`, so an abort or fiber dispose tears down the in-flight summarization instead of leaving an orphaned model call running past the cancellation. The turn that the `compact/*` events belong to is not a parameter — it is recoverable from the log (the currently-open turn), so the backend stamps it without the caller supplying it.
|
||||
|
||||
|
||||
@@ -89,6 +89,16 @@ export abstract class CompactService extends Service {
|
||||
* summarizes their content and appends a replacement surface node. Used by the
|
||||
* (future) `/compact` tool and internally by {@link compactIfNeeded}.
|
||||
*
|
||||
* The region MUST contain whole steps — `start` and `end` must each sit on a
|
||||
* step boundary (the first / last surface node of a step) or on a node that
|
||||
* belongs to no step (a pre-step user message, inter-step steering, or an
|
||||
* injection context message). A boundary that falls INSIDE a step would split
|
||||
* that step's `assistant/message` tool-calls from their `tool/result`s, leaving
|
||||
* the rehydrated transcript with a dangling tool-call or an orphaned
|
||||
* tool-result that every provider rejects. An `end` inside an open (unclosed)
|
||||
* tail step is likewise invalid — its tool-calls have no results yet.
|
||||
* `dsh-session` exports `isStepAlignedStart` / `isStepAlignedEnd` for this check.
|
||||
*
|
||||
* @param session - the session whose surface is mutated.
|
||||
* @param start - inclusive seq of the first surface node to compact.
|
||||
* @param end - inclusive seq of the last surface node to compact.
|
||||
@@ -97,8 +107,12 @@ export abstract class CompactService extends Service {
|
||||
* `ctx.llm.stream()` MUST forward this into the call's `GenerateOptions.signal`
|
||||
* so an abort/dispose tears down the in-flight summarization rather than
|
||||
* leaving an orphaned model call running past the cancellation.
|
||||
* @throws if compaction is already in progress, or if `start`/`end` are not
|
||||
* valid surface nodes, or if `start > end`.
|
||||
* @throws if compaction is already in progress, if `start`/`end` are not
|
||||
* valid surface nodes, if `start` is positioned after `end` on the surface
|
||||
* (the range is a surface-POSITION span, not a numeric seq interval — a
|
||||
* prior replace can leave the surface non-monotonic in seq order), or if
|
||||
* either boundary is not step-aligned (would split a step's tool-call/result
|
||||
* pair).
|
||||
*/
|
||||
abstract compactRegion(
|
||||
session: Session,
|
||||
|
||||
@@ -48,9 +48,16 @@ export interface CompactionResult {
|
||||
endSeq: number
|
||||
/** The summary content blocks produced by the backend. */
|
||||
summary: ContentBlock[]
|
||||
/** The seq range that was shadowed [start, end] inclusive. */
|
||||
/**
|
||||
* The surface-boundary pair that was shadowed: the seqs of the first
|
||||
* (`start`) and last (`end`) surface nodes of the replaced range. A
|
||||
* surface-POSITION span, not a numeric seq interval — after a prior replace
|
||||
* lands a fresh high-seq summary node at an older range's position, `start`
|
||||
* can be GREATER than `end`. {@link CompactionResult.shadowedSeqs} is the
|
||||
* authoritative set of shadowed nodes, in surface order.
|
||||
*/
|
||||
shadowedRange: { start: number; end: number }
|
||||
/** The seq numbers of all shadowed surface nodes. */
|
||||
/** The seqs of all shadowed surface nodes, in surface order. */
|
||||
shadowedSeqs: number[]
|
||||
/** Estimated token count of the shadowed content. */
|
||||
shadowedTokenCount: number
|
||||
|
||||
Reference in New Issue
Block a user