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