phi_core/context/
orchestration.rs1use super::compaction::*;
2use super::config::*;
3use super::strategy::*;
4use super::token::{resolve_counter, TokenCounter};
5use crate::session::Session;
6use crate::types::*;
7use std::sync::Arc;
8
9fn resolve_scope(
25 session: &Session,
26 chain: &[String],
27 scope: &CompactionScope,
28 max_context_tokens: usize,
29 counter: &dyn TokenCounter,
30) -> usize {
31 match scope {
32 CompactionScope::FixedCount(n) => *n,
33 CompactionScope::TokenBudget => {
34 let mut budget = max_context_tokens;
35 let mut count = 0usize;
36 for loop_id in chain.iter().rev().skip(1) {
38 if let Some(record) = session.get_loop(loop_id) {
39 let loop_tokens = counter.estimate_messages(&record.messages);
40 if loop_tokens > budget {
41 break;
42 }
43 budget -= loop_tokens;
44 count += 1;
45 }
46 }
47 count
48 }
49 }
50}
51
52pub fn compact_session_loops(
58 session: &mut Session,
59 current_loop_id: &str,
60 strategy: &dyn BlockCompactionStrategy,
61 config: &CompactionConfig,
62 max_context_tokens: usize,
63 counter: Option<&Arc<dyn TokenCounter>>,
64) {
65 let counter = resolve_counter(counter);
66 let chain = session.loop_chain_to(current_loop_id);
67
68 if let Some(current) = session.get_loop_mut(current_loop_id) {
70 current.compaction_block = Some(strategy.compact(current, config, true));
71 }
72
73 let earlier_count = resolve_scope(
75 session,
76 &chain,
77 &config.compaction_scope,
78 max_context_tokens,
79 counter,
80 )
81 .min(chain.len().saturating_sub(1));
82 let earlier_start = chain.len().saturating_sub(1 + earlier_count);
83 for loop_id in &chain[earlier_start..chain.len().saturating_sub(1)] {
84 if let Some(record) = session.get_loop_mut(loop_id) {
85 if record.compaction_block.is_none() {
86 record.compaction_block = Some(strategy.compact(record, config, false));
87 }
88 }
89 }
90}
91
92pub fn build_context_from_session(
105 session: &Session,
106 current_loop_id: &str,
107 config: &CompactionConfig,
108 max_context_tokens: usize,
109 counter: Option<&Arc<dyn TokenCounter>>,
110) -> Vec<AgentMessage> {
111 let counter = resolve_counter(counter);
112 let chain = session.loop_chain_to(current_loop_id);
113 let mut context = Vec::new();
114
115 let earlier_count = resolve_scope(
116 session,
117 &chain,
118 &config.compaction_scope,
119 max_context_tokens,
120 counter,
121 );
122 let load_start = chain.len().saturating_sub(earlier_count + 1);
123
124 for (i, loop_id) in chain.iter().enumerate().skip(load_start) {
125 let Some(record) = session.get_loop(loop_id) else {
126 continue;
127 };
128 let is_most_recent = i == chain.len() - 1;
129
130 match &record.compaction_block {
131 Some(block) => {
132 if is_most_recent {
133 if let Some(ref range) = block.keep_first {
135 let turn_map = TurnMap::from_messages(&record.messages);
136 let msgs = turn_map.messages_for_range(range, &record.messages);
137 context.extend_from_slice(msgs);
138 }
139 if let Some(ref section) = block.keep_compacted {
141 context.extend(section.messages.iter().cloned());
142 }
143 if let Some(ref section) = block.keep_recent {
145 context.extend(section.messages.iter().cloned());
146 }
147 } else {
148 if let Some(ref section) = block.keep_compacted {
150 context.extend(section.messages.iter().cloned());
151 }
152 }
153 }
154 None => {
155 context.extend(record.messages.iter().cloned());
157 }
158 }
159 }
160
161 context
162}