Skip to main content

kimetsu_core/
config.rs

1use serde::{Deserialize, Serialize};
2
3use crate::{KIMETSU_SCHEMA_VERSION, KimetsuResult};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct ProjectConfig {
7    pub kimetsu: KimetsuSection,
8    pub model: ModelSection,
9    pub broker: BrokerSection,
10    pub shell: ShellSection,
11    pub ingestion: IngestionSection,
12    pub run: RunSection,
13}
14
15impl ProjectConfig {
16    pub fn default_for_project(project_id: impl Into<String>) -> Self {
17        Self {
18            kimetsu: KimetsuSection {
19                project_id: project_id.into(),
20                schema_version: KIMETSU_SCHEMA_VERSION,
21            },
22            model: ModelSection::default(),
23            broker: BrokerSection::default(),
24            shell: ShellSection::default(),
25            ingestion: IngestionSection::default(),
26            run: RunSection::default(),
27        }
28    }
29
30    pub fn from_toml(value: &str) -> KimetsuResult<Self> {
31        Ok(toml::from_str(value)?)
32    }
33
34    pub fn to_toml(&self) -> KimetsuResult<String> {
35        Ok(toml::to_string_pretty(self)?)
36    }
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct KimetsuSection {
41    pub project_id: String,
42    pub schema_version: i64,
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct ModelSection {
47    pub provider: String,
48    pub model: String,
49    pub api_key_env: String,
50    pub max_output_tokens: u32,
51    pub temperature: f32,
52    pub request_timeout_secs: u64,
53}
54
55impl Default for ModelSection {
56    fn default() -> Self {
57        Self {
58            provider: "anthropic".to_string(),
59            model: "claude-opus-4-7".to_string(),
60            api_key_env: "ANTHROPIC_API_KEY".to_string(),
61            max_output_tokens: 8192,
62            temperature: 0.2,
63            request_timeout_secs: 120,
64        }
65    }
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct BrokerSection {
70    pub default_budget_tokens: u32,
71    pub weights: BrokerWeights,
72}
73
74impl Default for BrokerSection {
75    fn default() -> Self {
76        Self {
77            default_budget_tokens: 6000,
78            weights: BrokerWeights::default(),
79        }
80    }
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct BrokerWeights {
85    pub relevance: f32,
86    pub confidence: f32,
87    pub freshness: f32,
88    pub scope: f32,
89    pub localization: Option<StageWeights>,
90    pub patch_plan: Option<StageWeights>,
91    pub verification: Option<StageWeights>,
92    pub review: Option<StageWeights>,
93}
94
95impl Default for BrokerWeights {
96    fn default() -> Self {
97        Self {
98            relevance: 0.50,
99            confidence: 0.20,
100            freshness: 0.20,
101            scope: 0.10,
102            localization: Some(StageWeights {
103                relevance: 0.70,
104                confidence: 0.10,
105                freshness: 0.10,
106                scope: 0.10,
107            }),
108            patch_plan: Some(StageWeights {
109                relevance: 0.40,
110                confidence: 0.30,
111                freshness: 0.10,
112                scope: 0.20,
113            }),
114            verification: Some(StageWeights {
115                relevance: 0.40,
116                confidence: 0.10,
117                freshness: 0.40,
118                scope: 0.10,
119            }),
120            review: Some(StageWeights {
121                relevance: 0.50,
122                confidence: 0.20,
123                freshness: 0.20,
124                scope: 0.10,
125            }),
126        }
127    }
128}
129
130#[derive(Debug, Clone, Serialize, Deserialize)]
131pub struct StageWeights {
132    pub relevance: f32,
133    pub confidence: f32,
134    pub freshness: f32,
135    pub scope: f32,
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize)]
139pub struct ShellSection {
140    pub default_timeout_secs: u64,
141    pub max_timeout_secs: u64,
142    pub env_allowlist_extra: Vec<String>,
143    pub redact_secrets: bool,
144}
145
146impl Default for ShellSection {
147    fn default() -> Self {
148        Self {
149            default_timeout_secs: 60,
150            max_timeout_secs: 600,
151            env_allowlist_extra: vec!["RUSTFLAGS".to_string(), "CARGO_HOME".to_string()],
152            redact_secrets: true,
153        }
154    }
155}
156
157#[derive(Debug, Clone, Serialize, Deserialize)]
158pub struct IngestionSection {
159    pub max_file_bytes: u64,
160    pub extra_skip_dirs: Vec<String>,
161    pub max_total_files: u64,
162}
163
164impl Default for IngestionSection {
165    fn default() -> Self {
166        Self {
167            max_file_bytes: 524_288,
168            extra_skip_dirs: Vec::new(),
169            max_total_files: 50_000,
170        }
171    }
172}
173
174#[derive(Debug, Clone, Serialize, Deserialize)]
175pub struct RunSection {
176    pub max_total_tool_calls: u32,
177    pub max_total_model_turns: u32,
178    pub max_total_cost_usd: f32,
179}
180
181impl Default for RunSection {
182    fn default() -> Self {
183        // `max_total_cost_usd` is treated as advisory under subscription-based
184        // providers (e.g. Claude Code OAuth). The agent loop still enforces it
185        // when it does fire, but the default is set high enough that it
186        // functions as a runaway-prevention safety net rather than a per-run
187        // budget. Tighten in `project.toml` when running against a metered
188        // provider.
189        Self {
190            max_total_tool_calls: 60,
191            max_total_model_turns: 30,
192            max_total_cost_usd: 250.0,
193        }
194    }
195}