#![allow(clippy::expect_used, clippy::unwrap_used)]
use std::sync::Arc;
use async_trait::async_trait;
use capo_agent::{AppBuilder, Command, Config, UiEvent, UserMessage};
use capo_tui::state::AppState;
use futures::StreamExt;
use motosan_agent_loop::{ChatOutput, LlmClient, LlmResponse, Message};
use motosan_agent_tool::ToolDef;
use tempfile::tempdir;
use tokio::sync::mpsc;
struct EchoLlm;
#[async_trait]
impl LlmClient for EchoLlm {
async fn chat(
&self,
_messages: &[Message],
_tools: &[ToolDef],
) -> motosan_agent_loop::Result<ChatOutput> {
Ok(ChatOutput::new(LlmResponse::Message("ok".into())))
}
}
#[tokio::test]
async fn user_message_routes_through_tui_to_agent_and_back() {
let dir = tempdir().unwrap();
let mut cfg = Config::default();
cfg.anthropic.api_key = Some("sk-unused".into());
let (ui_tx, ui_rx) = mpsc::channel::<UiEvent>(32);
let app = AppBuilder::new()
.with_config(cfg)
.with_cwd(dir.path())
.with_builtin_tools()
.with_llm(Arc::new(EchoLlm))
.with_ui_channel(ui_tx.clone())
.build()
.await
.expect("build");
let app = Arc::new(app);
let (cmd_tx, mut cmd_rx) = mpsc::channel::<Command>(16);
cmd_tx
.send(Command::SendUserMessage {
text: "hi".into(),
attachments: Vec::new(),
})
.await
.unwrap();
drop(cmd_tx);
{
let app = Arc::clone(&app);
let ui_tx = ui_tx.clone();
tokio::spawn(async move {
while let Some(cmd) = cmd_rx.recv().await {
if let Command::SendUserMessage {
text,
attachments: _,
} = cmd
{
let mut stream = Box::pin(app.send_user_message(UserMessage::text(text)));
while let Some(ev) = stream.next().await {
if ui_tx.send(ev).await.is_err() {
break;
}
}
break;
}
}
});
}
drop(app);
drop(ui_tx);
let ui_rx_stream = Box::pin(futures::stream::unfold(ui_rx, |mut rx| async move {
rx.recv().await.map(|event| (event, rx))
}));
let (cmd_out_tx, _cmd_out_rx) = mpsc::channel::<Command>(16);
let state = tokio::time::timeout(
std::time::Duration::from_secs(3),
capo_tui::runtime::drive_headless(AppState::default(), ui_rx_stream, cmd_out_tx),
)
.await
.expect("drive_headless timeout")
.expect("drive_headless error");
assert!(
state
.messages
.iter()
.any(|m| matches!(m, capo_tui::state::MessageBlock::Assistant(t) if t == "ok")),
"assistant message missing: {:?}",
state.messages
);
}