Skip to main content

theway_core/agent/context/
collapse.rs

1//! Collapse context: parsing and injecting the old session's compact summary.
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6use crate::agent::session::session::SessionTreeEntry;
7
8/// Custom entry type written into a collapse child session.
9pub const COMPACT_CONTEXT_CUSTOM_TYPE: &str = "compact_context";
10
11/// Structured view of a `compact_context` custom entry.
12#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
13#[serde(rename_all = "camelCase")]
14pub struct CompactContext {
15    pub source_session_id: String,
16    pub compact_text: String,
17    pub raw_text_ref: String,
18}
19
20/// Extract the latest `compact_context` from session entries, newest first.
21pub fn latest_compact_context(entries: &[SessionTreeEntry]) -> Option<CompactContext> {
22    entries.iter().rev().find_map(compact_context_from_entry)
23}
24
25/// Extract a `compact_context` from a single session entry.
26pub fn compact_context_from_entry(entry: &SessionTreeEntry) -> Option<CompactContext> {
27    let SessionTreeEntry::Custom {
28        custom_type, data, ..
29    } = entry
30    else {
31        return None;
32    };
33    if custom_type != COMPACT_CONTEXT_CUSTOM_TYPE {
34        return None;
35    }
36    parse_compact_context(data.as_ref()?)
37}
38
39/// Return the non-empty compact text for an entry, if present.
40pub fn compact_context_text(entry: &SessionTreeEntry) -> Option<String> {
41    compact_context_from_entry(entry)
42        .map(|context| context.compact_text)
43        .filter(|text| !text.trim().is_empty())
44}
45
46/// Parse `compact_context` data, tolerating both `compactText` and legacy `text`.
47fn parse_compact_context(data: &Value) -> Option<CompactContext> {
48    let source_session_id = data
49        .get("sourceSessionId")
50        .and_then(Value::as_str)
51        .unwrap_or_default()
52        .to_string();
53    let compact_text = data
54        .get("compactText")
55        .and_then(Value::as_str)
56        .or_else(|| data.get("text").and_then(Value::as_str))
57        .unwrap_or_default()
58        .to_string();
59    let raw_text_ref = data
60        .get("rawTextRef")
61        .and_then(Value::as_str)
62        .unwrap_or_default()
63        .to_string();
64    if source_session_id.is_empty() && compact_text.trim().is_empty() && raw_text_ref.is_empty() {
65        None
66    } else {
67        Some(CompactContext {
68            source_session_id,
69            compact_text,
70            raw_text_ref,
71        })
72    }
73}