use std::sync::Arc;
use std::time::Duration;
use thal::actors::terminal_prompt::ScriptedPromptIo;
use thal::actors::ActorRegistry;
use thal::value::Value;
use thal::Reactor;
const PROGRAM: &str = r#"
reaction OnUserInput {
when: TerminalPrompt(p)
where: p.status == "Done"
emit: LlmCall { provider: "mock", prompt: p.answer }
}
reaction OnLlmResponse {
when: LlmCall(c)
where: c.status == "Done"
emit: TerminalWrite { content: c.text }
}
reaction OnPrintDone {
when: TerminalWrite(w)
where: w.status == "Done"
emit: TerminalPrompt { question: "you: " }
}
reaction Init {
when: Boot(b)
emit: TerminalPrompt { question: "you: " }
}
"#;
#[tokio::test(flavor = "multi_thread")]
async fn chat_demo_completes_a_round_trip() {
let program = thal::load_str(PROGRAM).expect("load");
let scripted = Arc::new(ScriptedPromptIo::new(vec![
"hello, thal".into(),
"second turn".into(),
]));
let registry = ActorRegistry::with_prompt_io(scripted);
let reactor = Reactor::with_actors(program, registry);
let store = reactor.store();
let task = tokio::spawn(reactor.run());
tokio::time::sleep(Duration::from_millis(500)).await;
task.abort();
let llm_calls = store.scan_by_name("LlmCall");
let any_done_echo = llm_calls.iter().any(|c| {
let status_done = matches!(c.fields.get("status"), Some(Value::String(s)) if s == "Done");
let text_echo =
matches!(c.fields.get("text"), Some(Value::String(s)) if s.starts_with("echo: "));
status_done && text_echo
});
assert!(
any_done_echo,
"expected at least one LlmCall to reach Done with `echo: ` text; got {llm_calls:#?}"
);
let writes = store.scan_by_name("TerminalWrite");
let any_echo_write = writes.iter().any(|w| {
matches!(w.fields.get("content"), Some(Value::String(s)) if s.starts_with("echo: "))
});
assert!(
any_echo_write,
"expected at least one TerminalWrite to carry the LLM response"
);
}