Skip to main content

codetether_agent/session/context/
derive.rs

1//! Phase-A legacy derivation: clone + experimental + enforce + pairing repair.
2
3use std::sync::Arc;
4
5use anyhow::Result;
6use tokio::sync::mpsc;
7
8use crate::provider::ToolDefinition;
9use crate::session::ResidencyLevel;
10use crate::session::Session;
11use crate::session::SessionEvent;
12use crate::session::helper::compression::{CompressContext, compress_last_message_if_oversized};
13use crate::session::helper::experimental;
14
15use super::compress_step::run_compression_step;
16use super::helpers::{DerivedContext, messages_len_changed};
17
18/// Derive an ephemeral [`DerivedContext`] from `session`'s pure history.
19///
20/// The canonical [`Session::messages`] buffer is never touched —
21/// `session` is borrowed immutably.
22///
23/// # Arguments
24///
25/// * `session` — The owning session (read-only borrow).
26/// * `provider` — Caller's primary provider.
27/// * `model` — Caller's primary model identifier.
28/// * `system_prompt` — Included in token estimates.
29/// * `tools` — Tool definitions, included in token estimates.
30/// * `event_tx` — Optional channel for compaction lifecycle events.
31/// * `force_keep_last` — When `Some(n)`, skip the adaptive budget
32///   cascade and force a single [`compress_messages_keep_last`] call.
33///
34/// # Errors
35///
36/// Propagates any error from the underlying compression pipeline that
37/// the recovery cascade cannot absorb.
38///
39/// [`compress_messages_keep_last`]: crate::session::helper::compression::compress_messages_keep_last
40pub async fn derive_context(
41    session: &Session,
42    provider: Arc<dyn crate::provider::Provider>,
43    model: &str,
44    system_prompt: &str,
45    tools: &[ToolDefinition],
46    event_tx: Option<&mpsc::Sender<SessionEvent>>,
47    force_keep_last: Option<usize>,
48) -> Result<DerivedContext> {
49    let origin_len = session.messages.len();
50    let mut messages = session.messages.clone();
51    let ctx = CompressContext::from_session(session);
52
53    let step0 =
54        compress_last_message_if_oversized(&mut messages, &ctx, Arc::clone(&provider), model)
55            .await?;
56
57    experimental::apply_all(&mut messages);
58
59    let before = messages.len();
60    let step1 = run_compression_step(
61        &mut messages,
62        &ctx,
63        provider,
64        model,
65        system_prompt,
66        tools,
67        event_tx,
68        force_keep_last,
69    )
70    .await?;
71
72    experimental::pairing::repair_orphans(&mut messages);
73
74    let compressed = step0 || step1 || messages_len_changed(before, &messages);
75    let mut provenance = vec!["legacy".to_string()];
76    if step0 {
77        provenance.push("oversized_last_message".to_string());
78    }
79    if step1 {
80        provenance.push("context_window".to_string());
81    }
82    if compressed && provenance.len() == 1 {
83        provenance.push("experimental".to_string());
84    }
85    Ok(DerivedContext {
86        resolutions: vec![ResidencyLevel::Full; messages.len()],
87        dropped_ranges: Vec::new(),
88        provenance,
89        messages,
90        origin_len,
91        compressed,
92    })
93}