coalesceMessagesForCap function
gh-1680 AC6/E2/E3: coalesces the OUTBOUND message list so its length
approaches targetMessageCount, merging whole tool-call/result pairs
and old turns older than the newest user message into compact carrier
messages.
Invariants (asserted by tests):
- The system prompt is not in this list (it rides Context.systemPrompt); the newest user message is NEVER merged or dropped, and nothing after it is touched.
- Only WHOLE tool-call/result groups go: an assistant message with tool calls and every result answering those calls merge together, so no orphaned result or unanswered call can appear on the wire. A group SPLIT by a steering interleave (a result answering from the kept tail or beyond a mid-region user message) never merges at all — the call rides verbatim and the pairing repair hoists the far-side result back (PR #1695). The result is still run through repairToolPairing as the belt on the suspenders (pre-broken input pairing is repaired, never propagated).
- The input list is never mutated (outbound-only; the session JSONL stays byte-identical, AC7).
Returns the input list unchanged when it already fits target (or
nothing is droppable — the newest user turn alone is over target, E3).
Implementation
List<Message> coalesceMessagesForCap(
List<Message> messages, {
required int targetMessageCount,
}) {
if (messages.length <= targetMessageCount) return messages;
// The cut: the newest user message stays verbatim, so the droppable
// region is [0, newestUserIndex). With no user message at all (a pure
// tool-call transcript), the LAST message is kept instead — a request
// must never end up empty (E3).
final regionEnd = _coalesceRegionEnd(messages);
if (regionEnd <= 0) return messages; // Nothing droppable (E2/E3).
final grouped = _groupCoalesceUnits(messages, regionEnd);
// Over target with a single carrier left: nothing more is droppable
// without touching the newest turn (E2/E3) — the wire gets the smallest
// pairing-safe shape we have.
final merged = _mergeCarriersToTarget(
grouped.units,
grouped.verbatim,
messages,
regionEnd,
targetMessageCount,
);
// Pairing belt-and-suspenders: the kept tail may carry pre-broken
// pairing in the input; the request must never propagate it.
return repairToolPairing(merged).messages;
}