#[path = "agent_helpers.rs"]
mod agent_helpers;
#[path = "agent_pool_cmds.rs"]
mod agent_pool_cmds;
#[path = "agent_runtime_cmds.rs"]
mod agent_runtime_cmds;
use std::path::PathBuf;
use crate::ansi_colors::Colorize;
use agent_helpers::{
load_manifest, print_manifest_summary, try_auto_pull, validate_model_file, validate_model_g2,
};
pub fn build_driver_pub(
manifest: &batuta::agent::AgentManifest,
) -> anyhow::Result<Box<dyn batuta::agent::driver::LlmDriver>> {
agent_helpers::build_driver(manifest)
}
#[cfg(test)]
use agent_helpers::{
build_driver, build_guard, build_memory, build_tool_registry, detect_model_format,
register_inference_tool, register_spawn_tool,
};
#[cfg(test)]
use agent_pool_cmds::{cmd_agent_pool, cmd_agent_status};
#[cfg(test)]
use agent_runtime_cmds::{cmd_agent_chat, cmd_agent_run};
#[derive(Debug, Clone, clap::Subcommand)]
pub enum AgentCommand {
Run {
#[arg(long)]
manifest: PathBuf,
#[arg(long)]
prompt: String,
#[arg(long)]
max_iterations: Option<u32>,
#[arg(long)]
daemon: bool,
#[arg(long)]
auto_pull: bool,
#[arg(long)]
stream: bool,
},
Chat {
#[arg(long)]
manifest: PathBuf,
#[arg(long)]
auto_pull: bool,
#[arg(long)]
stream: bool,
},
Validate {
#[arg(long)]
manifest: PathBuf,
#[arg(long)]
check_model: bool,
#[arg(long)]
check_inference: bool,
},
Sign {
#[arg(long)]
manifest: PathBuf,
#[arg(long)]
signer: Option<String>,
#[arg(long)]
output: Option<PathBuf>,
},
VerifySig {
#[arg(long)]
manifest: PathBuf,
#[arg(long)]
signature: Option<PathBuf>,
#[arg(long)]
pubkey: PathBuf,
},
Contracts,
Status {
#[arg(long)]
manifest: PathBuf,
},
Pool {
#[arg(long, required = true, num_args = 1..)]
manifest: Vec<PathBuf>,
#[arg(long)]
prompt: String,
#[arg(long)]
concurrency: Option<usize>,
},
}
pub fn cmd_agent(command: AgentCommand) -> anyhow::Result<()> {
match command {
AgentCommand::Run { manifest, prompt, max_iterations, daemon, auto_pull, stream } => {
agent_runtime_cmds::cmd_agent_run(
&manifest,
&prompt,
max_iterations,
daemon,
auto_pull,
stream,
)
}
AgentCommand::Chat { manifest, auto_pull, stream } => {
agent_runtime_cmds::cmd_agent_chat(&manifest, auto_pull, stream)
}
AgentCommand::Validate { manifest, check_model, check_inference } => {
cmd_agent_validate(&manifest, check_model, check_inference)
}
AgentCommand::Sign { manifest, signer, output } => {
cmd_agent_sign(&manifest, signer.as_deref(), output)
}
AgentCommand::VerifySig { manifest, signature, pubkey } => {
cmd_agent_verify_sig(&manifest, signature, &pubkey)
}
AgentCommand::Contracts => cmd_agent_contracts(),
AgentCommand::Status { manifest } => agent_pool_cmds::cmd_agent_status(&manifest),
AgentCommand::Pool { manifest, prompt, concurrency } => {
agent_pool_cmds::cmd_agent_pool(&manifest, &prompt, concurrency)
}
}
}
fn cmd_agent_validate(
manifest_path: &PathBuf,
check_model: bool,
check_inference: bool,
) -> anyhow::Result<()> {
let manifest = load_manifest(manifest_path)?;
match manifest.validate() {
Ok(()) => {
println!("{} Manifest is valid: {}", "✓".green(), manifest_path.display());
print_manifest_summary(&manifest);
}
Err(errors) => {
println!("{} Manifest validation failed:", "✗".bright_red());
for err in &errors {
println!(" {} {}", "•".bright_red(), err);
}
anyhow::bail!("{} validation error(s) in {}", errors.len(), manifest_path.display());
}
}
if let Some(repo) = manifest.model.needs_pull() {
println!();
println!("{} Model needs download: {}", "⚠".bright_yellow(), repo,);
if let Some(path) = manifest.model.resolve_model_path() {
println!(" Expected at: {}", path.display());
}
println!(
" Run: {} {} {}",
"apr pull".cyan(),
repo,
manifest.model.model_quantization.as_deref().unwrap_or("q4_k_m"),
);
}
if check_model {
validate_model_file(&manifest)?;
}
if check_inference {
validate_model_g2(&manifest)?;
}
Ok(())
}
fn cmd_agent_sign(
manifest_path: &PathBuf,
signer: Option<&str>,
output: Option<PathBuf>,
) -> anyhow::Result<()> {
let content = std::fs::read_to_string(manifest_path)
.map_err(|e| anyhow::anyhow!("Cannot read manifest {}: {e}", manifest_path.display()))?;
let signing_key = pacha::signing::SigningKey::generate();
let verifying_key = signing_key.verifying_key();
let sig = batuta::agent::signing::sign_manifest(&content, &signing_key, signer);
let sig_path = output.unwrap_or_else(|| {
let mut p = manifest_path.clone();
let ext = p
.extension()
.map(|e| format!("{}.sig", e.to_string_lossy()))
.unwrap_or_else(|| "sig".into());
p.set_extension(ext);
p
});
let sig_toml = batuta::agent::signing::signature_to_toml(&sig);
std::fs::write(&sig_path, &sig_toml)
.map_err(|e| anyhow::anyhow!("Cannot write signature to {}: {e}", sig_path.display()))?;
let pk_path = sig_path.with_extension("pub");
std::fs::write(&pk_path, verifying_key.to_hex())
.map_err(|e| anyhow::anyhow!("Cannot write public key to {}: {e}", pk_path.display()))?;
println!("{} Manifest signed: {}", "✓".green(), manifest_path.display());
println!(" Signature: {}", sig_path.display());
println!(" Public key: {}", pk_path.display());
println!(" Hash: {}", &sig.content_hash[..16]);
if let Some(ref s) = sig.signer {
println!(" Signer: {s}");
}
Ok(())
}
fn cmd_agent_verify_sig(
manifest_path: &PathBuf,
signature_path: Option<PathBuf>,
pubkey_path: &PathBuf,
) -> anyhow::Result<()> {
let content = std::fs::read_to_string(manifest_path)
.map_err(|e| anyhow::anyhow!("Cannot read manifest {}: {e}", manifest_path.display()))?;
let sig_path = signature_path.unwrap_or_else(|| {
let mut p = manifest_path.clone();
let ext = p
.extension()
.map(|e| format!("{}.sig", e.to_string_lossy()))
.unwrap_or_else(|| "sig".into());
p.set_extension(ext);
p
});
let sig_content = std::fs::read_to_string(&sig_path)
.map_err(|e| anyhow::anyhow!("Cannot read signature {}: {e}", sig_path.display()))?;
let sig = batuta::agent::signing::signature_from_toml(&sig_content)
.map_err(|e| anyhow::anyhow!("Invalid signature: {e}"))?;
let pk_hex = std::fs::read_to_string(pubkey_path)
.map_err(|e| anyhow::anyhow!("Cannot read public key {}: {e}", pubkey_path.display()))?;
let vk = pacha::signing::VerifyingKey::from_hex(pk_hex.trim())
.map_err(|e| anyhow::anyhow!("Invalid public key: {e}"))?;
match batuta::agent::signing::verify_manifest(&content, &sig, &vk) {
Ok(()) => {
println!("{} Signature valid: {}", "✓".green(), manifest_path.display());
if let Some(ref signer) = sig.signer {
println!(" Signer: {signer}");
}
println!(" Hash: {}", &sig.content_hash[..16]);
Ok(())
}
Err(e) => {
println!("{} Signature verification FAILED: {e}", "✗".bright_red());
anyhow::bail!("manifest signature verification failed: {e}");
}
}
}
fn cmd_agent_contracts() -> anyhow::Result<()> {
let yaml = include_str!("../../contracts/agent-loop-v1.yaml");
let contract = batuta::agent::contracts::parse_contract(yaml)
.map_err(|e| anyhow::anyhow!("contract parse: {e}"))?;
println!(
"{} Contract: {} v{}",
"📋".bright_blue(),
contract.contract.name.cyan(),
contract.contract.version,
);
println!(" {}", contract.contract.description.dimmed());
println!();
println!("{} Invariants ({})", "•".bright_blue(), contract.invariants.len(),);
for inv in &contract.invariants {
println!(" {} {} — {}", inv.id.bright_yellow(), inv.name, inv.description.dimmed(),);
println!(" Test: {}", inv.test_binding.cyan());
}
println!();
println!("{} Verification targets:", "•".bright_blue(),);
println!(
" Coverage: {}% Mutation: {}%",
contract.verification.coverage_target, contract.verification.mutation_target,
);
println!(
" Complexity: cyclomatic {} / cognitive {}",
contract.verification.complexity_max_cyclomatic,
contract.verification.complexity_max_cognitive,
);
println!();
println!(
"{} Run {} to check bindings.",
"ℹ".bright_blue(),
"cargo test --features agents -- test_all_contract_bindings_exist".cyan(),
);
Ok(())
}
#[cfg(test)]
#[path = "agent_tests.rs"]
mod tests;
#[cfg(test)]
#[path = "agent_tests_extended.rs"]
mod tests_extended;