1use std::sync::{Arc, OnceLock};
7
8use crate::SessionPolicy;
9use crate::SessionStateEnvelope;
10use crate::runtime::PersistedSessionState;
11
12use super::PluginError;
13
14#[derive(Clone, Debug)]
16pub enum RewriteTrigger {
17 Manual { instructions: Option<String> },
19 WindowShrink {
21 old_max: Option<usize>,
22 new_max: Option<usize>,
23 },
24 Periodic,
27}
28
29#[derive(Clone, Debug, Default)]
31pub struct HistoryRewriteMetadata {
32 pub summarized_token_count: Option<u64>,
33 pub pruned_message_count: u32,
34 pub produced_summary: bool,
35}
36
37#[derive(Clone, Debug)]
39pub struct HistoryState {
40 pub messages: Vec<crate::Message>,
41 pub tool_calls: Vec<crate::ToolCallRecord>,
42 pub metadata: HistoryRewriteMetadata,
43}
44
45impl HistoryState {
46 pub fn from_state(state: &SessionStateEnvelope) -> Self {
47 let read_view = state.read_view();
48 Self {
49 messages: read_view.messages().to_vec(),
50 tool_calls: read_view.tool_calls().to_vec(),
51 metadata: HistoryRewriteMetadata::default(),
52 }
53 }
54}
55
56#[derive(Clone, Debug)]
57pub struct SessionReadView(Arc<SessionReadState>);
58
59#[derive(Debug)]
60struct SessionReadState {
61 meta: SessionReadMeta,
62 graph: SessionReadGraph,
63 read_model: crate::session_graph::SessionReadModel,
64 chronological_projection: OnceLock<Arc<crate::ChronologicalProjection>>,
65}
66
67#[derive(Clone, Debug)]
68struct SessionReadMeta {
69 session_id: String,
70 policy: SessionPolicy,
71 turn_index: usize,
72 token_usage: crate::TokenUsage,
73 last_prompt_usage: Option<crate::runtime::PromptUsage>,
74 mode_turn_options: crate::ModeTurnOptions,
75}
76
77impl SessionReadMeta {
78 fn from_state_ref(state: &SessionStateEnvelope) -> Self {
79 Self {
80 session_id: state.session_id.clone(),
81 policy: state.policy.clone(),
82 turn_index: state.turn_index,
83 token_usage: state.token_usage.clone(),
84 last_prompt_usage: state.last_prompt_usage.clone(),
85 mode_turn_options: state.mode_turn_options.clone(),
86 }
87 }
88
89 fn from_state_owned(state: SessionStateEnvelope) -> Self {
90 Self {
91 session_id: state.session_id,
92 policy: state.policy,
93 turn_index: state.turn_index,
94 token_usage: state.token_usage,
95 last_prompt_usage: state.last_prompt_usage,
96 mode_turn_options: state.mode_turn_options,
97 }
98 }
99
100 fn from_persisted_ref(state: &PersistedSessionState) -> Self {
101 Self {
102 session_id: state.session_id.clone(),
103 policy: state.policy.clone(),
104 turn_index: state.turn_index,
105 token_usage: state.token_usage.clone(),
106 last_prompt_usage: state.last_prompt_usage.clone(),
107 mode_turn_options: state.mode_turn_options.clone(),
108 }
109 }
110
111 fn with_policy(mut self, policy: SessionPolicy) -> Self {
112 self.policy = policy;
113 self
114 }
115
116 fn with_turn_index(mut self, turn_index: usize) -> Self {
117 self.turn_index = turn_index;
118 self
119 }
120
121 fn with_mode_turn_options(mut self, mode_turn_options: crate::ModeTurnOptions) -> Self {
122 self.mode_turn_options = mode_turn_options;
123 self
124 }
125
126 fn to_owned_state(&self, session_graph: crate::SessionGraph) -> SessionStateEnvelope {
127 SessionStateEnvelope {
128 session_id: self.session_id.clone(),
129 policy: self.policy.clone(),
130 session_graph,
131 turn_index: self.turn_index,
132 token_usage: self.token_usage.clone(),
133 last_prompt_usage: self.last_prompt_usage.clone(),
134 mode_turn_options: self.mode_turn_options.clone(),
135 }
136 }
137}
138
139#[derive(Debug)]
140enum SessionReadGraph {
141 Owned(Arc<crate::SessionGraph>),
142 Derived {
143 cache: OnceLock<Arc<crate::SessionGraph>>,
144 base_graph: Option<Arc<crate::SessionGraph>>,
145 },
146}
147
148impl SessionReadView {
149 pub fn from_derived_message_view(
150 mut state: SessionStateEnvelope,
151 active_events: Arc<Vec<crate::SessionEventRecord>>,
152 messages: Arc<Vec<crate::Message>>,
153 tool_calls: Arc<Vec<crate::ToolCallRecord>>,
154 ) -> Self {
155 Self(Arc::new(SessionReadState {
156 meta: SessionReadMeta::from_state_owned({
157 state.session_graph = crate::SessionGraph::default();
158 state
159 }),
160 graph: SessionReadGraph::Derived {
161 cache: OnceLock::new(),
162 base_graph: None,
163 },
164 read_model: crate::session_graph::SessionReadModel {
165 active_events,
166 messages,
167 tool_calls,
168 prompt_render_cache: Arc::new(crate::BaseRenderCache::new()),
169 },
170 chronological_projection: OnceLock::new(),
171 }))
172 }
173
174 fn from_graph_message_sequence_meta(
175 meta: SessionReadMeta,
176 base_graph: Arc<crate::SessionGraph>,
177 messages: crate::MessageSequence,
178 tool_calls: Arc<Vec<crate::ToolCallRecord>>,
179 ) -> Self {
180 let base_read_model = base_graph.read_model();
181 let active_events = base_read_model.active_events;
182 Self(Arc::new(SessionReadState {
183 meta,
184 graph: SessionReadGraph::Derived {
185 cache: OnceLock::new(),
186 base_graph: Some(base_graph),
187 },
188 read_model: crate::session_graph::SessionReadModel {
189 active_events,
190 messages: messages.shared(),
191 tool_calls,
192 prompt_render_cache: Arc::new(crate::BaseRenderCache::new()),
193 },
194 chronological_projection: OnceLock::new(),
195 }))
196 }
197
198 pub fn from_exported_state(state: &SessionStateEnvelope) -> Self {
199 let read_model = state.session_graph.read_model();
200 Self(Arc::new(SessionReadState {
201 meta: SessionReadMeta::from_state_ref(state),
202 graph: SessionReadGraph::Owned(Arc::new(state.session_graph.clone())),
203 read_model,
204 chronological_projection: OnceLock::new(),
205 }))
206 }
207
208 pub fn from_persisted_state(state: &PersistedSessionState) -> Self {
209 let graph = Arc::new(state.session_graph.clone());
210 let read_model = graph.read_model();
211 Self(Arc::new(SessionReadState {
212 meta: SessionReadMeta::from_persisted_ref(state),
213 graph: SessionReadGraph::Owned(graph),
214 read_model,
215 chronological_projection: OnceLock::new(),
216 }))
217 }
218
219 pub(crate) fn from_runtime_state(
220 state: &PersistedSessionState,
221 policy: SessionPolicy,
222 mode_turn_options: crate::ModeTurnOptions,
223 ) -> Self {
224 let graph = Arc::new(state.session_graph.clone());
225 let read_model = graph.read_model();
226 Self(Arc::new(SessionReadState {
227 meta: SessionReadMeta::from_persisted_ref(state)
228 .with_policy(policy)
229 .with_mode_turn_options(mode_turn_options),
230 graph: SessionReadGraph::Owned(graph),
231 read_model,
232 chronological_projection: OnceLock::new(),
233 }))
234 }
235
236 pub(crate) fn derived_from_persisted_state(
237 state: &PersistedSessionState,
238 policy: SessionPolicy,
239 turn_index: usize,
240 mode_turn_options: crate::ModeTurnOptions,
241 base_graph: Arc<crate::SessionGraph>,
242 messages: crate::MessageSequence,
243 tool_calls: Arc<Vec<crate::ToolCallRecord>>,
244 ) -> Self {
245 Self::from_graph_message_sequence_meta(
246 SessionReadMeta::from_persisted_ref(state)
247 .with_policy(policy)
248 .with_turn_index(turn_index)
249 .with_mode_turn_options(mode_turn_options),
250 base_graph,
251 messages,
252 tool_calls,
253 )
254 }
255
256 fn graph_arc(&self) -> &Arc<crate::SessionGraph> {
257 match &self.0.graph {
258 SessionReadGraph::Owned(graph) => graph,
259 SessionReadGraph::Derived { cache, base_graph } => cache.get_or_init(|| {
260 let mut graph = base_graph
261 .as_ref()
262 .map(|graph| graph.as_ref().clone())
263 .unwrap_or_default();
264 graph.replace_active_read_state(
265 self.0.read_model.messages.as_slice(),
266 self.0.read_model.tool_calls.as_slice(),
267 );
268 Arc::new(graph)
269 }),
270 }
271 }
272
273 pub fn session_id(&self) -> &str {
274 &self.0.meta.session_id
275 }
276
277 pub fn policy(&self) -> &SessionPolicy {
278 &self.0.meta.policy
279 }
280
281 fn session_graph(&self) -> &crate::SessionGraph {
282 self.graph_arc().as_ref()
283 }
284
285 pub fn materialized_session_graph(&self) -> crate::SessionGraph {
286 self.session_graph().clone()
287 }
288
289 pub fn messages(&self) -> &[crate::Message] {
290 self.0.read_model.messages.as_slice()
291 }
292
293 pub fn tool_calls(&self) -> &[crate::ToolCallRecord] {
294 self.0.read_model.tool_calls.as_slice()
295 }
296
297 pub fn active_events(&self) -> &[crate::SessionEventRecord] {
298 self.0.read_model.active_events.as_slice()
299 }
300
301 pub fn chronological_projection(&self) -> crate::ChronologicalProjection {
302 crate::ChronologicalProjection::from_read_model(&self.0.read_model)
303 }
304
305 pub(crate) fn shared_chronological_projection(&self) -> Arc<crate::ChronologicalProjection> {
306 Arc::clone(self.0.chronological_projection.get_or_init(|| {
307 Arc::new(crate::ChronologicalProjection::from_read_model(
308 &self.0.read_model,
309 ))
310 }))
311 }
312
313 pub fn message_tree(&self) -> Vec<crate::SessionMessageTreeNode> {
314 self.session_graph().message_tree()
315 }
316
317 pub fn turn_index(&self) -> usize {
318 self.0.meta.turn_index
319 }
320
321 pub fn token_usage(&self) -> &crate::TokenUsage {
322 &self.0.meta.token_usage
323 }
324
325 pub fn last_prompt_usage(&self) -> Option<&crate::runtime::PromptUsage> {
326 self.0.meta.last_prompt_usage.as_ref()
327 }
328
329 pub fn mode_turn_options(&self) -> &crate::ModeTurnOptions {
330 &self.0.meta.mode_turn_options
331 }
332
333 pub fn to_owned_state(&self) -> SessionStateEnvelope {
334 self.0.meta.to_owned_state(self.session_graph().clone())
335 }
336}
337
338#[derive(Clone)]
340pub struct TurnTransformContext {
341 pub session_id: String,
342 pub state: SessionReadView,
343 pub prompt_usage: Option<crate::runtime::PromptUsage>,
344 pub max_context_tokens: Option<usize>,
345 pub host: Arc<dyn super::HistoryHost>,
346}
347
348#[derive(Clone)]
350pub struct RewriteContext {
351 pub session_id: String,
352 pub trigger: RewriteTrigger,
353 pub state: SessionReadView,
354 pub host: Arc<dyn super::HistoryHost>,
355}
356
357#[derive(Debug, thiserror::Error, Clone)]
358pub enum HistoryError {
359 #[error("history pipeline error: {0}")]
360 Pipeline(String),
361 #[error("history session error: {0}")]
362 Session(String),
363}
364
365impl From<PluginError> for HistoryError {
366 fn from(value: PluginError) -> Self {
367 Self::Session(value.to_string())
368 }
369}
370
371#[async_trait::async_trait]
373pub trait TurnContextTransform: Send + Sync {
374 fn id(&self) -> &'static str;
375 async fn transform(
376 &self,
377 ctx: &TurnTransformContext,
378 input: crate::session_model::context::PreparedContext,
379 ) -> Result<crate::session_model::context::PreparedContext, HistoryError>;
380}
381
382#[async_trait::async_trait]
385pub trait HistoryRewriter: Send + Sync {
386 fn id(&self) -> &'static str;
387 fn accepts(&self, _trigger: &RewriteTrigger) -> bool {
388 true
389 }
390 async fn rewrite(
391 &self,
392 ctx: &RewriteContext,
393 input: HistoryState,
394 ) -> Result<HistoryState, HistoryError>;
395}