ralph/config/validation/
agent.rs1use super::ci_gate::validate_ci_gate_config;
16use crate::constants::runner::{MAX_PHASES, MIN_ITERATIONS, MIN_PHASES};
17use crate::contracts::{AgentConfig, PhaseOverrides, Runner};
18use anyhow::{Result, bail};
19
20pub fn validate_agent_binary_paths(agent: &AgentConfig, label: &str) -> Result<()> {
21 macro_rules! check_bin {
22 ($field:ident) => {
23 if let Some(bin) = &agent.$field
24 && bin.trim().is_empty()
25 {
26 bail!(
27 "Empty {label}.{}: binary path is required if specified.",
28 stringify!($field)
29 );
30 }
31 };
32 }
33
34 check_bin!(codex_bin);
35 check_bin!(opencode_bin);
36 check_bin!(gemini_bin);
37 check_bin!(claude_bin);
38 check_bin!(cursor_bin);
39 check_bin!(kimi_bin);
40 check_bin!(pi_bin);
41
42 Ok(())
43}
44
45pub fn validate_agent_patch(agent: &AgentConfig, label: &str) -> Result<()> {
46 if let Some(phases) = agent.phases
47 && !(MIN_PHASES..=MAX_PHASES).contains(&phases)
48 {
49 bail!(
50 "Invalid {label}.phases: {phases}. Supported values are {MIN_PHASES}, {}, or {MAX_PHASES}.",
51 MIN_PHASES + 1
52 );
53 }
54
55 if let Some(iterations) = agent.iterations
56 && iterations < MIN_ITERATIONS
57 {
58 bail!(
59 "Invalid {label}.iterations: {iterations}. Iterations must be at least {MIN_ITERATIONS}."
60 );
61 }
62
63 if let Some(timeout) = agent.session_timeout_hours
64 && timeout == 0
65 {
66 bail!(
67 "Invalid {label}.session_timeout_hours: {timeout}. Session timeout must be greater than 0."
68 );
69 }
70
71 validate_agent_binary_paths(agent, label)?;
72 validate_ci_gate_config(agent.ci_gate.as_ref(), label)?;
73 Ok(())
74}
75
76pub(crate) fn agent_has_execution_settings(agent: &AgentConfig) -> bool {
77 agent.ci_gate.is_some()
78 || agent.codex_bin.is_some()
79 || agent.opencode_bin.is_some()
80 || agent.gemini_bin.is_some()
81 || agent.claude_bin.is_some()
82 || agent.cursor_bin.is_some()
83 || agent.kimi_bin.is_some()
84 || agent.pi_bin.is_some()
85 || agent.runner.as_ref().is_some_and(Runner::is_plugin)
86 || agent
87 .phase_overrides
88 .as_ref()
89 .is_some_and(phase_overrides_have_plugin_runner)
90}
91
92fn phase_overrides_have_plugin_runner(overrides: &PhaseOverrides) -> bool {
93 [&overrides.phase1, &overrides.phase2, &overrides.phase3]
94 .into_iter()
95 .flatten()
96 .filter_map(|phase| phase.runner.as_ref())
97 .any(Runner::is_plugin)
98}