Skip to main content

artifactize_core/inspect/
session.rs

1//! Saved conversation lookup and continuation shared by interfaces.
2mod send;
3use crate::{
4    agent::session::{self, Conversation, SessionRef, Summary},
5    store::{self, Producer, RequestView},
6};
7pub use send::{Sent, send};
8use serde_json::{Value, json};
9use std::path::{Path, PathBuf};
10/// The review whose conversation a reference names, in this state.
11pub struct Located {
12    /// The request whose Agent review ran as the session.
13    pub request: RequestView,
14    /// The request the reference named, when it reused that review's result.
15    pub reused_by: Option<crate::types::RequestId>,
16    pub id: crate::types::SessionId,
17    /// Set once the conversation was saved; absent when saving was off.
18    pub reference: Option<SessionRef>,
19    pub path: PathBuf,
20}
21
22impl Located {
23    /// The saved conversation, or why there is none.
24    pub fn load(&self) -> Result<Conversation, String> {
25        Conversation::load(&self.path)?.ok_or_else(|| {
26            let request = &self.request.request.id;
27            if self.reference.is_some() {
28                format!(
29                    "Session {} of request {request} was removed by the session GC (limits.json agentSessions).",
30                    self.id
31                )
32            } else {
33                format!(
34                    "Session {} of request {request} was not saved: saving was off (limits.json agentSessions.enabled), or the review never started.",
35                    self.id
36                )
37            }
38        })
39    }
40}
41
42/// Fail unless `reference` names this machine's producer and this state.
43fn here(state: &Path, reference: &SessionRef) -> Result<(), String> {
44    let producer = Producer::current().name;
45    let state_id = store::read_state_id(state)?;
46    if reference.producer == producer
47        && Some(reference.state.as_str()) == state_id.as_ref().map(crate::types::StateId::as_str)
48    {
49        return Ok(());
50    }
51    Err(format!(
52        "Session {} lives in {}'s state {}; this is {producer}'s state {}. Read or continue it there.",
53        reference.session_id,
54        reference.producer,
55        reference.state,
56        state_id.as_deref().unwrap_or("(not created yet)")
57    ))
58}
59
60/// Resolve a full reference, a request id or a session id to its review's request.
61pub async fn locate(state: &Path, text: &str) -> Result<Located, String> {
62    let mut view = if let Some(reference) = SessionRef::parse(text) {
63        here(state, &reference)?;
64        let view = store::read_request(state, &reference.request_id)
65            .await
66            .map_err(|_| format!("Request {} is not in this state.", reference.request_id))?;
67        if view.request.session_id.as_ref() != Some(&reference.session_id) {
68            return Err(format!(
69                "Request {} did not run session {}.",
70                reference.request_id, reference.session_id
71            ));
72        }
73        view
74    } else {
75        match store::read_request(state, text).await {
76            Ok(view) => view,
77            Err(_) => store::read_session_request(state, text)
78                .await?
79                .ok_or_else(|| {
80                    format!(
81                        "No Agent request or session {text} in this state ({}).",
82                        crate::platform::path_text(state)
83                    )
84                })?,
85        }
86    };
87    let mut reused_by = None;
88    if view.request.session_id.is_none() {
89        // A reused result names the review that produced it, which may live elsewhere.
90        let Some(reference) = view.request.session.clone() else {
91            return Err(format!(
92                "Request {} ran no Agent review and names no saved conversation.",
93                view.request.id
94            ));
95        };
96        here(state, &reference)?;
97        reused_by = Some(view.request.id.clone());
98        view = store::read_request(state, &reference.request_id)
99            .await
100            .map_err(|_| format!("Request {} is not in this state.", reference.request_id))?;
101    }
102    let id = view
103        .request
104        .session_id
105        .clone()
106        .ok_or_else(|| format!("Request {} ran no Agent review.", view.request.id))?;
107    Ok(Located {
108        path: session::path(state, &id)?,
109        reference: view.request.session.clone(),
110        reused_by,
111        id,
112        request: view,
113    })
114}
115
116/// Project the same saved events or summary that `session show --json` prints.
117pub async fn show(state: &Path, text: &str, summary: bool) -> Result<Value, String> {
118    let located = locate(state, text).await?;
119    let conversation = located.load()?;
120    if summary {
121        let mut value = json!(Summary::new(&conversation));
122        value["reference"] = json!(located.reference.as_ref().map(ToString::to_string));
123        value["sessionId"] = json!(located.id);
124        value["requestId"] = json!(located.request.request.id);
125        Ok(value)
126    } else {
127        Ok(
128            json!({"reference": located.reference.as_ref().map(ToString::to_string),
129            "file": crate::platform::path_text(&conversation.path), "events": conversation.wire_events}),
130        )
131    }
132}