Prune old tool results to save context space. Mirrors TS `SessionCompaction.prune`. Walks backwards through messages, skipping the most recent 2 user turns, and erases output of tool parts whose cumulative token count exceeds `PRUNE_PROTECT`. Returns the IDs of pruned parts. The caller is responsible for persisting the updated parts via `SessionOps::update_part`.
(&self, messages: &mut [MessageForPrune])
| 282 | /// Returns the IDs of pruned parts. The caller is responsible for |
| 283 | /// persisting the updated parts via `SessionOps::update_part`. |
| 284 | pub fn prune(&self, messages: &mut [MessageForPrune]) -> Vec<String> { |
| 285 | // TS: if (config.compaction?.prune === false) return |
| 286 | if !self.config.prune { |
| 287 | return vec![]; |
| 288 | } |
| 289 | |
| 290 | if !messages.is_empty() && !Self::should_prune(messages) { |
| 291 | return vec![]; |
| 292 | } |
| 293 | |
| 294 | tracing::info!("pruning"); |
| 295 | |
| 296 | let mut total: u64 = 0; |
| 297 | let mut pruned: u64 = 0; |
| 298 | let mut to_prune: Vec<(usize, usize)> = Vec::new(); |
| 299 | let mut turns = 0; |
| 300 | |
| 301 | 'outer: for msg_index in (0..messages.len()).rev() { |
| 302 | let msg = &messages[msg_index]; |
| 303 | if msg.role == "user" { |
| 304 | turns += 1; |
| 305 | } |
| 306 | if turns < 2 { |
| 307 | continue; |
| 308 | } |
| 309 | // TS: if (msg.info.role === "assistant" && msg.info.summary) break loop |
| 310 | if msg.role == "assistant" && msg.summary { |
| 311 | break; |
| 312 | } |
| 313 | |
| 314 | for part_index in (0..msg.parts.len()).rev() { |
| 315 | let part = &msg.parts[part_index]; |
| 316 | // Skip non-tool parts |
| 317 | if part.tool.is_empty() { |
| 318 | continue; |
| 319 | } |
| 320 | |
| 321 | // TS: if (part.state.status === "completed") { ... } |
| 322 | // Only prune completed tool parts |
| 323 | if part.status != ToolPartStatus::Completed { |
| 324 | continue; |
| 325 | } |
| 326 | |
| 327 | if PRUNE_PROTECTED_TOOLS.contains(&part.tool.as_str()) { |
| 328 | continue; |
| 329 | } |
| 330 | |
| 331 | // TS: if (part.state.time.compacted) break loop |
| 332 | if part.compacted.is_some() { |
| 333 | break 'outer; |
| 334 | } |
| 335 | |
| 336 | let estimate = Self::estimate_tokens(&part.output); |
| 337 | total += estimate; |
| 338 | if total > PRUNE_PROTECT { |
| 339 | pruned += estimate; |
| 340 | to_prune.push((msg_index, part_index)); |
| 341 | } |