use std::collections::HashMap;
use std::path::PathBuf;
use crate::error::XCheckerError;
use super::{
Config, ConfigSource, Defaults, HooksConfig, LlmConfig, PhasesConfig, RunnerConfig,
SecurityConfig, Selectors,
};
impl Config {
#[must_use]
pub fn builder() -> ConfigBuilder {
ConfigBuilder::new()
}
}
#[derive(Debug, Clone)]
pub struct ConfigBuilder {
state_dir: Option<PathBuf>,
packet_max_bytes: Option<usize>,
packet_max_lines: Option<usize>,
phase_timeout: Option<std::time::Duration>,
runner_mode: Option<String>,
model: Option<String>,
max_turns: Option<u32>,
verbose: Option<bool>,
llm_provider: Option<String>,
execution_strategy: Option<String>,
extra_secret_patterns: Vec<String>,
ignore_secret_patterns: Vec<String>,
}
impl Default for ConfigBuilder {
fn default() -> Self {
Self::new()
}
}
impl ConfigBuilder {
#[must_use]
pub fn new() -> Self {
Self {
state_dir: None,
packet_max_bytes: None,
packet_max_lines: None,
phase_timeout: None,
runner_mode: None,
model: None,
max_turns: None,
verbose: None,
llm_provider: None,
execution_strategy: None,
extra_secret_patterns: Vec::new(),
ignore_secret_patterns: Vec::new(),
}
}
#[must_use]
pub fn state_dir(mut self, path: impl Into<PathBuf>) -> Self {
self.state_dir = Some(path.into());
self
}
#[must_use]
pub fn packet_max_bytes(mut self, bytes: usize) -> Self {
self.packet_max_bytes = Some(bytes);
self
}
#[must_use]
pub fn packet_max_lines(mut self, lines: usize) -> Self {
self.packet_max_lines = Some(lines);
self
}
#[must_use]
pub fn phase_timeout(mut self, timeout: std::time::Duration) -> Self {
self.phase_timeout = Some(timeout);
self
}
#[must_use]
pub fn runner_mode(mut self, mode: impl Into<String>) -> Self {
self.runner_mode = Some(mode.into());
self
}
#[must_use]
pub fn model(mut self, model: impl Into<String>) -> Self {
self.model = Some(model.into());
self
}
#[must_use]
pub fn max_turns(mut self, turns: u32) -> Self {
self.max_turns = Some(turns);
self
}
#[must_use]
pub fn verbose(mut self, verbose: bool) -> Self {
self.verbose = Some(verbose);
self
}
#[must_use]
pub fn llm_provider(mut self, provider: impl Into<String>) -> Self {
self.llm_provider = Some(provider.into());
self
}
#[must_use]
pub fn execution_strategy(mut self, strategy: impl Into<String>) -> Self {
self.execution_strategy = Some(strategy.into());
self
}
#[must_use]
pub fn extra_secret_patterns(mut self, patterns: Vec<String>) -> Self {
self.extra_secret_patterns = patterns;
self
}
#[must_use]
pub fn add_extra_secret_pattern(mut self, pattern: impl Into<String>) -> Self {
self.extra_secret_patterns.push(pattern.into());
self
}
#[must_use]
pub fn ignore_secret_patterns(mut self, patterns: Vec<String>) -> Self {
self.ignore_secret_patterns = patterns;
self
}
#[must_use]
pub fn add_ignore_secret_pattern(mut self, pattern: impl Into<String>) -> Self {
self.ignore_secret_patterns.push(pattern.into());
self
}
pub fn build(self) -> Result<Config, XCheckerError> {
let mut source_attribution = HashMap::new();
let mut defaults = Defaults::default();
let selectors = Selectors::default();
let mut runner = RunnerConfig::default();
let mut llm = LlmConfig {
provider: None,
fallback_provider: None,
claude: None,
gemini: None,
openrouter: None,
anthropic: None,
execution_strategy: None,
prompt_template: None,
};
let phases = PhasesConfig::default();
let hooks = HooksConfig::default();
for key in [
"max_turns",
"packet_max_bytes",
"packet_max_lines",
"output_format",
"verbose",
"runner_mode",
"phase_timeout",
"stdout_cap_bytes",
"stderr_cap_bytes",
"lock_ttl_seconds",
"debug_packet",
"allow_links",
"llm_provider",
"execution_strategy",
] {
source_attribution.insert(key.to_string(), ConfigSource::Default);
}
llm.provider = Some("claude-cli".to_string());
llm.execution_strategy = Some("controlled".to_string());
if let Some(bytes) = self.packet_max_bytes {
defaults.packet_max_bytes = Some(bytes);
source_attribution.insert("packet_max_bytes".to_string(), ConfigSource::Programmatic);
}
if let Some(lines) = self.packet_max_lines {
defaults.packet_max_lines = Some(lines);
source_attribution.insert("packet_max_lines".to_string(), ConfigSource::Programmatic);
}
if let Some(timeout) = self.phase_timeout {
defaults.phase_timeout = Some(timeout.as_secs());
source_attribution.insert("phase_timeout".to_string(), ConfigSource::Programmatic);
}
if let Some(mode) = self.runner_mode {
runner.mode = Some(mode);
source_attribution.insert("runner_mode".to_string(), ConfigSource::Programmatic);
}
if let Some(model) = self.model {
defaults.model = Some(model);
source_attribution.insert("model".to_string(), ConfigSource::Programmatic);
}
if let Some(turns) = self.max_turns {
defaults.max_turns = Some(turns);
source_attribution.insert("max_turns".to_string(), ConfigSource::Programmatic);
}
if let Some(verbose) = self.verbose {
defaults.verbose = Some(verbose);
source_attribution.insert("verbose".to_string(), ConfigSource::Programmatic);
}
if let Some(provider) = self.llm_provider {
llm.provider = Some(provider);
source_attribution.insert("llm_provider".to_string(), ConfigSource::Programmatic);
}
if let Some(strategy) = self.execution_strategy {
llm.execution_strategy = Some(strategy);
source_attribution.insert("execution_strategy".to_string(), ConfigSource::Programmatic);
}
if self.state_dir.is_some() {
source_attribution.insert("state_dir".to_string(), ConfigSource::Programmatic);
}
let security = SecurityConfig {
extra_secret_patterns: self.extra_secret_patterns,
ignore_secret_patterns: self.ignore_secret_patterns,
};
if !security.extra_secret_patterns.is_empty() || !security.ignore_secret_patterns.is_empty()
{
source_attribution.insert("security".to_string(), ConfigSource::Programmatic);
}
let config = Config {
defaults,
selectors,
runner,
llm,
phases,
hooks,
security,
source_attribution,
};
config.validate()?;
Ok(config)
}
}