microclaw_engine/config/
defaults.rs1use super::*;
2
3pub(crate) fn default_telegram_bot_token() -> String {
4 String::new()
5}
6
7pub(crate) fn default_bot_username() -> String {
8 String::new()
9}
10
11pub(crate) fn default_llm_provider() -> String {
12 "anthropic".into()
13}
14
15pub(crate) fn default_api_key() -> String {
16 String::new()
17}
18
19pub fn default_model_for_provider_name(provider: &str) -> &'static str {
20 match provider.trim().to_ascii_lowercase().as_str() {
21 "anthropic" => "claude-sonnet-4-5-20250929",
22 "ollama" => "llama3.2",
23 "openai-codex" => "gpt-5.3-codex",
24 _ => "gpt-5.2",
25 }
26}
27
28pub(crate) fn default_llm_user_agent() -> String {
29 crate::http_client::default_llm_user_agent()
30}
31
32pub(crate) fn default_max_tokens() -> u32 {
33 8192
34}
35
36pub(crate) fn default_max_tool_iterations() -> usize {
37 100
38}
39
40pub(crate) fn default_chat_turn_queue_max_pending() -> usize {
41 20
42}
43
44pub(crate) fn default_parallel_tool_max_concurrency() -> usize {
45 8
46}
47
48pub(crate) fn default_compaction_timeout_secs() -> u64 {
49 180
50}
51
52pub(crate) fn default_max_history_messages() -> usize {
53 50
54}
55
56pub(crate) fn default_max_document_size_mb() -> u64 {
57 100
58}
59
60pub(crate) fn default_memory_token_budget() -> usize {
61 1500
62}
63
64pub(crate) fn default_memory_l0_identity_pct() -> usize {
65 20
66}
67
68pub(crate) fn default_memory_l1_essential_pct() -> usize {
69 30
70}
71
72pub(crate) fn default_memory_max_entries_per_chat() -> usize {
73 200
74}
75
76pub(crate) fn default_memory_max_global_entries() -> usize {
77 500
78}
79
80pub(crate) fn default_kg_max_triples_per_chat() -> usize {
81 1000
82}
83
84pub(crate) fn default_tool_result_truncation_threshold_chars() -> usize {
85 4000
86}
87
88pub(crate) fn default_tool_result_truncation_head_chars() -> usize {
89 1500
90}
91
92pub(crate) fn default_tool_result_truncation_tail_chars() -> usize {
93 500
94}
95
96pub(crate) fn default_tool_result_artifact_ttl_hours() -> u64 {
97 24
98}
99
100pub(crate) fn default_memory_recency_half_life_days() -> f64 {
101 30.0
102}
103
104pub(crate) fn default_memory_graph_recall_enabled() -> bool {
105 true
106}
107
108pub(crate) fn default_memory_graph_max_hops() -> usize {
109 2
110}
111
112pub(crate) fn default_memory_graph_max_triples() -> usize {
113 10
114}
115
116pub(crate) fn default_tool_repeat_window() -> usize {
117 10
118}
119
120pub(crate) fn default_tool_repeat_limit() -> usize {
121 3
122}
123
124pub(crate) fn default_anthropic_prompt_cache_enabled() -> bool {
125 true
126}
127
128pub(crate) fn default_anthropic_prompt_cache_ttl() -> String {
129 "5m".to_string()
130}
131
132pub(crate) fn default_checkpoints_enabled() -> bool {
133 false
134}
135
136pub(crate) fn default_skill_archive_after_days() -> u64 {
137 30
138}
139
140pub(crate) fn default_skills_catalog_top_k() -> usize {
141 3
142}
143
144pub(crate) fn default_skill_review_min_tool_calls() -> usize {
145 5
146}
147
148pub(crate) fn default_data_dir() -> String {
149 default_data_root().to_string_lossy().to_string()
150}
151
152pub(crate) fn default_data_root() -> PathBuf {
153 if std::env::var("SNAP").is_ok() {
154 if let Ok(snap_user_common) = std::env::var("SNAP_USER_COMMON") {
155 return PathBuf::from(snap_user_common);
156 }
157 }
158 expand_path("~/.microclaw")
159}
160
161pub(crate) fn default_working_dir() -> String {
162 default_data_root()
163 .join("working_dir")
164 .to_string_lossy()
165 .to_string()
166}
167
168pub(crate) fn default_working_dir_isolation() -> WorkingDirIsolation {
169 WorkingDirIsolation::Chat
170}
171
172pub(crate) fn default_bash_dangerous_patterns() -> Vec<String> {
173 vec![
174 r"\brm\s+(-[a-zA-Z]*[rfRF][a-zA-Z]*\s+)+(/|\*|~|\$HOME)".into(),
176 r"\b(curl|wget|fetch)\b[^|]*\|\s*(sudo\s+)?(sh|bash|zsh|fish)\b".into(),
178 r"\bsudo\b".into(),
180 r"\bdd\s+if=".into(),
182 r":\(\)\s*\{\s*:\s*\|\s*:&\s*\}\s*;\s*:".into(),
184 r"\bmkfs(\.[a-z0-9]+)?\b".into(),
186 r"\bch(mod|own)\s+-R\s+[^/]*\s+/(\s|$)".into(),
188 ]
189}
190
191pub(crate) fn default_high_risk_tool_user_confirmation_required() -> bool {
192 true
193}
194
195pub(crate) fn default_sandbox_image() -> String {
196 "ubuntu:25.10".into()
197}
198
199pub(crate) fn default_sandbox_container_prefix() -> String {
200 "microclaw-sandbox".into()
201}
202
203pub(crate) fn default_timezone() -> String {
204 "auto".into()
205}
206
207pub(crate) fn default_max_session_messages() -> usize {
208 40
209}
210
211pub(crate) fn default_diff_max_lines() -> usize {
212 microclaw_core::diff::DEFAULT_DIFF_MAX_LINES
213}
214
215pub(crate) fn default_model_context_window() -> usize {
216 200_000
217}
218
219pub(crate) fn default_context_pressure_compact_pct() -> usize {
220 85
221}
222
223pub(crate) fn default_file_diffs_in_chat() -> bool {
224 true
225}
226
227pub(crate) fn default_compact_keep_recent() -> usize {
228 20
229}
230
231pub(crate) fn default_tool_timeout_secs() -> u64 {
232 30
233}
234
235pub(crate) fn default_mcp_request_timeout_secs() -> u64 {
236 120
237}
238
239pub(crate) fn default_control_chat_ids() -> Vec<i64> {
240 Vec::new()
241}
242
243pub(crate) fn default_web_enabled() -> bool {
244 true
245}
246
247pub(crate) fn default_web_host() -> String {
248 "127.0.0.1".into()
249}
250
251pub(crate) fn default_web_port() -> u16 {
252 10961
253}
254
255pub(crate) fn default_web_max_inflight_per_session() -> usize {
256 10
257}
258
259pub(crate) fn default_web_max_requests_per_window() -> usize {
260 8
261}
262
263pub(crate) fn default_web_rate_window_seconds() -> u64 {
264 10
265}
266
267pub(crate) fn default_web_run_history_limit() -> usize {
268 512
269}
270
271pub(crate) fn default_web_session_idle_ttl_seconds() -> u64 {
272 300
273}
274
275pub(crate) fn default_allow_group_slash_without_mention() -> bool {
276 false
277}
278
279pub(crate) fn default_model_prices() -> Vec<ModelPrice> {
280 Vec::new()
281}
282
283pub(crate) fn default_reflector_enabled() -> bool {
284 true
285}
286
287pub(crate) fn default_reflector_interval_mins() -> u64 {
288 15
289}
290
291pub(crate) fn default_dlq_replay_enabled() -> bool {
292 true
293}
294
295pub(crate) fn default_dlq_replay_interval_secs() -> u64 {
296 300
297}
298
299pub(crate) fn default_dlq_max_replay_attempts() -> u32 {
300 3
301}
302
303pub(crate) fn default_soul_path() -> Option<String> {
304 None
305}
306
307pub(crate) fn default_souls_dir() -> Option<String> {
308 None
309}
310
311pub(crate) fn default_context_max_chars() -> usize {
312 8000
313}
314
315pub(crate) fn default_user_model_max_chars() -> usize {
316 1500
317}
318
319pub(crate) fn default_voice_provider() -> String {
320 "openai".into()
321}
322
323#[cfg(test)]
324mod tests {
325 use super::*;
326 #[allow(unused_imports)]
327 use crate::config::test_prelude::*;
328
329 #[test]
330 fn test_config_struct_clone_and_debug() {
331 let config = test_config();
332 let cloned = config.clone();
333 assert_eq!(cloned.telegram_bot_token, "tok");
334 assert_eq!(cloned.max_tokens, 8192);
335 assert_eq!(cloned.max_tool_iterations, 100);
336 assert_eq!(cloned.max_history_messages, 50);
337 assert_eq!(cloned.max_document_size_mb, 100);
338 assert_eq!(cloned.memory_token_budget, 1500);
339 assert!(cloned.openai_api_key.is_none());
340 assert_eq!(cloned.timezone, "UTC");
341 assert!(cloned.allowed_groups.is_empty());
342 assert!(cloned.control_chat_ids.is_empty());
343 assert_eq!(cloned.max_session_messages, 40);
344 assert_eq!(cloned.compact_keep_recent, 20);
345 assert_eq!(cloned.default_tool_timeout_secs, 30);
346 assert!(cloned.tool_timeout_overrides.is_empty());
347 assert_eq!(cloned.default_mcp_request_timeout_secs, 120);
348 assert!(cloned.discord_bot_token.is_none());
349 assert!(cloned.discord_allowed_channels.is_empty());
350 let _ = format!("{:?}", config);
351 }
352
353 #[test]
354 fn test_config_yaml_defaults() {
355 let yaml = "telegram_bot_token: tok\nbot_username: bot\napi_key: key\n";
356 let config: Config = serde_yaml::from_str(yaml).unwrap();
357 assert_eq!(config.llm_provider, "anthropic");
358 assert_eq!(config.max_tokens, 8192);
359 assert_eq!(config.max_tool_iterations, 100);
360 assert!(config.data_dir.ends_with(".microclaw"));
361 assert!(std::path::PathBuf::from(&config.working_dir)
362 .ends_with(std::path::Path::new(".microclaw").join("working_dir")));
363 assert_eq!(config.memory_token_budget, 1500);
364 assert!(matches!(
365 config.working_dir_isolation,
366 WorkingDirIsolation::Chat
367 ));
368 assert!(matches!(config.sandbox.mode, SandboxMode::Off));
369 assert_eq!(config.max_document_size_mb, 100);
370 assert_eq!(config.timezone, "auto");
371 assert_eq!(config.default_tool_timeout_secs, 30);
372 assert!(config.tool_timeout_overrides.is_empty());
373 assert_eq!(config.default_mcp_request_timeout_secs, 120);
374 assert!(config.web_fetch_validation.enabled);
375 assert!(config.web_fetch_validation.strict_mode);
376 assert_eq!(config.web_fetch_validation.max_scan_bytes, 100_000);
377 assert!(config.web_fetch_url_validation.enabled);
378 assert_eq!(
379 config.web_fetch_url_validation.allowed_schemes,
380 vec!["https".to_string(), "http".to_string()]
381 );
382 assert!(config.web_fetch_url_validation.allowlist_hosts.is_empty());
383 assert!(config.web_fetch_url_validation.denylist_hosts.is_empty());
384 }
385
386 #[test]
387 fn test_post_deserialize_timeout_defaults_and_overrides() {
388 let mut config = test_config();
389 config.default_tool_timeout_secs = 0;
390 config.default_mcp_request_timeout_secs = 0;
391 config.web_fetch_validation.max_scan_bytes = 0;
392 config.web_fetch_url_validation.allowed_schemes.clear();
393 config.web_fetch_url_validation.allowlist_hosts = vec![" Example.COM ".into()];
394 config.web_fetch_url_validation.denylist_hosts = vec![" .Bad.EXAMPLE ".into()];
395 config.tool_timeout_overrides = HashMap::from([
396 (" bash ".to_string(), 90),
397 ("".to_string(), 5),
398 ("browser".to_string(), 0),
399 ]);
400 config.post_deserialize().unwrap();
401
402 assert_eq!(config.default_tool_timeout_secs, 30);
403 assert_eq!(config.default_mcp_request_timeout_secs, 120);
404 assert_eq!(config.web_fetch_validation.max_scan_bytes, 100_000);
405 assert_eq!(
406 config.web_fetch_url_validation.allowed_schemes,
407 vec!["https".to_string(), "http".to_string()]
408 );
409 assert_eq!(
410 config.web_fetch_url_validation.allowlist_hosts,
411 vec!["example.com".to_string()]
412 );
413 assert_eq!(
414 config.web_fetch_url_validation.denylist_hosts,
415 vec!["bad.example".to_string()]
416 );
417 assert_eq!(config.tool_timeout_overrides.len(), 1);
418 assert_eq!(config.tool_timeout_overrides.get("bash"), Some(&90));
419 }
420
421 #[test]
422 fn test_tool_timeout_lookup_prefers_override_then_default() {
423 let mut config = test_config();
424 config.default_tool_timeout_secs = 45;
425 config.tool_timeout_overrides.insert("bash".to_string(), 75);
426
427 assert_eq!(config.tool_timeout_secs("bash", 120), 75);
428 assert_eq!(config.tool_timeout_secs("browser", 120), 45);
429 }
430}