pub mod manifest;
pub mod session;
pub mod task;
use std::collections::HashMap;
use clap::Args;
pub use session::{
ProviderCreds, build_provider_registry, build_provider_registry_from_config,
provider_creds_from_config,
};
#[derive(Args, Debug, Clone, Default)]
pub struct RunArgs {
#[arg(value_name = "PATH")]
pub path: Option<String>,
#[arg(short, long, value_name = "TEXT|FILE")]
pub task: Option<String>,
#[arg(short, long)]
pub model: Option<String>,
#[arg(long)]
pub yolo: bool,
#[arg(long)]
pub allow: Vec<String>,
#[arg(long)]
pub max_depth: Option<usize>,
#[arg(long)]
pub no_seed_commands: bool,
#[arg(long, value_name = "DIR")]
pub workdir: Option<std::path::PathBuf>,
#[arg(long)]
pub json: bool,
#[arg(long, value_name = "N", default_value_t = 1)]
pub count: usize,
#[arg(long, value_name = "LABEL")]
pub output_format: Option<String>,
#[arg(long, value_name = "TEXT")]
pub output_instructions: Option<String>,
#[arg(long, value_name = "JSON|@FILE")]
pub output_schema: Option<String>,
#[arg(skip)]
pub regions: HashMap<String, String>,
}
const KNOWN_RUN_FLAGS: &[&str] = &[
"task",
"model",
"yolo",
"allow",
"max-depth",
"no-seed-commands",
"workdir",
"output-format",
"output-instructions",
"output-schema",
"json",
"count",
"verbose",
"help",
"version",
];
pub fn output_request(
format: Option<String>,
instructions: Option<String>,
schema: Option<String>,
) -> anyhow::Result<Option<leviath_core::output::OutputSpec>> {
if format.is_none() && instructions.is_none() && schema.is_none() {
return Ok(None);
}
let schema = match schema {
Some(raw) => {
let text = task::read_region_value(&raw)?;
Some(
serde_json::from_str(&text)
.map_err(|e| anyhow::anyhow!("--output-schema is not valid JSON: {e}"))?,
)
}
None => None,
};
Ok(Some(leviath_core::output::OutputSpec {
format,
instructions,
example: None,
schema,
validator: None,
}))
}
pub fn effective_workdir(
flag: Option<std::path::PathBuf>,
cwd: std::path::PathBuf,
) -> anyhow::Result<String> {
let dir = match flag {
Some(dir) => {
let canonical = std::fs::canonicalize(&dir).map_err(|e| {
anyhow::anyhow!(
"--workdir '{}' is not a usable directory: {e}",
dir.display()
)
})?;
if !canonical.is_dir() {
anyhow::bail!("--workdir '{}' is not a directory", dir.display());
}
canonical
}
None => cwd,
};
Ok(dir.to_string_lossy().to_string())
}
pub fn extract_region_flags(argv: Vec<String>) -> (Vec<String>, HashMap<String, String>) {
let sub_pos = argv
.iter()
.enumerate()
.skip(1)
.find(|(_, t)| !t.starts_with('-'))
.map(|(i, _)| i);
let Some(sub_pos) = sub_pos else {
return (argv, HashMap::new());
};
if argv[sub_pos] != "run" {
return (argv, HashMap::new());
}
let mut out: Vec<String> = argv[..=sub_pos].to_vec();
let mut regions = HashMap::new();
let mut i = sub_pos + 1;
while i < argv.len() {
let token = &argv[i];
if let Some(name) = token.strip_prefix("--") {
let (name, inline) = match name.split_once('=') {
Some((n, v)) => (n, Some(v.to_string())),
None => (name, None),
};
if !name.is_empty() && !KNOWN_RUN_FLAGS.contains(&name) {
let value = match inline {
Some(v) => v,
None => {
i += 1;
argv.get(i).cloned().unwrap_or_default()
}
};
regions.insert(name.to_string(), value);
i += 1;
continue;
}
}
out.push(token.clone());
i += 1;
}
(out, regions)
}
#[cfg(test)]
mod tests {
#[test]
fn an_output_request_carries_an_unrecognized_format_through() {
let spec = output_request(
Some("a2ui".to_string()),
Some("One card per finding.".to_string()),
None,
)
.expect("no schema to parse")
.expect("something was asked for");
assert_eq!(spec.format.as_deref(), Some("a2ui"));
assert_eq!(spec.instructions.as_deref(), Some("One card per finding."));
assert!(spec.schema.is_none());
}
#[test]
fn an_output_schema_can_be_read_from_a_file() {
let dir = tempfile::tempdir().expect("temp dir");
let path = dir.path().join("schema.json");
std::fs::write(&path, r#"{"type":"object","required":["summary"]}"#).expect("write");
let spec = output_request(None, None, Some(format!("@{}", path.display())))
.expect("the file parses")
.expect("something was asked for");
assert_eq!(
spec.schema,
Some(serde_json::json!({"type": "object", "required": ["summary"]}))
);
let err = output_request(
None,
None,
Some(format!("@{}", dir.path().join("gone.json").display())),
)
.expect_err("a file that is not there");
assert!(
err.to_string().contains("Failed to read region file"),
"{err}"
);
}
#[test]
fn no_output_flags_request_nothing() {
assert!(
output_request(None, None, None)
.expect("nothing to parse")
.is_none()
);
}
#[test]
fn an_output_schema_is_parsed_and_a_broken_one_is_refused() {
let spec = output_request(None, None, Some(r#"{"type":"object"}"#.to_string()))
.expect("valid JSON")
.expect("something was asked for");
assert_eq!(spec.schema, Some(serde_json::json!({"type": "object"})));
let err = output_request(None, None, Some("{not json".to_string()))
.expect_err("broken JSON is refused");
assert!(err.to_string().contains("not valid JSON"), "{err}");
}
use super::*;
fn argv(parts: &[&str]) -> Vec<String> {
parts.iter().map(|s| s.to_string()).collect()
}
#[test]
fn every_flag_run_declares_is_a_known_run_flag() {
let command = <RunArgs as clap::Args>::augment_args(clap::Command::new("run"));
for arg in command.get_arguments() {
if let Some(long) = arg.get_long() {
assert!(
KNOWN_RUN_FLAGS.contains(&long),
"`--{long}` is missing from KNOWN_RUN_FLAGS",
);
}
}
}
#[test]
fn extracts_dynamic_region_flags_and_preserves_known_ones() {
let (out, regions) = extract_region_flags(argv(&[
"lev",
"run",
"agents/reviewer",
"--task",
"review it",
"--files",
"@src/main.rs",
"--review-criteria",
"@policy.md",
"--yolo",
]));
assert_eq!(
out,
argv(&[
"lev",
"run",
"agents/reviewer",
"--task",
"review it",
"--yolo",
])
);
assert_eq!(
regions.get("files").map(String::as_str),
Some("@src/main.rs")
);
assert_eq!(
regions.get("review-criteria").map(String::as_str),
Some("@policy.md")
);
}
#[test]
fn extracts_equals_joined_region_flag() {
let (out, regions) = extract_region_flags(argv(&["lev", "run", "a", "--criteria=be safe"]));
assert_eq!(out, argv(&["lev", "run", "a"]));
assert_eq!(regions.get("criteria").map(String::as_str), Some("be safe"));
}
#[test]
fn no_region_flags_leaves_argv_unchanged() {
let input = argv(&["lev", "run", "a", "--task", "t"]);
let (out, regions) = extract_region_flags(input.clone());
assert_eq!(out, input);
assert!(regions.is_empty());
}
#[test]
fn non_run_subcommand_is_untouched() {
let input = argv(&["lev", "ps", "--weird", "x"]);
let (out, regions) = extract_region_flags(input.clone());
assert_eq!(out, input);
assert!(regions.is_empty());
}
#[test]
fn no_subcommand_token_is_untouched() {
let input = argv(&["lev", "--verbose"]);
let (out, regions) = extract_region_flags(input.clone());
assert_eq!(out, input);
assert!(regions.is_empty());
}
#[test]
fn trailing_region_flag_without_value_maps_to_empty() {
let (out, regions) = extract_region_flags(argv(&["lev", "run", "a", "--spec"]));
assert_eq!(out, argv(&["lev", "run", "a"]));
assert_eq!(regions.get("spec").map(String::as_str), Some(""));
}
#[test]
fn global_verbose_before_run_still_activates() {
let (_out, regions) = extract_region_flags(argv(&["lev", "-v", "run", "a", "--spec", "x"]));
assert_eq!(regions.get("spec").map(String::as_str), Some("x"));
}
#[test]
fn workdir_flag_is_not_eaten_as_a_region() {
let input = argv(&["lev", "run", "a", "--workdir", "/elsewhere", "--task", "t"]);
let (out, regions) = extract_region_flags(input.clone());
assert_eq!(out, input);
assert!(regions.is_empty());
}
#[test]
fn effective_workdir_uses_the_flag_canonicalized() {
let dir = tempfile::tempdir().unwrap();
let got = effective_workdir(
Some(dir.path().to_path_buf()),
std::path::PathBuf::from("/unused"),
)
.unwrap();
assert_eq!(
got,
std::fs::canonicalize(dir.path())
.unwrap()
.to_string_lossy()
.to_string()
);
}
#[test]
fn effective_workdir_defaults_to_the_supplied_cwd() {
let cwd = tempfile::tempdir().unwrap();
let got = effective_workdir(None, cwd.path().to_path_buf()).unwrap();
assert_eq!(got, cwd.path().to_string_lossy().to_string());
}
#[test]
fn effective_workdir_refuses_a_missing_or_non_directory_path() {
let cwd = std::path::PathBuf::from("/unused");
let err = effective_workdir(
Some(std::path::PathBuf::from("/definitely/not/a/real/dir")),
cwd.clone(),
)
.unwrap_err();
assert!(err.to_string().contains("not a usable directory"), "{err}");
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("f.txt");
std::fs::write(&file, "x").unwrap();
let err = effective_workdir(Some(file), cwd).unwrap_err();
assert!(err.to_string().contains("not a directory"), "{err}");
}
}