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 Self {
190 max_total_tool_calls: 60,
191 max_total_model_turns: 30,
192 max_total_cost_usd: 250.0,
193 }
194 }
195}