use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use tokio::runtime::Handle;
use tokio::sync::{Mutex, oneshot};
use leviath_providers::{
ContentBlock, FinishReason, InferenceRequest, InferenceResponse, MessageContent,
ModelCapabilities, Provider, TokenUsage, ToolCall,
};
use leviath_runtime::host::{ControlOp, SpawnArgs};
use leviath_runtime::{AgentStatus, ProviderRegistry};
struct WritesThenAnswers {
answering_turns: Arc<AtomicUsize>,
}
impl WritesThenAnswers {
fn target_from_prompt(request: &InferenceRequest) -> Option<String> {
let from_system = request.system.iter().map(|b| b.text.clone());
let from_messages = request.messages.iter().filter_map(|m| match &m.content {
MessageContent::Text(t) => Some(t.clone()),
MessageContent::Blocks(_) => None,
});
from_system.chain(from_messages).find_map(|text| {
text.split_whitespace()
.skip_while(|w| !w.eq_ignore_ascii_case("write"))
.nth(1)
.map(str::to_string)
})
}
fn already_ran_the_tool(request: &InferenceRequest) -> bool {
request.messages.iter().any(|m| match &m.content {
MessageContent::Blocks(blocks) => blocks
.iter()
.any(|b| matches!(b, ContentBlock::ToolResult { .. })),
MessageContent::Text(_) => false,
})
}
}
#[async_trait::async_trait]
impl Provider for WritesThenAnswers {
async fn infer(
&self,
request: &InferenceRequest,
) -> leviath_providers::Result<InferenceResponse> {
let tool_calls = match Self::already_ran_the_tool(request) {
true => {
self.answering_turns.fetch_add(1, Ordering::SeqCst);
Vec::new()
}
false => vec![ToolCall {
id: "call-1".to_string(),
name: "write_file".to_string(),
arguments: serde_json::json!({
"path": Self::target_from_prompt(request)
.unwrap_or_else(|| "the-task-never-arrived".to_string()),
"content": "written by the agent\n",
}),
thought_signature: None,
}],
};
Ok(InferenceResponse {
content: match tool_calls.is_empty() {
true => "done".to_string(),
false => String::new(),
},
tool_calls,
tokens_used: TokenUsage {
prompt_tokens: 1,
completion_tokens: 1,
cached_tokens: 0,
cache_write_tokens: 0,
total_tokens: 2,
},
finish_reason: FinishReason::Stop,
})
}
async fn count_tokens(&self, _text: &str, _model: &str) -> usize {
1
}
fn max_context_tokens(&self, _model: &str) -> usize {
100_000
}
fn name(&self) -> &str {
"e2e"
}
fn capabilities(&self, _model: &str) -> ModelCapabilities {
ModelCapabilities::default()
}
}
fn one_stage_manifest() -> &'static str {
r#"[agent]
name = "e2e"
version = "0.0.0"
description = "Writes one file, then finishes."
entry_stage = "work"
[stages.work]
mode = "autonomous"
model = { provider = "e2e", model = "m" }
description = "Write the file"
available_tools = ["write_file"]
system_prompt = "Write the file you were asked for, then stop."
# Without this the task text has nowhere to land, and the run would answer a
# question it was never given. The provider below reads its target back out of
# the prompt, so a missing region fails the test rather than passing it.
[context.regions]
task = { kind = "pinned", max_tokens = 500, seed = "task" }
conversation = { kind = "sliding_window", max_items = 20, max_tokens = 10000 }
"#
}
#[tokio::test]
async fn an_agent_runs_a_tool_and_the_file_lands_on_disk() {
let agent_dir = tempfile::tempdir().expect("agent dir");
let manifest = agent_dir.path().join("agent.leviath");
std::fs::write(&manifest, one_stage_manifest()).expect("write manifest");
let workdir = tempfile::tempdir().expect("workdir");
let runs = tempfile::tempdir().expect("runs dir");
let answering_turns = Arc::new(AtomicUsize::new(0));
let mut providers = ProviderRegistry::new();
providers.register(
"e2e".to_string(),
Arc::new(WritesThenAnswers {
answering_turns: answering_turns.clone(),
}),
);
let mcp = Arc::new(Mutex::new(leviath_mcp::ToolExecutor::new()));
let mut host = leviath_cli::daemon::setup::build_host(leviath_cli::daemon::setup::HostParts {
config: leviath_cli::config::Config::default(),
providers,
runs_dir: runs.path().to_path_buf(),
shared_mcp: mcp,
mcp_tool_defs: vec![],
mcp_pool: leviath_cli::daemon::mcp_pool::McpPool::for_daemon(
Arc::new(Mutex::new(leviath_mcp::ToolExecutor::new())),
&[],
),
runtime: Handle::current(),
now_secs: || 1_700_000_000,
});
let (reply, spawned) = oneshot::channel();
host.handle(ControlOp::Spawn {
args: Box::new(SpawnArgs {
run_id: "e2e-1".to_string(),
blueprint_path: manifest.to_string_lossy().to_string(),
task: "write output.txt".to_string(),
workdir: workdir.path().to_string_lossy().to_string(),
allow: vec!["write_file".to_string()],
..Default::default()
}),
reply,
});
assert_eq!(
spawned.await.expect("spawn replied"),
Ok("e2e-1".to_string())
);
host.world_mut().run_until_idle(64).await;
let written = workdir.path().join("output.txt");
assert!(
written.exists(),
"the agent's write_file never reached the filesystem"
);
assert_eq!(
std::fs::read_to_string(&written).expect("read the written file"),
"written by the agent\n"
);
assert!(
answering_turns.load(Ordering::SeqCst) >= 1,
"the tool result never came back to the model"
);
let (reply, status) = oneshot::channel();
host.handle(ControlOp::Status {
run_id: "e2e-1".to_string(),
reply,
});
let status = status.await.expect("status replied");
assert!(
matches!(status, Some(AgentStatus::Complete)),
"run did not finish cleanly: {status:?}"
);
}