use std::io::Write;
pub async fn run_attach(port: u16, session_filter: &str) -> anyhow::Result<()> {
let target_port = if port != 4096 {
port
} else if !session_filter.is_empty() {
match find_by_session_id(session_filter).await {
Some(p) => p,
None => {
eprintln!("No running instance matches session '{session_filter}'.");
eprintln!("Run /status in a serve session to see its session ID.");
return Ok(());
}
}
} else {
let bridges = agent_code_lib::services::bridge::discover_bridges();
if bridges.is_empty() {
eprintln!("No running agent-code instances found.");
eprintln!("Start one with: agent --serve");
return Ok(());
}
if bridges.len() == 1 {
let b = &bridges[0];
eprintln!(
"Attaching to instance on port {} (pid {}, {})",
b.port, b.pid, b.cwd
);
b.port
} else {
eprintln!("Multiple instances found:\n");
for (i, b) in bridges.iter().enumerate() {
let session = fetch_session_id(b.port).await.unwrap_or_default();
let session_display = if session.is_empty() {
String::new()
} else {
format!(" — session {session}")
};
eprintln!(
" [{}] port {} — pid {} — {}{}",
i + 1,
b.port,
b.pid,
b.cwd,
session_display
);
}
eprintln!();
eprint!("Select [1-{}]: ", bridges.len());
std::io::stderr().flush()?;
let mut choice = String::new();
std::io::stdin().read_line(&mut choice)?;
let idx: usize = choice.trim().parse().unwrap_or(0);
if idx < 1 || idx > bridges.len() {
eprintln!("Invalid selection.");
return Ok(());
}
let b = &bridges[idx - 1];
eprintln!("Attaching to instance on port {} (pid {})", b.port, b.pid);
b.port
}
};
let base_url = format!("http://127.0.0.1:{target_port}");
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(5))
.build()?;
match client.get(format!("{base_url}/health")).send().await {
Ok(resp) if resp.status().is_success() => {}
Ok(resp) => {
eprintln!("Instance returned HTTP {}", resp.status());
return Ok(());
}
Err(e) => {
eprintln!("Cannot connect to {base_url}: {e}");
eprintln!("Is the server running? Start with: agent --serve --port {target_port}");
return Ok(());
}
}
if let Ok(resp) = client.get(format!("{base_url}/status")).send().await
&& let Ok(status) = resp.json::<serde_json::Value>().await
{
let model = status["model"].as_str().unwrap_or("unknown");
let session = status["session_id"].as_str().unwrap_or("?");
let turns = status["turn_count"].as_u64().unwrap_or(0);
let cost = status["cost_usd"].as_f64().unwrap_or(0.0);
eprintln!(
"Connected. Session: {session}, model: {model}, turns: {turns}, cost: ${cost:.4}\n"
);
}
eprintln!("Type a message and press Enter. Ctrl+D to detach.\n");
let stdin = std::io::stdin();
let mut input = String::new();
loop {
print!("> ");
std::io::stdout().flush()?;
input.clear();
if stdin.read_line(&mut input)? == 0 {
eprintln!("\nDetached.");
break;
}
let prompt = input.trim();
if prompt.is_empty() {
continue;
}
let body = serde_json::json!({"content": prompt});
match client
.post(format!("{base_url}/message"))
.json(&body)
.timeout(std::time::Duration::from_secs(300))
.send()
.await
{
Ok(resp) => {
if let Ok(result) = resp.json::<serde_json::Value>().await {
let response = result["response"].as_str().unwrap_or("");
let tools: Vec<&str> = result["tools_used"]
.as_array()
.map(|a| a.iter().filter_map(|v| v.as_str()).collect())
.unwrap_or_default();
let cost = result["cost_usd"].as_f64().unwrap_or(0.0);
println!("{response}");
if !tools.is_empty() {
eprintln!("[tools: {} | cost: ${cost:.4}]", tools.join(", "));
}
println!();
} else {
eprintln!("[Error: could not parse response]");
}
}
Err(e) => {
eprintln!("[Error: {e}]");
}
}
}
Ok(())
}
async fn find_by_session_id(prefix: &str) -> Option<u16> {
let bridges = agent_code_lib::services::bridge::discover_bridges();
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(3))
.build()
.ok()?;
for b in &bridges {
if let Some(session_id) = fetch_session_id_with_client(&client, b.port).await
&& session_id.starts_with(prefix)
{
eprintln!(
"Found session {session_id} on port {} (pid {}, {})",
b.port, b.pid, b.cwd
);
return Some(b.port);
}
}
None
}
async fn fetch_session_id(port: u16) -> Option<String> {
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(2))
.build()
.ok()?;
fetch_session_id_with_client(&client, port).await
}
async fn fetch_session_id_with_client(client: &reqwest::Client, port: u16) -> Option<String> {
let url = format!("http://127.0.0.1:{port}/status");
let resp = client.get(&url).send().await.ok()?;
let status: serde_json::Value = resp.json().await.ok()?;
status["session_id"].as_str().map(|s| s.to_string())
}