1pub(crate) mod platform;
2pub mod session_config_view;
3pub(crate) mod session_model;
4pub mod terminal;
5pub mod workspace_status;
6
7use crate::error::AppError;
8use crate::session::workspace_status::WorkspaceStatus;
9use acp_utils::client::{AcpClient, AcpClientError, connect_acp_client, initialize_request};
10use acp_utils::notifications::{RemoteServerInfo, SessionPreviewParams};
11use agent_client_protocol::schema::v2::{
12 Implementation, NewSessionRequest, NewSessionResponse, ReplayFrom, ReplayFromStart, ResumeSessionRequest,
13 SessionId,
14};
15use agent_client_protocol::{AcpAgent, Client, ConnectTo};
16use std::env::current_dir;
17use std::path::PathBuf;
18use std::str::FromStr;
19
20pub use workspace_status::WorkspaceAccess;
21
22pub struct Session {
23 pub client: AcpClient,
24 pub response: NewSessionResponse,
25 pub working_dir: PathBuf,
26 pub workspace_status: WorkspaceStatus,
27 pub workspace_access: WorkspaceAccess,
28}
29
30impl Session {
31 pub async fn connect_remote_to(
33 transport: impl ConnectTo<Client> + 'static,
34 requested_session: Option<SessionId>,
35 ) -> Result<Self, AppError> {
36 let client = connect_acp_client(transport, initialize_request(client_info())).await?;
37 let remote = RemoteServerInfo::from_meta(client.initialize_response.meta.as_ref())
38 .ok_or(AppError::MissingRemoteContract)?;
39 let (selected, working_dir) = match (requested_session, remote.session_id) {
40 (Some(requested), live) if live.as_ref() != Some(&requested) => {
41 let cwd = client.handle.request(SessionPreviewParams { session_id: requested.to_string() }).await?.cwd;
42 (Some(requested), cwd)
43 }
44 (requested, live) => (requested.or(live), remote.cwd),
45 };
46 let response = if let Some(id) = selected {
47 let resumed = client.handle
48 .resume_session(
49 ResumeSessionRequest::new(id.clone(), working_dir.clone())
50 .replay_from(ReplayFrom::Start(ReplayFromStart::new())),
51 )
52 .await?;
53 NewSessionResponse::new(id).config_options(resumed.config_options)
54 } else {
55 client.handle.new_session(NewSessionRequest::new(working_dir.clone())).await?
56 };
57 let workspace_status = WorkspaceStatus::initial(&working_dir);
58 Ok(Self { client, response, working_dir, workspace_status, workspace_access: WorkspaceAccess::Remote })
59 }
60
61 pub async fn connect(agent_command: &str) -> Result<Self, AppError> {
62 let working_dir = current_dir().unwrap_or_else(|_| PathBuf::from("."));
63 let agent = AcpAgent::from_str(agent_command).map_err(AcpClientError::InvalidAgentCommand)?;
64 Self::connect_to(agent, working_dir).await
65 }
66
67 pub async fn connect_to(agent: impl ConnectTo<Client> + 'static, working_dir: PathBuf) -> Result<Self, AppError> {
68 let workspace_status = WorkspaceStatus::initial(&working_dir);
69 let client = connect_acp_client(agent, initialize_request(client_info())).await?;
70 let session_response = client.handle.new_session(NewSessionRequest::new(working_dir.clone())).await?;
71
72 Ok(Self {
73 client,
74 response: session_response,
75 workspace_access: WorkspaceAccess::Local,
76 working_dir,
77 workspace_status,
78 })
79 }
80}
81
82fn client_info() -> Implementation {
83 Implementation::new("wisp", env!("CARGO_PKG_VERSION"))
84}