source dump of claude code
at main 63 lines 2.8 kB view raw
1import type { Message } from '../../types/message.js' 2 3/** 4 * Groups messages at API-round boundaries: one group per API round-trip. 5 * A boundary fires when a NEW assistant response begins (different 6 * message.id from the prior assistant). For well-formed conversations 7 * this is an API-safe split point — the API contract requires every 8 * tool_use to be resolved before the next assistant turn, so pairing 9 * validity falls out of the assistant-id boundary. For malformed inputs 10 * (dangling tool_use after resume/truncation) the fork's 11 * ensureToolResultPairing repairs the split at API time. 12 * 13 * Replaces the prior human-turn grouping (boundaries only at real user 14 * prompts) with finer-grained API-round grouping, allowing reactive 15 * compact to operate on single-prompt agentic sessions (SDK/CCR/eval 16 * callers) where the entire workload is one human turn. 17 * 18 * Extracted to its own file to break the compact.ts ↔ compactMessages.ts 19 * cycle (CC-1180) — the cycle shifted module-init order enough to surface 20 * a latent ws CJS/ESM resolution race in CI shard-2. 21 */ 22export function groupMessagesByApiRound(messages: Message[]): Message[][] { 23 const groups: Message[][] = [] 24 let current: Message[] = [] 25 // message.id of the most recently seen assistant. This is the sole 26 // boundary gate: streaming chunks from the same API response share an 27 // id, so boundaries only fire at the start of a genuinely new round. 28 // normalizeMessages yields one AssistantMessage per content block, and 29 // StreamingToolExecutor interleaves tool_results between chunks live 30 // (yield order, not concat order — see query.ts:613). The id check 31 // correctly keeps `[tu_A(id=X), result_A, tu_B(id=X)]` in one group. 32 let lastAssistantId: string | undefined 33 34 // In a well-formed conversation the API contract guarantees every 35 // tool_use is resolved before the next assistant turn, so lastAssistantId 36 // alone is a sufficient boundary gate. Tracking unresolved tool_use IDs 37 // would only do work when the conversation is malformed (dangling tool_use 38 // after resume-from-partial-batch or max_tokens truncation) — and in that 39 // case it pins the gate shut forever, merging all subsequent rounds into 40 // one group. We let those boundaries fire; the summarizer fork's own 41 // ensureToolResultPairing at claude.ts:1136 repairs the dangling tu at 42 // API time. 43 for (const msg of messages) { 44 if ( 45 msg.type === 'assistant' && 46 msg.message.id !== lastAssistantId && 47 current.length > 0 48 ) { 49 groups.push(current) 50 current = [msg] 51 } else { 52 current.push(msg) 53 } 54 if (msg.type === 'assistant') { 55 lastAssistantId = msg.message.id 56 } 57 } 58 59 if (current.length > 0) { 60 groups.push(current) 61 } 62 return groups 63}