Skip to main content

algocline_app/service/
status.rs

1use algocline_engine::PendingFilter;
2
3use super::AppService;
4
5impl AppService {
6    /// Snapshot of all active sessions (or one by ID) for external observation.
7    ///
8    /// # Arguments
9    ///
10    /// * `session_id` - When `Some`, returns detail for one session; when `None`, lists all.
11    /// * `pending_filter` - Optional preset name or custom field-filter for pending query projection.
12    /// * `include_history` - When `true`, each snapshot includes `conversation_history` (cap=10).
13    ///   Pass `false` (the default) for lightweight high-frequency polling snapshots.
14    ///
15    /// # Returns
16    ///
17    /// JSON string with either a single session object or `{active_sessions, sessions}` list.
18    ///
19    /// # Errors
20    ///
21    /// Returns `Err` when `pending_filter` is an unknown preset name or an invalid shape.
22    pub async fn status(
23        &self,
24        session_id: Option<&str>,
25        pending_filter: Option<serde_json::Value>,
26        include_history: bool,
27    ) -> Result<String, String> {
28        let filter = self.resolve_pending_filter(pending_filter)?;
29        let snapshots = self
30            .registry
31            .list_snapshots(filter.as_ref(), include_history)
32            .await;
33
34        // If a specific session requested, return just that one
35        if let Some(sid) = session_id {
36            return match snapshots.get(sid) {
37                Some(snapshot) => {
38                    let mut result = snapshot.clone();
39                    // Enrich with strategy name
40                    if let Ok(strategies) = self.session_strategies.lock() {
41                        if let Some(name) = strategies.get(sid) {
42                            result["strategy"] = serde_json::json!(name);
43                        }
44                    }
45                    result["session_id"] = serde_json::json!(sid);
46                    serde_json::to_string_pretty(&result).map_err(|e| e.to_string())
47                }
48                None => Err(format!("session '{sid}' not found (may have completed)")),
49            };
50        }
51
52        // List all active sessions
53        if snapshots.is_empty() {
54            return Ok(serde_json::json!({
55                "active_sessions": 0,
56                "sessions": [],
57            })
58            .to_string());
59        }
60
61        let strategies = self.session_strategies.lock().ok();
62        let sessions: Vec<serde_json::Value> = snapshots
63            .into_iter()
64            .map(|(id, mut snapshot)| {
65                if let Some(ref strats) = strategies {
66                    if let Some(name) = strats.get(&id) {
67                        snapshot["strategy"] = serde_json::json!(name);
68                    }
69                }
70                snapshot["session_id"] = serde_json::json!(id);
71                snapshot
72            })
73            .collect();
74
75        let result = serde_json::json!({
76            "active_sessions": sessions.len(),
77            "sessions": sessions,
78        });
79
80        serde_json::to_string_pretty(&result).map_err(|e| e.to_string())
81    }
82
83    /// Decode the incoming `pending_filter` JSON value into an optional
84    /// `PendingFilter`. Preset strings read the per-request char count
85    /// from this service's `AppConfig`; custom objects use the values
86    /// declared by the caller.
87    fn resolve_pending_filter(
88        &self,
89        raw: Option<serde_json::Value>,
90    ) -> Result<Option<PendingFilter>, String> {
91        let Some(value) = raw else {
92            return Ok(None);
93        };
94        match value {
95            serde_json::Value::String(name) => PendingFilter::from_preset_with(
96                &name,
97                self.log_config.prompt_preview_chars,
98            )
99            .map(Some)
100            .ok_or_else(|| {
101                format!(
102                    "unknown pending_filter preset '{name}' (valid: \"meta\" | \"preview\" | \"full\")"
103                )
104            }),
105            serde_json::Value::Object(_) => serde_json::from_value::<PendingFilter>(value)
106                .map(Some)
107                .map_err(|e| format!("invalid pending_filter object: {e}")),
108            other => Err(format!(
109                "pending_filter must be a preset name (string) or filter object, got {}",
110                match other {
111                    serde_json::Value::Null => "null",
112                    serde_json::Value::Bool(_) => "bool",
113                    serde_json::Value::Number(_) => "number",
114                    serde_json::Value::Array(_) => "array",
115                    _ => "unknown",
116                }
117            )),
118        }
119    }
120}