astrid_telegram/
client.rs1use std::path::PathBuf;
8use std::time::Duration;
9
10use astrid_core::{ApprovalDecision, ElicitationResponse, SessionId};
11use astrid_gateway::rpc::{AstridRpcClient, BudgetInfo, DaemonEvent, DaemonStatus, SessionInfo};
12use astrid_gateway::server::DaemonPaths;
13use jsonrpsee::ws_client::{WsClient, WsClientBuilder};
14
15use crate::error::TelegramBotError;
16
17pub struct DaemonClient {
22 client: WsClient,
23}
24
25impl DaemonClient {
26 pub async fn connect_url(url: &str) -> Result<Self, TelegramBotError> {
28 let client = WsClientBuilder::default()
29 .connection_timeout(Duration::from_secs(10))
30 .build(url)
31 .await
32 .map_err(|e| {
33 TelegramBotError::DaemonConnection(format!(
34 "failed to connect to daemon at {url}: {e}"
35 ))
36 })?;
37
38 Ok(Self { client })
39 }
40
41 pub async fn connect_discover() -> Result<Self, TelegramBotError> {
44 let paths = DaemonPaths::default_dir()
45 .map_err(|e| TelegramBotError::DaemonConnection(e.to_string()))?;
46
47 let port = astrid_gateway::DaemonServer::read_port(&paths).ok_or_else(|| {
48 TelegramBotError::DaemonConnection(
49 "daemon port file not found — is astridd running?".to_string(),
50 )
51 })?;
52
53 let url = format!("ws://127.0.0.1:{port}");
54 Self::connect_url(&url).await
55 }
56
57 pub async fn connect(daemon_url: Option<&str>) -> Result<Self, TelegramBotError> {
59 match daemon_url {
60 Some(url) => Self::connect_url(url).await,
61 None => Self::connect_discover().await,
62 }
63 }
64
65 pub async fn create_session(
67 &self,
68 workspace_path: Option<PathBuf>,
69 ) -> Result<SessionInfo, TelegramBotError> {
70 self.client
71 .create_session(workspace_path)
72 .await
73 .map_err(|e| TelegramBotError::DaemonRpc(e.to_string()))
74 }
75
76 pub async fn end_session(&self, session_id: &SessionId) -> Result<(), TelegramBotError> {
78 self.client
79 .end_session(session_id.clone())
80 .await
81 .map_err(|e| TelegramBotError::DaemonRpc(e.to_string()))
82 }
83
84 pub async fn send_input(
86 &self,
87 session_id: &SessionId,
88 input: &str,
89 ) -> Result<(), TelegramBotError> {
90 self.client
91 .send_input(session_id.clone(), input.to_string())
92 .await
93 .map_err(|e| TelegramBotError::DaemonRpc(e.to_string()))
94 }
95
96 pub async fn subscribe_events(
98 &self,
99 session_id: &SessionId,
100 ) -> Result<jsonrpsee::core::client::Subscription<DaemonEvent>, TelegramBotError> {
101 self.client
102 .subscribe_events(session_id.clone())
103 .await
104 .map_err(|e| TelegramBotError::DaemonRpc(e.to_string()))
105 }
106
107 pub async fn send_approval(
109 &self,
110 session_id: &SessionId,
111 request_id: &str,
112 decision: ApprovalDecision,
113 ) -> Result<(), TelegramBotError> {
114 self.client
115 .approval_response(session_id.clone(), request_id.to_string(), decision)
116 .await
117 .map_err(|e| TelegramBotError::DaemonRpc(e.to_string()))
118 }
119
120 pub async fn send_elicitation(
122 &self,
123 session_id: &SessionId,
124 request_id: &str,
125 response: ElicitationResponse,
126 ) -> Result<(), TelegramBotError> {
127 self.client
128 .elicitation_response(session_id.clone(), request_id.to_string(), response)
129 .await
130 .map_err(|e| TelegramBotError::DaemonRpc(e.to_string()))
131 }
132
133 pub async fn cancel_turn(&self, session_id: &SessionId) -> Result<(), TelegramBotError> {
135 self.client
136 .cancel_turn(session_id.clone())
137 .await
138 .map_err(|e| TelegramBotError::DaemonRpc(e.to_string()))
139 }
140
141 pub async fn status(&self) -> Result<DaemonStatus, TelegramBotError> {
143 self.client
144 .status()
145 .await
146 .map_err(|e| TelegramBotError::DaemonRpc(e.to_string()))
147 }
148
149 pub async fn session_budget(
151 &self,
152 session_id: &SessionId,
153 ) -> Result<BudgetInfo, TelegramBotError> {
154 self.client
155 .session_budget(session_id.clone())
156 .await
157 .map_err(|e| TelegramBotError::DaemonRpc(e.to_string()))
158 }
159}