use std::path::PathBuf;
use bitrouter_config::{AgentConfig, Distribution};
use tokio::sync::mpsc;
use super::connection::spawn_agent_thread;
use super::types::{AgentCommand, AgentEvent};
pub(crate) struct LaunchCommand {
pub binary: PathBuf,
pub args: Vec<String>,
}
pub struct AcpAgentProvider {
agent_id: String,
command_tx: mpsc::Sender<AgentCommand>,
thread_handle: Option<std::thread::JoinHandle<()>>,
}
impl AcpAgentProvider {
pub fn spawn(
agent_id: String,
config: &AgentConfig,
event_tx: mpsc::Sender<AgentEvent>,
) -> Self {
let launch = resolve_launch(config);
let (handle, command_tx) =
spawn_agent_thread(agent_id.clone(), launch.binary, launch.args, event_tx);
Self {
agent_id,
command_tx,
thread_handle: Some(handle),
}
}
pub async fn prompt(&self, text: String) -> Result<(), mpsc::error::SendError<AgentCommand>> {
self.command_tx.send(AgentCommand::Prompt(text)).await
}
pub fn try_prompt(&self, text: String) {
let _ = self.command_tx.try_send(AgentCommand::Prompt(text));
}
pub fn agent_id(&self) -> &str {
&self.agent_id
}
}
impl Drop for AcpAgentProvider {
fn drop(&mut self) {
drop(self.thread_handle.take());
}
}
fn resolve_launch(config: &AgentConfig) -> LaunchCommand {
if let Some(path) = find_on_path(&config.binary) {
return LaunchCommand {
binary: path,
args: config.args.clone(),
};
}
for dist in &config.distribution {
match dist {
Distribution::Npx { package, args } => {
if find_on_path("npx").is_some() {
let mut full_args = vec![package.clone()];
full_args.extend(args.iter().cloned());
return LaunchCommand {
binary: PathBuf::from("npx"),
args: full_args,
};
}
}
Distribution::Uvx { package, args } => {
if find_on_path("uvx").is_some() {
let mut full_args = vec![package.clone()];
full_args.extend(args.iter().cloned());
return LaunchCommand {
binary: PathBuf::from("uvx"),
args: full_args,
};
}
}
Distribution::Binary { .. } => {
continue;
}
}
}
LaunchCommand {
binary: PathBuf::from(&config.binary),
args: config.args.clone(),
}
}
fn find_on_path(name: &str) -> Option<PathBuf> {
let path = PathBuf::from(name);
if path.components().count() > 1 {
return Some(path);
}
let path_var = std::env::var_os("PATH")?;
for dir in std::env::split_paths(&path_var) {
let candidate = dir.join(name);
if candidate.is_file() {
return Some(candidate);
}
}
None
}
const _: () = {
const fn _assert<T: Send + Sync>() {}
_assert::<AcpAgentProvider>();
};