Skip to main content

wisp/session/
mod.rs

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::agent::TokioAcpAgent;
10use acp_utils::client::{AcpClient, AcpClientError, connect_acp_client};
11use acp_utils::notifications::{RemoteServerInfo, SessionPreviewParams};
12use agent_client_protocol::schema::ProtocolVersion;
13use agent_client_protocol::schema::v2::{
14    ClientCapabilities, ElicitationCapabilities, ElicitationFormCapabilities, ElicitationUrlCapabilities,
15    Implementation, InitializeRequest, NewSessionRequest, NewSessionResponse, ResumeSessionRequest, SessionId,
16};
17use agent_client_protocol::{Client, ConnectTo};
18use std::env::current_dir;
19use std::path::PathBuf;
20use std::str::FromStr;
21
22pub use workspace_status::WorkspaceAccess;
23
24pub struct Session {
25    pub client: AcpClient,
26    pub response: NewSessionResponse,
27    pub working_dir: PathBuf,
28    pub workspace_status: WorkspaceStatus,
29    pub workspace_access: WorkspaceAccess,
30}
31
32impl Session {
33    /// Initialize against a remote host, resuming its live session unless explicitly overridden.
34    pub async fn connect_remote_to(
35        transport: impl ConnectTo<Client> + 'static,
36        requested_session: Option<SessionId>,
37    ) -> Result<Self, AppError> {
38        let client = connect_acp_client(transport, initialize_request()).await?;
39        let remote = RemoteServerInfo::from_meta(client.initialize_response.meta.as_ref())
40            .ok_or(AppError::MissingRemoteContract)?;
41        let (selected, working_dir) = match (requested_session, remote.session_id) {
42            (Some(requested), live) if live.as_ref() != Some(&requested) => {
43                let cwd = client.handle.request(SessionPreviewParams { session_id: requested.to_string() }).await?.cwd;
44                (Some(requested), cwd)
45            }
46            (requested, live) => (requested.or(live), remote.cwd),
47        };
48        let response = if let Some(id) = selected {
49            let resumed = client.handle
50                .resume_session_with_replay(ResumeSessionRequest::new(id.clone(), working_dir.clone()))
51                .await?;
52            NewSessionResponse::new(id).config_options(resumed.config_options)
53        } else {
54            client.handle.new_session(NewSessionRequest::new(working_dir.clone())).await?
55        };
56        let workspace_status = WorkspaceStatus::remote(&working_dir);
57        Ok(Self { client, response, working_dir, workspace_status, workspace_access: WorkspaceAccess::Remote })
58    }
59
60    pub async fn connect(agent_command: &str) -> Result<Self, AppError> {
61        let working_dir = current_dir().unwrap_or_else(|_| PathBuf::from("."));
62        let agent = TokioAcpAgent::from_str(agent_command).map_err(AcpClientError::InvalidAgentCommand)?;
63        Self::connect_to(agent, working_dir).await
64    }
65
66    pub async fn connect_to(agent: impl ConnectTo<Client> + 'static, working_dir: PathBuf) -> Result<Self, AppError> {
67        let workspace_status = WorkspaceStatus::initial(&working_dir);
68        let client = connect_acp_client(agent, initialize_request()).await?;
69        let session_response = client.handle.new_session(NewSessionRequest::new(working_dir.clone())).await?;
70
71        Ok(Self {
72            client,
73            response: session_response,
74            workspace_access: WorkspaceAccess::Local,
75            working_dir,
76            workspace_status,
77        })
78    }
79}
80
81fn initialize_request() -> InitializeRequest {
82    InitializeRequest::new(ProtocolVersion::V2, Implementation::new("wisp", env!("CARGO_PKG_VERSION")))
83        .capabilities(client_capabilities())
84}
85
86fn client_capabilities() -> ClientCapabilities {
87    ClientCapabilities::new().elicitation(
88        ElicitationCapabilities::new().form(ElicitationFormCapabilities::new()).url(ElicitationUrlCapabilities::new()),
89    )
90}