coalesceMessagesForCap function

List<Message> coalesceMessagesForCap(
  1. List<Message> messages, {
  2. required int targetMessageCount,
})

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;
}