1use crate::traits::{ChannelConfig, HasPropKind, PropKind};
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7use std::fmt;
8use zeroclaw_macros::Configurable;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
14#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
15#[serde(rename_all = "lowercase")]
16pub enum ThinkingLevel {
17 Off,
18 Minimal,
19 Low,
20 #[default]
21 Medium,
22 High,
23 Max,
24}
25
26impl HasPropKind for ThinkingLevel {
27 const PROP_KIND: PropKind = PropKind::Enum;
28}
29
30impl ThinkingLevel {
31 pub fn from_str_insensitive(s: &str) -> Option<Self> {
32 match s.to_lowercase().as_str() {
33 "off" | "none" => Some(Self::Off),
34 "minimal" | "min" => Some(Self::Minimal),
35 "low" => Some(Self::Low),
36 "medium" | "med" | "default" => Some(Self::Medium),
37 "high" => Some(Self::High),
38 "max" | "maximum" => Some(Self::Max),
39 _ => None,
40 }
41 }
42
43 pub fn as_str(&self) -> &'static str {
44 match self {
45 Self::Off => "off",
46 Self::Minimal => "minimal",
47 Self::Low => "low",
48 Self::Medium => "medium",
49 Self::High => "high",
50 Self::Max => "max",
51 }
52 }
53
54 pub fn default_budget_tokens(&self) -> Option<u32> {
55 match self {
56 Self::Off | Self::Minimal | Self::Low | Self::Medium => None,
57 Self::High => Some(10_000),
58 Self::Max => Some(50_000),
59 }
60 }
61}
62
63pub use zeroclaw_api::model_provider::{
64 MAX_BUDGET_TOKENS, MIN_BUDGET_TOKENS, NativeThinkingParams,
65};
66
67#[derive(Debug, Clone, Serialize, Deserialize, Configurable)]
69#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
70#[prefix = "agent.thinking"]
71pub struct ThinkingConfig {
72 #[serde(default)]
73 pub default_level: ThinkingLevel,
74 #[serde(default)]
80 pub native_thinking: bool,
81 #[serde(default)]
82 pub budget_tokens: HashMap<String, u32>,
83}
84
85impl Default for ThinkingConfig {
86 fn default() -> Self {
87 Self {
88 default_level: ThinkingLevel::Medium,
89 native_thinking: false,
90 budget_tokens: HashMap::new(),
91 }
92 }
93}
94
95impl ThinkingConfig {
96 pub fn budget_tokens_for(&self, level: ThinkingLevel) -> Option<u32> {
102 let default = level.default_budget_tokens()?;
104 Some(
105 self.budget_tokens
106 .get(level.as_str())
107 .copied()
108 .unwrap_or(default),
109 )
110 }
111
112 pub fn warn_unknown_budget_keys(&self) {
113 use ThinkingLevel::{High, Low, Max, Medium, Minimal, Off};
114 const ALL_LEVELS: &[ThinkingLevel] = &[Off, Minimal, Low, Medium, High, Max];
115 for key in self.budget_tokens.keys() {
116 if !ALL_LEVELS.iter().any(|l| l.as_str() == key) {
117 ::zeroclaw_log::record!(
118 WARN,
119 ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Note)
120 .with_attrs(::serde_json::json!({"key": key})),
121 "Unknown thinking level in budget_tokens config; \
122 valid levels are: off, minimal, low, medium, high, max"
123 );
124 }
125 }
126 }
127}
128
129fn default_max_tokens() -> usize {
130 8192
131}
132fn default_keep_recent() -> usize {
133 4
134}
135fn default_collapse() -> bool {
136 true
137}
138
139#[derive(Debug, Clone, Serialize, Deserialize, Configurable)]
140#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
141#[prefix = "agent.history-pruning"]
142pub struct HistoryPrunerConfig {
143 #[serde(default)]
144 pub enabled: bool,
145 #[serde(default = "default_max_tokens")]
146 pub max_tokens: usize,
147 #[serde(default = "default_keep_recent")]
148 pub keep_recent: usize,
149 #[serde(default = "default_collapse")]
150 pub collapse_tool_results: bool,
151}
152
153impl Default for HistoryPrunerConfig {
154 fn default() -> Self {
155 Self {
156 enabled: false,
157 max_tokens: 8192,
158 keep_recent: 4,
159 collapse_tool_results: true,
160 }
161 }
162}
163
164fn default_cost_optimized_hint() -> String {
165 "cost-optimized".to_string()
166}
167
168#[derive(Debug, Clone, Serialize, Deserialize, Configurable)]
169#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
170#[prefix = "agent.auto-classify"]
171pub struct AutoClassifyConfig {
172 #[serde(default)]
173 pub simple_hint: Option<String>,
174 #[serde(default)]
175 pub standard_hint: Option<String>,
176 #[serde(default)]
177 pub complex_hint: Option<String>,
178 #[serde(default = "default_cost_optimized_hint")]
179 pub cost_optimized_hint: String,
180}
181
182impl Default for AutoClassifyConfig {
183 fn default() -> Self {
184 Self {
185 simple_hint: None,
186 standard_hint: None,
187 complex_hint: None,
188 cost_optimized_hint: default_cost_optimized_hint(),
189 }
190 }
191}
192
193fn default_min_quality_score() -> f64 {
194 0.5
195}
196fn default_eval_max_retries() -> u32 {
197 1
198}
199
200#[derive(Debug, Clone, Serialize, Deserialize, Configurable)]
201#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
202#[prefix = "agent.eval"]
203pub struct EvalConfig {
204 #[serde(default)]
205 pub enabled: bool,
206 #[serde(default = "default_min_quality_score")]
207 pub min_quality_score: f64,
208 #[serde(default = "default_eval_max_retries")]
209 pub max_retries: u32,
210}
211
212impl Default for EvalConfig {
213 fn default() -> Self {
214 Self {
215 enabled: false,
216 min_quality_score: default_min_quality_score(),
217 max_retries: default_eval_max_retries(),
218 }
219 }
220}
221
222fn default_cc_enabled() -> bool {
223 true
224}
225fn default_threshold_ratio() -> f64 {
226 0.50
227}
228fn default_protect_first_n() -> usize {
229 3
230}
231fn default_protect_last_n() -> usize {
232 4
233}
234fn default_cc_max_passes() -> u32 {
235 3
236}
237fn default_summary_max_chars() -> usize {
238 4000
239}
240fn default_source_max_chars() -> usize {
241 50_000
242}
243fn default_cc_timeout_secs() -> u64 {
244 60
245}
246fn default_identifier_policy() -> String {
247 "strict".to_string()
248}
249fn default_tool_result_retrim_chars() -> usize {
250 2_000
251}
252
253#[derive(Debug, Clone, Serialize, Deserialize, Configurable)]
254#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
255#[prefix = "agent.context-compression"]
256pub struct ContextCompressionConfig {
257 #[serde(default = "default_cc_enabled")]
258 pub enabled: bool,
259 #[serde(default = "default_threshold_ratio")]
260 pub threshold_ratio: f64,
261 #[serde(default = "default_protect_first_n")]
262 pub protect_first_n: usize,
263 #[serde(default = "default_protect_last_n")]
264 pub protect_last_n: usize,
265 #[serde(default = "default_cc_max_passes")]
266 pub max_passes: u32,
267 #[serde(default = "default_summary_max_chars")]
268 pub summary_max_chars: usize,
269 #[serde(default = "default_source_max_chars")]
270 pub source_max_chars: usize,
271 #[serde(default = "default_cc_timeout_secs")]
272 pub timeout_secs: u64,
273 #[serde(default)]
274 pub summary_model: Option<String>,
275 #[serde(default = "default_identifier_policy")]
276 pub identifier_policy: String,
277 #[serde(default = "default_tool_result_retrim_chars")]
278 pub tool_result_retrim_chars: usize,
279 #[serde(default)]
280 pub tool_result_trim_exempt: Vec<String>,
281}
282
283impl Default for ContextCompressionConfig {
284 fn default() -> Self {
285 Self {
286 enabled: default_cc_enabled(),
287 threshold_ratio: default_threshold_ratio(),
288 protect_first_n: default_protect_first_n(),
289 protect_last_n: default_protect_last_n(),
290 max_passes: default_cc_max_passes(),
291 summary_max_chars: default_summary_max_chars(),
292 source_max_chars: default_source_max_chars(),
293 timeout_secs: default_cc_timeout_secs(),
294 summary_model: None,
295 identifier_policy: default_identifier_policy(),
296 tool_result_retrim_chars: default_tool_result_retrim_chars(),
297 tool_result_trim_exempt: Vec::new(),
298 }
299 }
300}
301
302fn default_precheck_enabled() -> bool {
303 true
304}
305fn default_precheck_timeout_secs() -> u64 {
306 5
307}
308
309#[derive(Debug, Clone, Serialize, Deserialize, Configurable)]
322#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
323#[prefix = "agent.precheck"]
324pub struct ChannelPrecheckConfig {
325 #[serde(default = "default_precheck_enabled")]
328 pub enabled: bool,
329 #[serde(default = "default_precheck_timeout_secs")]
332 pub timeout_secs: u64,
333}
334
335impl Default for ChannelPrecheckConfig {
336 fn default() -> Self {
337 Self {
338 enabled: default_precheck_enabled(),
339 timeout_secs: default_precheck_timeout_secs(),
340 }
341 }
342}
343
344fn default_browser_cli() -> String {
347 "claude".into()
348}
349fn default_browser_task_timeout() -> u64 {
350 120
351}
352
353#[derive(Debug, Clone, Serialize, Deserialize, Configurable)]
354#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
355#[prefix = "browser-delegate"]
356pub struct BrowserDelegateConfig {
357 #[serde(default)]
358 pub enabled: bool,
359 #[serde(default = "default_browser_cli")]
360 pub cli_binary: String,
361 #[serde(default)]
362 pub chrome_profile_dir: String,
363 #[serde(default)]
364 pub allowed_domains: Vec<String>,
365 #[serde(default)]
366 pub blocked_domains: Vec<String>,
367 #[serde(default = "default_browser_task_timeout")]
368 pub task_timeout_secs: u64,
369}
370
371impl Default for BrowserDelegateConfig {
372 fn default() -> Self {
373 Self {
374 enabled: false,
375 cli_binary: default_browser_cli(),
376 chrome_profile_dir: String::new(),
377 allowed_domains: Vec::new(),
378 blocked_domains: Vec::new(),
379 task_timeout_secs: default_browser_task_timeout(),
380 }
381 }
382}
383
384fn default_initial_score() -> f64 {
387 0.8
388}
389fn default_decay_half_life() -> f64 {
390 30.0
391}
392fn default_regression_threshold() -> f64 {
393 0.5
394}
395fn default_correction_penalty() -> f64 {
396 0.05
397}
398fn default_success_boost() -> f64 {
399 0.01
400}
401
402#[derive(Debug, Clone, Serialize, Deserialize, Configurable)]
403#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
404#[prefix = "trust"]
405pub struct TrustConfig {
406 #[serde(default = "default_initial_score")]
407 pub initial_score: f64,
408 #[serde(default = "default_decay_half_life")]
409 pub decay_half_life_days: f64,
410 #[serde(default = "default_regression_threshold")]
411 pub regression_threshold: f64,
412 #[serde(default = "default_correction_penalty")]
413 pub correction_penalty: f64,
414 #[serde(default = "default_success_boost")]
415 pub success_boost: f64,
416}
417
418impl Default for TrustConfig {
419 fn default() -> Self {
420 Self {
421 initial_score: default_initial_score(),
422 decay_half_life_days: default_decay_half_life(),
423 regression_threshold: default_regression_threshold(),
424 correction_penalty: default_correction_penalty(),
425 success_boost: default_success_boost(),
426 }
427 }
428}
429
430fn default_imap_port() -> u16 {
433 993
434}
435fn default_smtp_port() -> u16 {
436 465
437}
438fn default_imap_folder() -> String {
439 "INBOX".into()
440}
441fn default_idle_timeout() -> u64 {
442 1740
443}
444fn default_poll_interval_secs() -> u64 {
445 60
446}
447fn default_true() -> bool {
448 true
449}
450fn default_subject() -> String {
451 "Re: Message".into()
452}
453fn default_max_attachment_bytes() -> usize {
454 25 * 1024 * 1024
455}
456
457#[derive(Debug, Clone, Serialize, Deserialize, zeroclaw_macros::Configurable)]
458#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
459#[prefix = "channels.email"]
460pub struct EmailConfig {
461 #[serde(default)]
466 pub enabled: bool,
467 pub imap_host: String,
468 #[serde(default = "default_imap_port")]
469 pub imap_port: u16,
470 #[serde(default = "default_imap_folder")]
471 pub imap_folder: String,
472 pub smtp_host: String,
473 #[serde(default = "default_smtp_port")]
474 pub smtp_port: u16,
475 #[serde(default = "default_true")]
476 pub smtp_tls: bool,
477 #[serde(default)]
478 pub smtp_username: Option<String>,
479 #[secret]
480 #[serde(default)]
481 pub smtp_password: Option<String>,
482 pub username: String,
483 #[secret]
484 pub password: String,
485 pub from_address: String,
486 #[serde(default = "default_idle_timeout")]
487 pub idle_timeout_secs: u64,
488 #[serde(default = "default_poll_interval_secs")]
491 pub poll_interval_secs: u64,
492 #[serde(default = "default_subject")]
493 pub default_subject: String,
494 #[serde(default = "default_max_attachment_bytes")]
495 pub max_attachment_bytes: usize,
496
497 #[serde(default)]
500 pub excluded_tools: Vec<String>,
501 #[serde(default = "default_true")]
504 pub html_body: bool,
505}
506
507impl ChannelConfig for EmailConfig {
508 fn name() -> &'static str {
509 "Email"
510 }
511 fn desc() -> &'static str {
512 "Email over IMAP/SMTP"
513 }
514}
515
516impl Default for EmailConfig {
517 fn default() -> Self {
518 Self {
519 enabled: false,
520 imap_host: String::new(),
521 imap_port: default_imap_port(),
522 imap_folder: default_imap_folder(),
523 smtp_host: String::new(),
524 smtp_port: default_smtp_port(),
525 smtp_tls: true,
526 smtp_username: None,
527 smtp_password: None,
528 username: String::new(),
529 password: String::new(),
530 from_address: String::new(),
531 idle_timeout_secs: default_idle_timeout(),
532 poll_interval_secs: default_poll_interval_secs(),
533 default_subject: default_subject(),
534 max_attachment_bytes: default_max_attachment_bytes(),
535 excluded_tools: Vec::new(),
536 html_body: true,
537 }
538 }
539}
540
541fn default_label_filter() -> Vec<String> {
542 vec!["INBOX".into()]
543}
544
545#[derive(Debug, Clone, Serialize, Deserialize, zeroclaw_macros::Configurable)]
546#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
547#[prefix = "channels.gmail"]
548pub struct GmailPushConfig {
549 #[serde(default)]
554 pub enabled: bool,
555 pub topic: String,
556 #[serde(default = "default_label_filter")]
557 pub label_filter: Vec<String>,
558 #[serde(default)]
559 #[secret]
560 pub oauth_token: String,
561 #[serde(default)]
562 pub webhook_url: String,
563 #[serde(default)]
564 pub webhook_secret: String,
565
566 #[serde(default)]
569 pub excluded_tools: Vec<String>,
570}
571
572impl ChannelConfig for GmailPushConfig {
573 fn name() -> &'static str {
574 "Gmail Push"
575 }
576 fn desc() -> &'static str {
577 "Gmail Pub/Sub push notifications"
578 }
579}
580
581impl Default for GmailPushConfig {
582 fn default() -> Self {
583 Self {
584 enabled: false,
585 topic: String::new(),
586 label_filter: default_label_filter(),
587 oauth_token: String::new(),
588 webhook_url: String::new(),
589 webhook_secret: String::new(),
590 excluded_tools: Vec::new(),
591 }
592 }
593}
594
595#[derive(Debug, Clone, Default, Serialize, Deserialize, zeroclaw_macros::Configurable)]
596#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
597#[prefix = "channels.clawdtalk"]
598pub struct ClawdTalkConfig {
599 #[serde(default)]
604 pub enabled: bool,
605 #[secret]
606 pub api_key: String,
607 pub connection_id: String,
608 pub from_number: String,
609 #[serde(default)]
610 pub allowed_destinations: Vec<String>,
611 #[serde(default)]
612 #[secret]
613 pub webhook_secret: Option<String>,
614
615 #[serde(default)]
618 pub excluded_tools: Vec<String>,
619}
620
621impl ChannelConfig for ClawdTalkConfig {
622 fn name() -> &'static str {
623 "ClawdTalk"
624 }
625 fn desc() -> &'static str {
626 "ClawdTalk Channel"
627 }
628}
629
630#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
632#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
633#[serde(rename_all = "lowercase")]
634pub enum VoiceProvider {
635 #[default]
636 Twilio,
637 Telnyx,
638 Plivo,
639}
640
641impl HasPropKind for VoiceProvider {
642 const PROP_KIND: PropKind = PropKind::Enum;
643}
644
645impl fmt::Display for VoiceProvider {
646 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
647 match self {
648 Self::Twilio => write!(f, "twilio"),
649 Self::Telnyx => write!(f, "telnyx"),
650 Self::Plivo => write!(f, "plivo"),
651 }
652 }
653}
654
655fn default_webhook_port() -> u16 {
656 8090
657}
658fn default_max_call_duration() -> u64 {
659 3600
660}
661
662#[derive(Debug, Clone, Serialize, Deserialize, Configurable)]
663#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
664#[prefix = "channels.voice-call"]
665pub struct VoiceCallConfig {
666 #[serde(default)]
671 pub enabled: bool,
672 #[serde(default)]
673 pub model_provider: VoiceProvider,
674 pub account_id: String,
675 pub auth_token: String,
676 pub from_number: String,
677 #[serde(default = "default_webhook_port")]
678 pub webhook_port: u16,
679 #[serde(default = "default_true")]
680 pub require_outbound_approval: bool,
681 #[serde(default = "default_true")]
682 pub transcription_logging: bool,
683 #[serde(default)]
684 pub tts_voice: Option<String>,
685 #[serde(default = "default_max_call_duration")]
686 pub max_call_duration_secs: u64,
687 #[serde(default)]
688 pub webhook_base_url: Option<String>,
689
690 #[serde(default)]
693 pub excluded_tools: Vec<String>,
694}
695
696impl crate::traits::ChannelConfig for VoiceCallConfig {
697 fn name() -> &'static str {
698 "Voice Call"
699 }
700 fn desc() -> &'static str {
701 "outbound voice call channel"
702 }
703}
704
705impl Default for VoiceCallConfig {
706 fn default() -> Self {
707 Self {
708 enabled: false,
709 model_provider: VoiceProvider::default(),
710 account_id: String::new(),
711 auth_token: String::new(),
712 from_number: String::new(),
713 webhook_port: default_webhook_port(),
714 require_outbound_approval: default_true(),
715 transcription_logging: default_true(),
716 tts_voice: None,
717 max_call_duration_secs: default_max_call_duration(),
718 webhook_base_url: None,
719 excluded_tools: Vec::new(),
720 }
721 }
722}