use clap::parser::ValueSource;
use clap::{ArgMatches, CommandFactory, FromArgMatches};
use super::config_loop::subcommand_flag_from_command_line;
use super::{Cli, Commands, SharedOpts};
use crate::malvin_config_file::AgentConfig;
use crate::model_id::require_prefixed_model;
pub(crate) fn global_flag_from_command_line(matches: &ArgMatches, id: &str) -> bool {
matches
.value_source(id)
.is_some_and(|source| source == ValueSource::CommandLine)
}
pub(crate) struct LoopDefaultMut<'a> {
pub max_loops: &'a mut usize,
pub max_hypotheses: &'a mut usize,
pub config_max_loops: usize,
pub config_max_hypotheses: usize,
}
pub(crate) fn apply_loop_defaults(
matches: &ArgMatches,
subcommand: &str,
loops: LoopDefaultMut<'_>,
) {
if !subcommand_flag_from_command_line(matches, subcommand, "max_loops") {
*loops.max_loops = loops.config_max_loops;
}
if !subcommand_flag_from_command_line(matches, subcommand, "max_hypotheses") {
*loops.max_hypotheses = loops.config_max_hypotheses;
}
}
fn apply_write_loop_defaults(
matches: &ArgMatches,
write_args: &mut crate::cli::write_flow::WriteArgs,
agent: &AgentConfig,
review: &crate::malvin_config_file::ReviewConfig,
) {
apply_loop_defaults(
matches,
"write",
LoopDefaultMut {
max_loops: &mut write_args.max_loops,
max_hypotheses: &mut write_args.max_hypotheses,
config_max_loops: agent.max_loops_code,
config_max_hypotheses: review
.max_hypotheses
.unwrap_or(crate::malvin_config_file::DEFAULT_WRITE_MAX_HYPOTHESES),
},
);
}
fn apply_gate_loop_command_defaults(
matches: &ArgMatches,
command: &mut Commands,
agent: &AgentConfig,
review: &crate::malvin_config_file::ReviewConfig,
) {
match command {
Commands::Write(write_args) => {
apply_write_loop_defaults(matches, write_args, agent, review);
}
Commands::Inspire(_) | Commands::Adaptix(_) | Commands::Models(_) => {}
}
}
fn finalize_shared_model(matches: &ArgMatches, shared: &mut SharedOpts) -> Result<(), String> {
let _ = matches;
let _ = require_prefixed_model(&shared.model.canonical())?;
Ok(())
}
fn load_agent_config(matches: &ArgMatches) -> Result<AgentConfig, String> {
let cwd = std::env::current_dir().map_err(|e| e.to_string())?;
if global_flag_from_command_line(matches, "model") {
return Ok(crate::malvin_config_file::load_agent_config_lenient(&cwd));
}
crate::malvin_config_file::load_agent_config_strict(&cwd)
}
fn apply_shared_and_finalize(
matches: &ArgMatches,
shared: &mut SharedOpts,
agent: &AgentConfig,
) -> Result<(), String> {
apply_shared_config_defaults(matches, shared, agent);
finalize_shared_model(matches, shared)
}
fn apply_default_route_max_hypotheses(matches: &ArgMatches, cli: &mut Cli) -> Result<(), String> {
if global_flag_from_command_line(matches, "max_hypotheses") {
return Ok(());
}
let cwd = std::env::current_dir().map_err(|e| e.to_string())?;
let configured = crate::malvin_config_file::load_malvin_config(&cwd)
.default_workflow
.max_hypotheses_or_default();
cli.max_hypotheses = if configured == 0 {
crate::malvin_config_file::DEFAULT_MAX_HYPOTHESES
} else {
configured
};
Ok(())
}
const fn is_bare_default_route(cli: &Cli) -> bool {
!cli.do_workflow && cli.command.is_none() && cli.request.is_some()
}
#[must_use]
pub(crate) const fn is_gates_only_route(cli: &Cli) -> bool {
!cli.do_workflow && cli.command.is_none() && cli.request.is_none() && cli.shared.gates
}
fn apply_gates_only_loop_defaults(matches: &ArgMatches, cli: &mut Cli, agent: &AgentConfig) {
if !global_flag_from_command_line(matches, "max_loops") {
cli.max_loops = agent.max_loops_code;
}
if !global_flag_from_command_line(matches, "max_hypotheses") {
cli.max_hypotheses = agent.max_hypotheses;
}
}
const fn uses_lightweight_config_path(cli: &Cli) -> bool {
cli.do_workflow
|| matches!(cli.command, Some(Commands::Models(_)))
|| (cli.command.is_none() && cli.request.is_some())
}
fn apply_gates_only_workspace_defaults(
matches: &ArgMatches,
cli: &mut Cli,
) -> Result<(), String> {
let agent = load_agent_config(matches)?;
apply_shared_config_defaults(matches, &mut cli.shared, &agent);
apply_gates_only_loop_defaults(matches, cli, &agent);
finalize_shared_model(matches, &mut cli.shared)
}
pub fn apply_workspace_config_defaults(matches: &ArgMatches, cli: &mut Cli) -> Result<(), String> {
if uses_lightweight_config_path(cli) {
let agent = load_agent_config(matches)?;
apply_shared_and_finalize(matches, &mut cli.shared, &agent)?;
if is_bare_default_route(cli) {
apply_default_route_max_hypotheses(matches, cli)?;
}
return Ok(());
}
if is_gates_only_route(cli) {
return apply_gates_only_workspace_defaults(matches, cli);
}
let Some(command) = cli.command.as_mut() else {
return finalize_shared_model(matches, &mut cli.shared);
};
let agent = load_agent_config(matches)?;
let cwd = std::env::current_dir().map_err(|e| e.to_string())?;
let review = crate::malvin_config_file::load_malvin_config(&cwd).review;
apply_shared_config_defaults(matches, &mut cli.shared, &agent);
apply_gate_loop_command_defaults(matches, command, &agent, &review);
finalize_shared_model(matches, &mut cli.shared)
}
pub(crate) fn apply_shared_config_defaults(
matches: &ArgMatches,
shared: &mut SharedOpts,
agent: &AgentConfig,
) {
if !global_flag_from_command_line(matches, "model")
&& let Ok(parsed) = crate::model_id::parse_model_id(&agent.model) {
shared.model = parsed;
}
if !global_flag_from_command_line(matches, "max_acp_retries") {
shared.max_acp_retries = agent.max_acp_retries;
}
}
pub fn parse_cli_with_config_defaults(
args: impl IntoIterator<Item = impl Into<std::ffi::OsString> + Clone>,
) -> Result<(Cli, ArgMatches), clap::Error> {
let cmd = Cli::command();
let matches = cmd.try_get_matches_from(args)?;
let mut cli = Cli::from_arg_matches(&matches)?;
if let Err(e) = apply_workspace_config_defaults(&matches, &mut cli) {
return Err(clap::Error::raw(clap::error::ErrorKind::InvalidValue, e));
}
Ok((cli, matches))
}
#[cfg(test)]
#[path = "config_defaults_tests.rs"]
mod config_defaults_tests;
#[cfg(test)]
#[path = "config_defaults_tests_write.rs"]
mod config_defaults_tests_write;
#[cfg(test)]
#[path = "config_defaults_tests_legacy_model.rs"]
mod config_defaults_tests_legacy_model;
#[cfg(test)]
#[path = "config_defaults_tests_router.rs"]
mod config_defaults_tests_router;