use agent_client_protocol_conductor::{ConductorImpl, McpBridgeMode, ProxiesAndAgent};
use agent_client_protocol_test::test_binaries::{arrow_proxy_example, testy};
use agent_client_protocol_test::testy::TestyCommand;
use agent_client_protocol_tokio::AcpAgent;
use tokio::io::duplex;
use tokio_util::compat::{TokioAsyncReadCompatExt, TokioAsyncWriteCompatExt};
#[tokio::test]
async fn test_trace_generation() -> Result<(), agent_client_protocol::Error> {
drop(
tracing_subscriber::fmt()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.with_ansi(false)
.try_init(),
);
let trace_path = std::env::temp_dir().join(format!("trace_test_{}.jsons", std::process::id()));
let arrow_proxy_agent =
AcpAgent::from_args([arrow_proxy_example().to_string_lossy().to_string()])?;
let eliza_agent = testy();
let (editor_write, conductor_read) = duplex(8192);
let (conductor_write, editor_read) = duplex(8192);
let trace_path_clone = trace_path.clone();
let conductor_handle = tokio::spawn(async move {
ConductorImpl::new_agent(
"conductor".to_string(),
ProxiesAndAgent::new(eliza_agent).proxy(arrow_proxy_agent),
McpBridgeMode::default(),
)
.trace_to_path(&trace_path_clone)
.expect("Failed to create trace writer")
.run(agent_client_protocol::ByteStreams::new(
conductor_write.compat_write(),
conductor_read.compat(),
))
.await
});
let result = tokio::time::timeout(std::time::Duration::from_secs(30), async move {
let result = yopo::prompt(
agent_client_protocol::ByteStreams::new(
editor_write.compat_write(),
editor_read.compat(),
),
TestyCommand::Greet.to_prompt(),
)
.await?;
Ok::<String, agent_client_protocol::Error>(result)
})
.await
.expect("Test timed out")
.expect("Editor failed");
conductor_handle.abort();
let trace_content = std::fs::read_to_string(&trace_path).expect("Failed to read trace file");
let events: Vec<serde_json::Value> = trace_content
.lines()
.filter(|line| !line.trim().is_empty())
.map(|line| serde_json::from_str(line).expect("Invalid JSON in trace"))
.collect();
println!("Trace file: {}", trace_path.display());
println!("Generated {} events", events.len());
for (i, event) in events.iter().enumerate() {
let event_type = event.get("type").and_then(|v| v.as_str()).unwrap_or("?");
let from = event.get("from").and_then(|v| v.as_str()).unwrap_or("?");
let to = event.get("to").and_then(|v| v.as_str()).unwrap_or("?");
let method = event.get("method").and_then(|v| v.as_str()).unwrap_or("-");
let protocol = event
.get("protocol")
.and_then(|v| v.as_str())
.unwrap_or("acp");
println!(" [{i}] {event_type} {from} -> {to} {method} ({protocol})");
}
assert!(!events.is_empty(), "Expected trace events, got none");
let has_request = events
.iter()
.any(|e| e.get("type").and_then(|v| v.as_str()) == Some("request"));
let has_response = events
.iter()
.any(|e| e.get("type").and_then(|v| v.as_str()) == Some("response"));
assert!(has_request, "Expected at least one request event");
assert!(has_response, "Expected at least one response event");
for event in &events {
assert!(
event.get("ts").is_some(),
"Event missing 'ts' field: {event:?}"
);
assert!(
event.get("type").is_some(),
"Event missing 'type' field: {event:?}"
);
}
drop(std::fs::remove_file(&trace_path));
println!("Test passed! Response: {result}");
Ok(())
}