Skip to main content

aether_cli/acp/
mod.rs

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    /// Path to log file directory (default: /tmp/aether-acp-logs)
28    #[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    // Use multi-threaded runtime with LocalSet:
40    // - LocalSet pins !Send ACP futures to this thread via spawn_local
41    // - The multi-threaded pool allows agents to use tokio::spawn for Send futures
42    // This enables sub-agents to spawn correctly while supporting ACP's !Send requirements
43    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) // No ANSI colors in log files
68        .with_env_filter(EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")))
69        .pretty()
70        .init();
71}