mod support;
use openquiver_acp::StopReason;
use openquiver_acp::mock::{MockAgents, Script};
use openquiver_runtime::Runtime;
use openquiver_store::{RunStatus, Store};
use support::{HELLO, kinds, plan, printed};
#[tokio::test]
async fn a_run_walks_the_graph_and_prints_what_the_agent_said() {
let plan = plan(HELLO);
let store = Store::open_in_memory().await.expect("store");
let agents = MockAgents::new(Script::new().says("Hi."));
let mut run = Runtime::start(&store, &plan, &agents, "hello")
.await
.expect("start");
run.run().await.expect("run");
assert_eq!(agents.transcript().prompts(), ["Say hello."]);
assert_eq!(printed(&store, run.id()).await, ["Hi."]);
assert_eq!(
store.status(run.id()).await.expect("status"),
RunStatus::Finished
);
}
#[tokio::test]
async fn the_journal_brackets_every_node_that_ran() {
let plan = plan(HELLO);
let store = Store::open_in_memory().await.expect("store");
let agents = MockAgents::new(Script::new().says("Hi."));
let mut run = Runtime::start(&store, &plan, &agents, "hello")
.await
.expect("start");
run.run().await.expect("run");
assert_eq!(
kinds(&store, run.id()).await,
[
"run.started",
"node.entered",
"node.exited", "node.entered",
"node.exited", "node.entered",
"node.exited", "node.entered",
"node.exited", "node.entered",
"print",
"node.exited", "run.finished",
]
);
}
const BRANCHING: &str = r#"
schema 1
graph "branching" {
node start = event.manual
node agent = acp.connect_agent { agent = "mock" }
node sess = acp.new_session
node ask = acp.prompt { text = "Say hello." }
node answered = io.print
node declined = io.print { value = "declined" }
start.out -> agent.in
agent.out -> sess.in
sess.out -> ask.in
ask.end_turn -> answered.in
ask.refusal -> declined.in
agent.agent => sess.agent
ask.text => answered.value
sess.session => ask.session
}
"#;
const BRANCH: &str = r#"
schema 1
graph "branch" {
node start = event.manual
node gate = flow.branch { condition = true }
node yes = io.print { value = "taken" }
node no = io.print { value = "not taken" }
start.out -> gate.in
gate.true -> yes.in
gate.false -> no.in
}
"#;
#[tokio::test]
async fn a_branch_sends_execution_down_one_path_only() {
let plan = plan(BRANCH);
let store = Store::open_in_memory().await.expect("store");
let agents = MockAgents::new(Script::new());
let mut run = Runtime::start(&store, &plan, &agents, "branch")
.await
.expect("start");
run.run().await.expect("run");
assert_eq!(printed(&store, run.id()).await, ["taken"]);
}
const TWO_AGENTS: &str = r#"
schema 1
graph "two-agents" {
node start = event.manual
node alpha = acp.connect_agent { agent = "alpha" }
node first = acp.new_session
node beta = acp.connect_agent { agent = "beta" }
node second = acp.new_session
node ask = acp.prompt { text = "whose conversation is this?" }
node out = io.print
start.out -> alpha.in
alpha.out -> first.in
first.out -> beta.in
beta.out -> second.in
second.out -> ask.in
ask.end_turn -> out.in
alpha.agent => first.agent
ask.text => out.value
beta.agent => second.agent
first.session => ask.session
}
"#;
#[tokio::test]
async fn a_prompt_goes_to_the_session_it_is_wired_to() {
let plan = plan(TWO_AGENTS);
let store = Store::open_in_memory().await.expect("store");
let agents = MockAgents::new(Script::new().says("mine"));
let mut run = Runtime::start(&store, &plan, &agents, "two-agents")
.await
.expect("start");
run.run().await.expect("run");
let asked = agents.transcript().asked();
assert_eq!(asked.len(), 1);
assert_eq!(asked[0].agent, "alpha");
}
#[tokio::test]
async fn the_stop_reason_chooses_which_way_execution_leaves_a_prompt() {
let plan = plan(BRANCHING);
let store = Store::open_in_memory().await.expect("store");
let agents = MockAgents::new(Script::new().stops(StopReason::Refusal, "I would rather not."));
let mut run = Runtime::start(&store, &plan, &agents, "branching")
.await
.expect("start");
run.run().await.expect("run");
assert_eq!(printed(&store, run.id()).await, ["declined"]);
}