1pub(crate) mod config_setting;
2pub(crate) mod mappers;
3pub(crate) mod model_config;
4pub(crate) mod relay;
5pub(crate) mod session;
6pub(crate) mod session_manager;
7pub(crate) mod session_store;
8
9pub use mappers::map_mcp_prompt_to_available_command;
10pub use session_manager::SessionManager;
11
12use acp_utils::server::{AcpActor, AcpActorHandle};
13use agent_client_protocol::{self as acp, AgentSideConnection};
14use std::{fs::create_dir_all, path::PathBuf};
15use tokio::{
16 io::{stdin, stdout},
17 sync::mpsc,
18 task::{LocalSet, spawn_local},
19};
20use tokio_util::compat::{TokioAsyncReadCompatExt, TokioAsyncWriteCompatExt};
21use tracing::info;
22use tracing_appender::rolling::daily;
23use tracing_subscriber::EnvFilter;
24
25#[derive(clap::Args, Debug)]
26pub struct AcpArgs {
27 #[clap(long, default_value = "/tmp/aether-acp-logs")]
29 pub log_dir: PathBuf,
30}
31
32pub async fn run_acp(args: AcpArgs) -> acp::Result<()> {
33 info!("Starting Aether ACP server");
34
35 setup_logging(&args);
36 let stdout = stdout().compat_write();
37 let stdin = stdin().compat();
38
39 LocalSet::new()
44 .run_until(async move {
45 let (actor_request_tx, actor_request_rx) = mpsc::unbounded_channel();
46 let actor_handle = AcpActorHandle::new(actor_request_tx);
47 let agent = SessionManager::new(actor_handle.clone());
48
49 let (conn, handle_io) = AgentSideConnection::new(agent, stdout, stdin, |fut| {
50 spawn_local(fut);
51 });
52
53 let actor = AcpActor::new(conn, actor_request_rx);
54 spawn_local(async move {
55 actor.run().await;
56 });
57
58 handle_io.await
59 })
60 .await
61}
62
63fn setup_logging(args: &AcpArgs) {
64 create_dir_all(&args.log_dir).ok();
65 tracing_subscriber::fmt()
66 .with_writer(daily(&args.log_dir, "aether-acp.log"))
67 .with_ansi(false) .with_env_filter(EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")))
69 .pretty()
70 .init();
71}