artifactize_core/inspect/
session.rs1mod 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
11#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize)]
14#[serde(try_from = "String")]
15pub enum Selector {
16 Reference(SessionRef),
17 Id {
18 request: crate::types::RequestId,
19 session: crate::types::SessionId,
20 },
21}
22
23impl std::str::FromStr for Selector {
24 type Err = String;
25
26 fn from_str(text: &str) -> Result<Self, String> {
27 if let Some(reference) = SessionRef::parse(text) {
28 return Ok(Self::Reference(reference));
29 }
30 match (text.parse(), text.parse()) {
31 (Ok(request), Ok(session)) => Ok(Self::Id { request, session }),
32 _ => Err(format!(
33 "{text} is not a session reference, a request id or a session id."
34 )),
35 }
36 }
37}
38
39impl TryFrom<String> for Selector {
40 type Error = String;
41
42 fn try_from(text: String) -> Result<Self, String> {
43 text.parse()
44 }
45}
46
47pub struct Located {
49 pub request: RequestView,
51 pub reused_by: Option<crate::types::RequestId>,
53 pub id: crate::types::SessionId,
54 pub reference: Option<SessionRef>,
56 pub path: PathBuf,
57}
58
59impl Located {
60 pub fn load(&self) -> Result<Conversation, String> {
62 Conversation::load(&self.path)?.ok_or_else(|| {
63 let request = &self.request.request.id;
64 if self.reference.is_some() {
65 format!(
66 "Session {} of request {request} was removed by the session GC (limits.json agentSessions).",
67 self.id
68 )
69 } else {
70 format!(
71 "Session {} of request {request} was not saved: saving was off (limits.json agentSessions.enabled), or the review never started.",
72 self.id
73 )
74 }
75 })
76 }
77}
78
79fn here(state: &Path, reference: &SessionRef) -> Result<(), String> {
81 let producer = Producer::current().name;
82 let state_id = store::read_state_id(state)?;
83 if reference.producer == producer
84 && Some(reference.state.as_str()) == state_id.as_ref().map(crate::types::StateId::as_str)
85 {
86 return Ok(());
87 }
88 Err(format!(
89 "Session {} lives in {}'s state {}; this is {producer}'s state {}. Read or continue it there.",
90 reference.session_id,
91 reference.producer,
92 reference.state,
93 state_id.as_deref().unwrap_or("(not created yet)")
94 ))
95}
96
97pub async fn locate(state: &Path, selector: &Selector) -> Result<Located, String> {
99 let mut view = match selector {
100 Selector::Reference(reference) => {
101 here(state, reference)?;
102 let view = store::read_request(state, &reference.request_id)
103 .await
104 .map_err(|_| format!("Request {} is not in this state.", reference.request_id))?;
105 if view.request.session_id.as_ref() != Some(&reference.session_id) {
106 return Err(format!(
107 "Request {} did not run session {}.",
108 reference.request_id, reference.session_id
109 ));
110 }
111 view
112 }
113 Selector::Id { request, session } => {
114 match store::read_request(state, request.as_str()).await {
115 Ok(view) => view,
116 Err(_) => store::read_session_request(state, session.as_str())
117 .await?
118 .ok_or_else(|| {
119 format!(
120 "No Agent request or session {session} in this state ({}).",
121 crate::platform::path_text(state)
122 )
123 })?,
124 }
125 }
126 };
127 let mut reused_by = None;
128 if view.request.session_id.is_none() {
129 let Some(reference) = view.request.session.clone() else {
131 return Err(format!(
132 "Request {} ran no Agent review and names no saved conversation.",
133 view.request.id
134 ));
135 };
136 here(state, &reference)?;
137 reused_by = Some(view.request.id.clone());
138 view = store::read_request(state, &reference.request_id)
139 .await
140 .map_err(|_| format!("Request {} is not in this state.", reference.request_id))?;
141 }
142 let id = view
143 .request
144 .session_id
145 .clone()
146 .ok_or_else(|| format!("Request {} ran no Agent review.", view.request.id))?;
147 Ok(Located {
148 path: session::path(state, &id)?,
149 reference: view.request.session.clone(),
150 reused_by,
151 id,
152 request: view,
153 })
154}
155
156pub async fn show(state: &Path, selector: &Selector, summary: bool) -> Result<Value, String> {
158 let located = locate(state, selector).await?;
159 let conversation = located.load()?;
160 if summary {
161 let mut value = json!(Summary::new(&conversation));
162 value["reference"] = json!(located.reference.as_ref().map(ToString::to_string));
163 value["sessionId"] = json!(located.id);
164 value["requestId"] = json!(located.request.request.id);
165 Ok(value)
166 } else {
167 Ok(
168 json!({"reference": located.reference.as_ref().map(ToString::to_string),
169 "file": crate::platform::path_text(&conversation.path), "events": conversation.wire_events}),
170 )
171 }
172}