use std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
};
use agent_client_protocol::{
schema::v1::{DeleteSessionRequest, DeleteSessionResponse},
Error as AcpError,
};
use futures_util::{
future::{BoxFuture, Shared},
FutureExt,
};
use super::agent::RhoAcpAgent;
use crate::{
app::{bootstrap, config_repository::ConfigRepository},
cli::Cli,
};
pub(super) struct LazyAgent {
pending: Shared<BoxFuture<'static, Result<Arc<RhoAcpAgent>, Arc<str>>>>,
publication: tokio::sync::Mutex<()>,
closed: AtomicBool,
}
impl LazyAgent {
pub(super) fn new(cli: Cli) -> anyhow::Result<Self> {
ConfigRepository::new(cli.config.clone())
.load()?
.providers
.activate()?;
let pending = async move {
bootstrap::prepare_acp(cli)
.await
.map(|startup| Arc::new(RhoAcpAgent::new(startup)))
.map_err(|error| Arc::<str>::from(format!("{error:#}")))
}
.boxed()
.shared();
Ok(Self {
pending,
publication: tokio::sync::Mutex::new(()),
closed: AtomicBool::new(false),
})
}
pub(super) async fn ready(&self) -> Result<Arc<RhoAcpAgent>, AcpError> {
if self.closed.load(Ordering::Acquire) {
return Err(AcpError::invalid_request().data("ACP connection is closed"));
}
let agent = self
.pending
.clone()
.await
.map_err(|error| AcpError::internal_error().data(error.to_string()))?;
let _publication = self.publication.lock().await;
if self.closed.load(Ordering::Acquire) {
agent.shutdown_all().await;
return Err(AcpError::invalid_request().data("ACP connection is closed"));
}
Ok(agent)
}
pub(super) async fn delete_session(
&self,
request: DeleteSessionRequest,
) -> Result<DeleteSessionResponse, AcpError> {
let publication = self.publication.lock().await;
if self.closed.load(Ordering::Acquire) {
return Err(AcpError::invalid_request().data("ACP connection is closed"));
}
if let Some(agent) = self.started() {
let agent = Arc::clone(agent);
drop(publication);
agent.delete_session(request).await
} else {
let target = super::session_list::delete_target(request.session_id).await?;
super::session_list::delete(target).await
}
}
pub(super) fn started(&self) -> Option<&Arc<RhoAcpAgent>> {
self.pending.peek().and_then(|result| result.as_ref().ok())
}
pub(super) async fn shutdown(&self) {
self.closed.store(true, Ordering::Release);
if let Some(agent) = self.started() {
agent.shutdown_all().await;
}
}
}
#[cfg(test)]
#[path = "startup_tests.rs"]
mod tests;