use std::path::PathBuf;
use std::time::Duration;
use astrid_core::{ApprovalDecision, ElicitationResponse, SessionId};
use astrid_gateway::rpc::{AstridRpcClient, BudgetInfo, DaemonEvent, DaemonStatus, SessionInfo};
use astrid_gateway::server::DaemonPaths;
use jsonrpsee::ws_client::{WsClient, WsClientBuilder};
use crate::error::TelegramBotError;
pub struct DaemonClient {
client: WsClient,
}
impl DaemonClient {
pub async fn connect_url(url: &str) -> Result<Self, TelegramBotError> {
let client = WsClientBuilder::default()
.connection_timeout(Duration::from_secs(10))
.build(url)
.await
.map_err(|e| {
TelegramBotError::DaemonConnection(format!(
"failed to connect to daemon at {url}: {e}"
))
})?;
Ok(Self { client })
}
pub async fn connect_discover() -> Result<Self, TelegramBotError> {
let paths = DaemonPaths::default_dir()
.map_err(|e| TelegramBotError::DaemonConnection(e.to_string()))?;
let port = astrid_gateway::DaemonServer::read_port(&paths).ok_or_else(|| {
TelegramBotError::DaemonConnection(
"daemon port file not found — is astridd running?".to_string(),
)
})?;
let url = format!("ws://127.0.0.1:{port}");
Self::connect_url(&url).await
}
pub async fn connect(daemon_url: Option<&str>) -> Result<Self, TelegramBotError> {
match daemon_url {
Some(url) => Self::connect_url(url).await,
None => Self::connect_discover().await,
}
}
pub async fn create_session(
&self,
workspace_path: Option<PathBuf>,
) -> Result<SessionInfo, TelegramBotError> {
self.client
.create_session(workspace_path)
.await
.map_err(|e| TelegramBotError::DaemonRpc(e.to_string()))
}
pub async fn end_session(&self, session_id: &SessionId) -> Result<(), TelegramBotError> {
self.client
.end_session(session_id.clone())
.await
.map_err(|e| TelegramBotError::DaemonRpc(e.to_string()))
}
pub async fn send_input(
&self,
session_id: &SessionId,
input: &str,
) -> Result<(), TelegramBotError> {
self.client
.send_input(session_id.clone(), input.to_string())
.await
.map_err(|e| TelegramBotError::DaemonRpc(e.to_string()))
}
pub async fn subscribe_events(
&self,
session_id: &SessionId,
) -> Result<jsonrpsee::core::client::Subscription<DaemonEvent>, TelegramBotError> {
self.client
.subscribe_events(session_id.clone())
.await
.map_err(|e| TelegramBotError::DaemonRpc(e.to_string()))
}
pub async fn send_approval(
&self,
session_id: &SessionId,
request_id: &str,
decision: ApprovalDecision,
) -> Result<(), TelegramBotError> {
self.client
.approval_response(session_id.clone(), request_id.to_string(), decision)
.await
.map_err(|e| TelegramBotError::DaemonRpc(e.to_string()))
}
pub async fn send_elicitation(
&self,
session_id: &SessionId,
request_id: &str,
response: ElicitationResponse,
) -> Result<(), TelegramBotError> {
self.client
.elicitation_response(session_id.clone(), request_id.to_string(), response)
.await
.map_err(|e| TelegramBotError::DaemonRpc(e.to_string()))
}
pub async fn cancel_turn(&self, session_id: &SessionId) -> Result<(), TelegramBotError> {
self.client
.cancel_turn(session_id.clone())
.await
.map_err(|e| TelegramBotError::DaemonRpc(e.to_string()))
}
pub async fn status(&self) -> Result<DaemonStatus, TelegramBotError> {
self.client
.status()
.await
.map_err(|e| TelegramBotError::DaemonRpc(e.to_string()))
}
pub async fn session_budget(
&self,
session_id: &SessionId,
) -> Result<BudgetInfo, TelegramBotError> {
self.client
.session_budget(session_id.clone())
.await
.map_err(|e| TelegramBotError::DaemonRpc(e.to_string()))
}
}