1use serde::{Deserialize, Serialize};
7use std::path::PathBuf;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct AppConfig {
12 pub gateway: GatewayConfig,
13 pub database: DatabaseConfig,
14 pub providers: ProvidersConfig,
15 #[serde(default)]
16 pub external_backends: ExternalBackendsConfig,
17 pub memory: MemoryConfig,
18 pub session_routing: SessionRoutingConfig,
19 pub scheduler: SchedulerConfig,
20 pub security: SecurityConfig,
21 pub sidecar: SidecarConfig,
22 pub observability: ObservabilityConfig,
23 pub channels: ChannelsConfig,
24 pub voice: VoiceConfig,
25 pub skills: Option<SkillsConfig>,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct SessionRoutingConfig {
31 #[serde(default = "default_direct_strategy")]
32 pub direct_strategy: String,
33 #[serde(default = "default_group_strategy")]
34 pub group_strategy: String,
35 #[serde(default = "default_thread_overrides_channel")]
36 pub thread_overrides_channel: bool,
37 #[serde(default = "default_pairing_approval_required")]
38 pub pairing_approval_required: bool,
39 #[serde(default = "default_group_activation_mode")]
40 pub default_group_activation: String,
41 #[serde(default = "default_send_mode")]
42 pub default_send_mode: String,
43 #[serde(default = "default_chunk_chars")]
44 pub default_chunk_chars: usize,
45 #[serde(default)]
46 pub default_chunk_delay_ms: u64,
47}
48
49fn default_direct_strategy() -> String {
50 "shared_main".to_string()
51}
52
53fn default_group_strategy() -> String {
54 "isolated".to_string()
55}
56
57fn default_thread_overrides_channel() -> bool {
58 true
59}
60
61fn default_pairing_approval_required() -> bool {
62 false
63}
64
65fn default_group_activation_mode() -> String {
66 "mention".to_string()
67}
68
69fn default_send_mode() -> String {
70 "blocks".to_string()
71}
72
73fn default_chunk_chars() -> usize {
74 1600
75}
76
77fn default_true() -> bool {
78 true
79}
80
81#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct GatewayConfig {
84 #[serde(default = "default_gateway_network_mode")]
85 pub network_mode: String,
86 pub host: String,
87 pub port: u16,
88 pub allowed_origins: Vec<String>,
89}
90
91fn default_gateway_network_mode() -> String {
92 "loopback".to_string()
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize)]
97pub struct DatabaseConfig {
98 pub url: String,
99 pub wal_mode: bool,
100 pub max_connections: u32,
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize)]
105pub struct ProvidersConfig {
106 pub default_provider: String,
107 pub fallback_chain: Vec<String>,
108 #[serde(default)]
109 pub control_plane_provider: Option<String>,
110 #[serde(default)]
111 pub control_plane_fallback_chain: Vec<String>,
112 pub anthropic: AnthropicConfig,
113 pub openai: OpenAiConfig,
114 pub openrouter: OpenRouterConfig,
115 pub ollama: OllamaConfig,
116 #[serde(default)]
117 pub gemini: GeminiConfig,
118}
119
120#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct AnthropicConfig {
123 pub model: String,
124 #[serde(default)]
125 pub api_key_env: Option<String>,
126 pub api_version: String,
127 pub strict_tools: bool,
128 pub streaming_tool_deltas: bool,
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize)]
133pub struct OpenAiConfig {
134 pub model: String,
135 #[serde(default = "default_codex_model")]
136 pub codex_model: String,
137 #[serde(default)]
138 pub api_key_env: Option<String>,
139 pub use_responses_api: bool,
140 pub strict_tools: bool,
141}
142
143fn default_codex_model() -> String {
144 "gpt-5.3-codex".to_string()
145}
146
147#[derive(Debug, Clone, Serialize, Deserialize)]
149pub struct OpenRouterConfig {
150 pub model: String,
151 #[serde(default)]
152 pub api_key_env: Option<String>,
153 pub route_strategy: String,
154}
155
156#[derive(Debug, Clone, Serialize, Deserialize)]
158pub struct OllamaConfig {
159 pub base_url: String,
160 pub model: String,
161}
162
163#[derive(Debug, Clone, Serialize, Deserialize)]
165pub struct GeminiConfig {
166 pub model: String,
167 #[serde(default)]
168 pub api_key_env: Option<String>,
169 #[serde(default)]
170 pub base_url: Option<String>,
171}
172
173impl Default for GeminiConfig {
174 fn default() -> Self {
175 Self {
176 model: "gemini-1.5-pro".to_string(),
177 api_key_env: Some("GEMINI_API_KEY".to_string()),
178 base_url: None,
179 }
180 }
181}
182
183#[derive(Debug, Clone, Serialize, Deserialize, Default)]
185pub struct ExternalBackendsConfig {
186 #[serde(default = "default_allowed_external_backends")]
187 pub allowed_backends: Vec<String>,
188 #[serde(default = "default_true")]
189 pub allow_local_cli_wrappers: bool,
190 #[serde(default)]
191 pub allow_cloud_agent_execution: bool,
192 #[serde(default = "default_external_backend_audit_log_path")]
193 pub audit_log_path: String,
194 #[serde(default = "default_external_backend_env_allowlist")]
195 pub command_env_allowlist: Vec<String>,
196}
197
198fn default_allowed_external_backends() -> Vec<String> {
199 vec!["agent_browser_cli".to_string()]
200}
201
202fn default_external_backend_audit_log_path() -> String {
203 ".claw/control/external-backends-audit.jsonl".to_string()
204}
205
206fn default_external_backend_env_allowlist() -> Vec<String> {
207 vec![
208 "PATH".to_string(),
209 "HOME".to_string(),
210 "USER".to_string(),
211 "USERNAME".to_string(),
212 "TMPDIR".to_string(),
213 "TMP".to_string(),
214 "TEMP".to_string(),
215 "LANG".to_string(),
216 "LC_ALL".to_string(),
217 "SSL_CERT_FILE".to_string(),
218 "SSL_CERT_DIR".to_string(),
219 "XDG_RUNTIME_DIR".to_string(),
220 "XDG_CACHE_HOME".to_string(),
221 "XDG_CONFIG_HOME".to_string(),
222 ]
223}
224
225#[derive(Debug, Clone, Serialize, Deserialize)]
227pub struct MemoryConfig {
228 pub core_memory_max_tokens: usize,
229 pub core_memory_max_entries: usize,
230 pub embedding_concurrency: usize,
231 pub dedupe_cosine_threshold: f32,
232 pub decay_half_life_days: f64,
233 pub ttl: MemoryTtlConfig,
234 pub consolidation: ConsolidationConfig,
235}
236
237#[derive(Debug, Clone, Serialize, Deserialize)]
239pub struct MemoryTtlConfig {
240 pub episodic_days: u64,
242 pub semantic_days: u64,
244 pub procedural_days: u64,
246}
247
248#[derive(Debug, Clone, Serialize, Deserialize)]
250pub struct ConsolidationConfig {
251 pub enabled: bool,
252 pub threshold_entries: usize,
253 pub schedule_interval_hours: u64,
254}
255
256#[derive(Debug, Clone, Serialize, Deserialize)]
258pub struct SchedulerConfig {
259 pub poll_interval_ms: u64,
260 pub lease_duration_secs: u64,
261 pub max_retries: u32,
262 pub base_retry_delay_secs: u64,
263 pub max_retry_delay_secs: u64,
264}
265
266#[derive(Debug, Clone, Serialize, Deserialize)]
268pub struct SecurityConfig {
269 pub require_auth: bool,
270 pub origin_validation: bool,
271 #[serde(default)]
272 pub control_api_token_env: Option<String>,
273 #[serde(default)]
274 pub trusted_proxy_token_env: Option<String>,
275 pub prompt_injection_defense: bool,
276 pub skill_signature_required: bool,
277 pub skill_verifying_key: Option<String>,
278}
279
280#[derive(Debug, Clone, Serialize, Deserialize)]
282pub struct SidecarConfig {
283 pub grpc_port: u16,
284 pub python_path: String,
285 #[serde(default)]
286 pub role: SidecarRole,
287 pub auto_start: bool,
288 pub restart_on_crash: bool,
289}
290
291impl SidecarConfig {
292 pub fn supports_compat_dispatch(&self) -> bool {
293 matches!(self.role, SidecarRole::Compatibility)
294 }
295
296 pub fn supports_experimental_lane(&self) -> bool {
297 matches!(self.role, SidecarRole::Experimental)
298 }
299
300 pub fn is_disabled(&self) -> bool {
301 matches!(self.role, SidecarRole::Disabled)
302 }
303}
304
305#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
307#[serde(rename_all = "snake_case")]
308pub enum SidecarRole {
309 #[default]
311 Compatibility,
312 Experimental,
314 Disabled,
316}
317
318#[derive(Debug, Clone, Serialize, Deserialize)]
320pub struct ObservabilityConfig {
321 pub langsmith_enabled: bool,
322 pub tracing_enabled: bool,
323 pub metrics_enabled: bool,
324 pub metrics_port: u16,
325}
326
327#[derive(Debug, Clone, Serialize, Deserialize, Default)]
329pub struct VoiceConfig {
330 #[serde(default)]
331 pub enabled: bool,
332 #[serde(default)]
333 pub wake_word: VoiceWakeWordConfig,
334 #[serde(default)]
335 pub stt: VoiceSttRuntimeConfig,
336 #[serde(default)]
337 pub tts: VoiceTtsRuntimeConfig,
338 #[serde(default)]
339 pub talk_mode: VoiceTalkModeRuntimeConfig,
340}
341
342#[derive(Debug, Clone, Serialize, Deserialize)]
344pub struct VoiceWakeWordConfig {
345 #[serde(default = "default_true")]
346 pub enabled: bool,
347 #[serde(default)]
348 pub model_path: Option<String>,
349 #[serde(default = "default_wake_word_sensitivity")]
350 pub sensitivity: f32,
351}
352
353impl Default for VoiceWakeWordConfig {
354 fn default() -> Self {
355 Self {
356 enabled: true,
357 model_path: None,
358 sensitivity: default_wake_word_sensitivity(),
359 }
360 }
361}
362
363fn default_wake_word_sensitivity() -> f32 {
364 0.7
365}
366
367#[derive(Debug, Clone, Serialize, Deserialize)]
369pub struct VoiceSttRuntimeConfig {
370 #[serde(default = "default_voice_stt_provider")]
371 pub provider: String,
372 #[serde(default = "default_voice_stt_model")]
373 pub model: String,
374 #[serde(default = "default_voice_language")]
375 pub language: String,
376 #[serde(default)]
377 pub api_base_url: Option<String>,
378 #[serde(default)]
379 pub api_key_env: Option<String>,
380 #[serde(default)]
381 pub prompt: Option<String>,
382 #[serde(default)]
383 pub transcribe_inbound_notes: bool,
384 #[serde(default)]
385 pub download_dir: Option<String>,
386 #[serde(default = "default_voice_max_audio_bytes")]
387 pub max_audio_bytes: usize,
388 #[serde(default = "default_voice_timeout_secs")]
389 pub timeout_secs: u64,
390}
391
392impl Default for VoiceSttRuntimeConfig {
393 fn default() -> Self {
394 Self {
395 provider: default_voice_stt_provider(),
396 model: default_voice_stt_model(),
397 language: default_voice_language(),
398 api_base_url: None,
399 api_key_env: None,
400 prompt: None,
401 transcribe_inbound_notes: false,
402 download_dir: None,
403 max_audio_bytes: default_voice_max_audio_bytes(),
404 timeout_secs: default_voice_timeout_secs(),
405 }
406 }
407}
408
409fn default_voice_stt_provider() -> String {
410 "openai".to_string()
411}
412
413fn default_voice_stt_model() -> String {
414 "whisper-1".to_string()
415}
416
417fn default_voice_language() -> String {
418 "auto".to_string()
419}
420
421fn default_voice_max_audio_bytes() -> usize {
422 25 * 1024 * 1024
423}
424
425fn default_voice_timeout_secs() -> u64 {
426 60
427}
428
429#[derive(Debug, Clone, Serialize, Deserialize)]
431pub struct VoiceTtsRuntimeConfig {
432 #[serde(default = "default_voice_tts_provider")]
433 pub provider: String,
434 #[serde(default = "default_voice_tts_model")]
435 pub model: String,
436 #[serde(default = "default_voice_tts_voice")]
437 pub voice: String,
438 #[serde(default)]
439 pub api_base_url: Option<String>,
440 #[serde(default)]
441 pub api_key_env: Option<String>,
442}
443
444impl Default for VoiceTtsRuntimeConfig {
445 fn default() -> Self {
446 Self {
447 provider: default_voice_tts_provider(),
448 model: default_voice_tts_model(),
449 voice: default_voice_tts_voice(),
450 api_base_url: None,
451 api_key_env: None,
452 }
453 }
454}
455
456fn default_voice_tts_provider() -> String {
457 "openai".to_string()
458}
459
460fn default_voice_tts_model() -> String {
461 "tts-1".to_string()
462}
463
464fn default_voice_tts_voice() -> String {
465 "alloy".to_string()
466}
467
468#[derive(Debug, Clone, Serialize, Deserialize)]
470pub struct VoiceTalkModeRuntimeConfig {
471 #[serde(default = "default_true")]
472 pub enabled: bool,
473 #[serde(default = "default_talk_timeout_secs")]
474 pub timeout_secs: u64,
475}
476
477impl Default for VoiceTalkModeRuntimeConfig {
478 fn default() -> Self {
479 Self {
480 enabled: true,
481 timeout_secs: default_talk_timeout_secs(),
482 }
483 }
484}
485
486fn default_talk_timeout_secs() -> u64 {
487 30
488}
489
490#[derive(Debug, Clone, Serialize, Deserialize)]
492pub struct ChannelRuntimeConfig {
493 #[serde(default = "default_true")]
494 pub health_monitor_enabled: bool,
495 #[serde(default = "default_channel_probe_interval_secs")]
496 pub probe_interval_secs: u64,
497 #[serde(default)]
498 pub auto_restart_on_failure: bool,
499 #[serde(default = "default_channel_failure_threshold")]
500 pub failure_threshold: usize,
501}
502
503impl Default for ChannelRuntimeConfig {
504 fn default() -> Self {
505 Self {
506 health_monitor_enabled: true,
507 probe_interval_secs: default_channel_probe_interval_secs(),
508 auto_restart_on_failure: false,
509 failure_threshold: default_channel_failure_threshold(),
510 }
511 }
512}
513
514fn default_channel_probe_interval_secs() -> u64 {
515 300
516}
517
518fn default_channel_failure_threshold() -> usize {
519 3
520}
521
522#[derive(Debug, Clone, Serialize, Deserialize)]
524pub struct ChannelsConfig {
525 #[serde(default)]
526 pub runtime: ChannelRuntimeConfig,
527 pub telegram: TelegramConfig,
528 pub discord: DiscordConfig,
529 pub slack: SlackConfig,
530 pub whatsapp: WhatsAppConfig,
531 pub teams: TeamsConfig,
532 pub mattermost: MattermostConfig,
533 pub google_chat: GoogleChatConfig,
534 pub google_meet: GoogleMeetConfig,
535 pub gmail_pubsub: GmailPubSubConfig,
536 pub signal: SignalConfig,
537 pub matrix: MatrixConfig,
538 pub x: XConfig,
539 pub twilio: TwilioConfig,
540 pub meta: MetaConfig,
541 pub imessage: IMessageConfig,
542 pub line: LineConfig,
543 pub viber: ViberConfig,
544 pub wechat: WeChatConfig,
545}
546
547#[derive(Debug, Clone, Serialize, Deserialize)]
549pub struct MattermostConfig {
550 pub enabled: bool,
551 pub server_url: String,
553 pub bot_token: String,
555 pub webhook_path: String,
557 pub webhook_token: Option<String>,
559 pub bot_username: Option<String>,
561 pub allowlist: Vec<String>,
563 pub allowed_channels: Vec<String>,
565 pub rate_limit_requests_per_second: u32,
567}
568
569#[derive(Debug, Clone, Serialize, Deserialize)]
571pub struct SkillsConfig {
572 pub registry_url: Option<String>,
574 pub auto_update: Option<bool>,
576 pub skill_dirs: Option<Vec<String>>,
578}
579
580#[derive(Debug, Clone, Serialize, Deserialize)]
582pub struct TelegramConfig {
583 pub enabled: bool,
584 pub token: String,
585 #[serde(default)]
586 pub api_base_url: Option<String>,
587 pub mode: TelegramMode,
588 pub webhook_url: Option<String>,
589 pub webhook_port: Option<u16>,
590 pub allowed_users: Vec<String>,
591 pub rate_limit_per_second: u32,
592}
593
594#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
596#[serde(rename_all = "snake_case")]
597pub enum TelegramMode {
598 Polling,
599 Webhook,
600}
601
602#[derive(Debug, Clone, Serialize, Deserialize)]
604pub struct DiscordConfig {
605 pub enabled: bool,
606 pub token: String,
607 pub application_id: String,
608 #[serde(default)]
609 pub interaction_public_key: Option<String>,
610 #[serde(default)]
611 pub api_base_url: Option<String>,
612 #[serde(default)]
613 pub attachment_download_dir: Option<String>,
614 pub rate_limit_requests_per_second: u32,
615 pub allowed_guilds: Vec<String>,
616 pub allowed_channels: Vec<String>,
617 pub dm_enabled: bool,
618}
619
620#[derive(Debug, Clone, Serialize, Deserialize)]
622pub struct SlackConfig {
623 pub enabled: bool,
624 pub token: String,
625 #[serde(default)]
626 pub api_base_url: Option<String>,
627 pub app_token: Option<String>,
628 pub signing_secret: Option<String>,
629 pub mode: SlackMode,
630 pub socket_mode: bool,
631 pub rate_limit_requests_per_second: u32,
632 pub allowed_workspaces: Vec<String>,
633 pub app_home_enabled: bool,
634}
635
636#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
638#[serde(rename_all = "snake_case")]
639pub enum SlackMode {
640 Http,
641 SocketMode,
642}
643
644#[derive(Debug, Clone, Serialize, Deserialize)]
646pub struct WhatsAppConfig {
647 pub enabled: bool,
648 pub session_path: String,
650 pub pairing_mode: bool,
652 pub allowlist: Vec<String>,
654 pub webhook_url: Option<String>,
656 pub bridge_path: String,
658 pub rate_limit_per_second: u32,
660 pub max_reconnect_attempts: u32,
662 pub reconnect_delay_secs: u64,
664}
665
666#[derive(Debug, Clone, Serialize, Deserialize)]
668pub struct TeamsConfig {
669 pub enabled: bool,
670 pub app_id: String,
672 pub app_password: String,
674 pub tenant_id: Option<String>,
676 pub webhook_path: String,
678 pub allowlist: Vec<String>,
680 pub group_policy: TeamsGroupPolicy,
682 pub rate_limit_requests_per_second: u32,
684 pub adaptive_cards_enabled: bool,
686 #[serde(default)]
688 pub attachment_download_dir: Option<String>,
689}
690
691#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
693#[serde(rename_all = "snake_case")]
694pub enum TeamsGroupPolicy {
695 Mention,
697 Open,
699}
700
701#[derive(Debug, Clone, Serialize, Deserialize)]
703pub struct GoogleChatConfig {
704 pub enabled: bool,
705 pub service_account_key: String,
707 pub project_id: String,
709 pub webhook_url: Option<String>,
711 pub pubsub_subscription: Option<String>,
713 pub allowlist: Vec<String>,
715 pub allowed_spaces: Vec<String>,
717 pub rate_limit_requests_per_second: u32,
719 pub cards_enabled: bool,
721 #[serde(default)]
723 pub attachment_download_dir: Option<String>,
724 pub response_mode: GoogleChatResponseMode,
726}
727
728#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
730#[serde(rename_all = "snake_case")]
731pub enum GoogleChatResponseMode {
732 Mention,
734 Open,
736 SlashCommands,
738}
739
740#[derive(Debug, Clone, Serialize, Deserialize)]
742pub struct GoogleMeetConfig {
743 pub enabled: bool,
744 pub service_account_key_path: String,
746 pub delegated_user_email: String,
748 pub webhook_path: String,
750 pub allowed_spaces: Vec<String>,
752 pub rate_limit_requests_per_second: u32,
754 pub api_base_url: Option<String>,
756 pub oauth_token_url: Option<String>,
758 pub additional_scopes: Vec<String>,
760 pub hydrate_transcript_events: bool,
762}
763
764#[derive(Debug, Clone, Serialize, Deserialize)]
766pub struct GmailPubSubConfig {
767 pub enabled: bool,
768 pub project_id: String,
770 pub subscription_name: String,
772 pub topic_name: Option<String>,
774 pub service_account_key_path: String,
776 pub user_email: String,
778 pub label_filters: Vec<String>,
780 pub query_filter: Option<String>,
782 pub auto_reply: bool,
784 pub max_history_fetch: u32,
786 pub rate_limit_requests_per_second: u32,
788 pub api_base_url: Option<String>,
790 pub oauth_token_url: Option<String>,
792}
793
794#[derive(Debug, Clone, Serialize, Deserialize)]
796pub struct SignalConfig {
797 pub enabled: bool,
798 pub phone_number: String,
800 pub data_dir: PathBuf,
802 pub allowlist: Vec<String>,
804 pub allowed_groups: Vec<String>,
806 pub signal_cli_path: Option<PathBuf>,
808 pub use_libsignal: bool,
810 pub rate_limit_per_minute: u32,
812 pub require_allowlist: bool,
814}
815
816#[derive(Debug, Clone, Serialize, Deserialize)]
818pub struct MatrixConfig {
819 pub enabled: bool,
820 pub homeserver: String,
822 pub user_id: String,
824 pub access_token: Option<String>,
826 pub password: Option<String>,
828 pub device_id: Option<String>,
830 pub data_dir: String,
832 pub allowlist: Vec<String>,
834 pub room_allowlist: Vec<String>,
836 pub auto_join_rooms: bool,
838 pub enable_encryption: bool,
840 pub rate_limit_per_second: u32,
842}
843
844#[derive(Debug, Clone, Serialize, Deserialize)]
846pub struct XConfig {
847 pub enabled: bool,
848 pub bearer_token: String,
850 pub api_key: String,
852 pub api_secret: String,
854 pub access_token: String,
856 pub access_token_secret: String,
858 pub bot_user_id: String,
860 pub allowlist: Vec<String>,
862 pub respond_to_mentions: bool,
864 pub respond_to_dms: bool,
866 pub max_tweet_length: usize,
868 pub mention_poll_interval_secs: u64,
870 pub dm_poll_interval_secs: u64,
872 pub rate_limit_per_minute: u32,
874}
875
876#[derive(Debug, Clone, Serialize, Deserialize)]
878pub struct TwilioConfig {
879 pub enabled: bool,
880 pub account_sid: String,
882 pub auth_token: String,
884 pub phone_number: String,
886 pub webhook_url: Option<String>,
888 pub allowlist: Vec<String>,
890 pub max_message_length: usize,
892 pub enable_mms: bool,
894 pub rate_limit_per_second: u32,
896}
897
898#[derive(Debug, Clone, Serialize, Deserialize)]
900pub struct LineConfig {
901 pub enabled: bool,
902 pub channel_access_token: String,
904 pub channel_secret: String,
906 pub webhook_path: String,
908 pub allowlist: Vec<String>,
910 pub rate_limit_per_second: u32,
912 pub enable_rich_menu: bool,
914 pub enable_quick_replies: bool,
916}
917
918#[derive(Debug, Clone, Serialize, Deserialize)]
920pub struct ViberConfig {
921 pub enabled: bool,
922 pub auth_token: String,
924 pub webhook_url: Option<String>,
926 pub webhook_path: String,
928 pub allowlist: Vec<String>,
930 pub rate_limit_per_minute: u32,
932 pub allow_broadcast: bool,
934 pub welcome_message: Option<String>,
936 pub enable_keyboards: bool,
938}
939
940#[derive(Debug, Clone, Serialize, Deserialize)]
942pub struct WeChatConfig {
943 pub enabled: bool,
944 pub app_type: String,
946 pub corp_id: String,
949 pub corp_secret: String,
951 pub agent_id: String,
953 pub app_id: String,
956 pub app_secret: String,
958 pub token: String,
960 pub encoding_aes_key: Option<String>,
962 pub webhook_path: String,
964 pub allowlist: Vec<String>,
966 pub rate_limit_per_second: u32,
968 pub enable_encryption: bool,
970}
971
972#[derive(Debug, Clone, Serialize, Deserialize)]
974pub struct MetaConfig {
975 pub enabled: bool,
976 pub app_id: String,
978 pub app_secret: String,
980 pub page_access_token: String,
982 pub verify_token: String,
984 pub webhook_path: String,
986 pub page_id: String,
988 pub instagram_account_id: Option<String>,
990 pub allowlist: Vec<String>,
992 pub respond_to_messenger: bool,
994 pub respond_to_instagram: bool,
996 pub show_typing_indicator: bool,
998 pub rate_limit_per_second: u32,
1000}
1001
1002#[derive(Debug, Clone, Serialize, Deserialize)]
1004pub struct IMessageConfig {
1005 pub enabled: bool,
1006 pub bridge_mode: IMessageBridgeMode,
1008 pub allowlist: Vec<String>,
1010 pub enable_tapbacks: bool,
1012 pub enable_typing_indicator: bool,
1014}
1015
1016#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
1018#[serde(rename_all = "snake_case", tag = "mode")]
1019pub enum IMessageBridgeMode {
1020 #[serde(rename = "bluebubbles", alias = "blue_bubbles")]
1022 BlueBubbles {
1023 server_url: String,
1025 password: String,
1027 },
1028 #[default]
1030 #[serde(rename = "macos_direct", alias = "mac_o_s_direct")]
1031 MacOSDirect,
1032 PrivateApi,
1034}
1035
1036impl AppConfig {
1037 pub fn load() -> Result<Self, config::ConfigError> {
1042 let config = config::Config::builder()
1043 .add_source(config::File::with_name("config/default").required(false))
1044 .add_source(
1045 config::Environment::with_prefix("OPENRUSTCLAW")
1046 .separator("__")
1047 .try_parsing(true),
1048 )
1049 .build()?;
1050
1051 config.try_deserialize()
1052 }
1053
1054 pub fn load_from(path: &str) -> Result<Self, config::ConfigError> {
1056 let config = config::Config::builder()
1057 .add_source(config::File::with_name(path))
1058 .add_source(
1059 config::Environment::with_prefix("OPENRUSTCLAW")
1060 .separator("__")
1061 .try_parsing(true),
1062 )
1063 .build()?;
1064
1065 config.try_deserialize()
1066 }
1067}
1068
1069impl Default for AppConfig {
1070 fn default() -> Self {
1071 Self {
1072 gateway: GatewayConfig {
1073 network_mode: default_gateway_network_mode(),
1074 host: "127.0.0.1".to_string(),
1075 port: 18789,
1076 allowed_origins: vec![
1077 "http://localhost:3000".to_string(),
1078 "http://127.0.0.1:3000".to_string(),
1079 ],
1080 },
1081 database: DatabaseConfig {
1082 url: "sqlite://data/openrustclaw.db".to_string(),
1083 wal_mode: true,
1084 max_connections: 10,
1085 },
1086 providers: ProvidersConfig {
1087 default_provider: "anthropic".to_string(),
1088 fallback_chain: vec![
1089 "anthropic".to_string(),
1090 "openai".to_string(),
1091 "openrouter".to_string(),
1092 ],
1093 control_plane_provider: Some("openrouter".to_string()),
1094 control_plane_fallback_chain: vec!["ollama".to_string(), "anthropic".to_string()],
1095 anthropic: AnthropicConfig {
1096 model: "claude-sonnet-4-20250514".to_string(),
1097 api_key_env: None,
1098 api_version: "2023-06-01".to_string(),
1099 strict_tools: true,
1100 streaming_tool_deltas: true,
1101 },
1102 openai: OpenAiConfig {
1103 model: "gpt-4o".to_string(),
1104 codex_model: default_codex_model(),
1105 api_key_env: None,
1106 use_responses_api: true,
1107 strict_tools: true,
1108 },
1109 openrouter: OpenRouterConfig {
1110 model: "anthropic/claude-sonnet-4".to_string(),
1111 api_key_env: None,
1112 route_strategy: "quality".to_string(),
1113 },
1114 ollama: OllamaConfig {
1115 base_url: "http://localhost:11434".to_string(),
1116 model: "llama3.1".to_string(),
1117 },
1118 gemini: GeminiConfig::default(),
1119 },
1120 external_backends: ExternalBackendsConfig {
1121 allowed_backends: default_allowed_external_backends(),
1122 allow_local_cli_wrappers: true,
1123 allow_cloud_agent_execution: false,
1124 audit_log_path: default_external_backend_audit_log_path(),
1125 command_env_allowlist: default_external_backend_env_allowlist(),
1126 },
1127 memory: MemoryConfig {
1128 core_memory_max_tokens: 500,
1129 core_memory_max_entries: 20,
1130 embedding_concurrency: 4,
1131 dedupe_cosine_threshold: 0.92,
1132 decay_half_life_days: 30.0,
1133 ttl: MemoryTtlConfig {
1134 episodic_days: 90,
1135 semantic_days: 0,
1136 procedural_days: 0,
1137 },
1138 consolidation: ConsolidationConfig {
1139 enabled: true,
1140 threshold_entries: 1000,
1141 schedule_interval_hours: 24,
1142 },
1143 },
1144 session_routing: SessionRoutingConfig {
1145 direct_strategy: "shared_main".to_string(),
1146 group_strategy: "isolated".to_string(),
1147 thread_overrides_channel: true,
1148 pairing_approval_required: false,
1149 default_group_activation: "mention".to_string(),
1150 default_send_mode: "blocks".to_string(),
1151 default_chunk_chars: 1600,
1152 default_chunk_delay_ms: 250,
1153 },
1154 scheduler: SchedulerConfig {
1155 poll_interval_ms: 1000,
1156 lease_duration_secs: 60,
1157 max_retries: 3,
1158 base_retry_delay_secs: 5,
1159 max_retry_delay_secs: 300,
1160 },
1161 security: SecurityConfig {
1162 require_auth: true,
1163 origin_validation: true,
1164 control_api_token_env: None,
1165 trusted_proxy_token_env: None,
1166 prompt_injection_defense: true,
1167 skill_signature_required: false,
1168 skill_verifying_key: None,
1169 },
1170 sidecar: SidecarConfig {
1171 grpc_port: 50051,
1172 python_path: "python3".to_string(),
1173 role: SidecarRole::Compatibility,
1174 auto_start: false,
1175 restart_on_crash: true,
1176 },
1177 observability: ObservabilityConfig {
1178 langsmith_enabled: false,
1179 tracing_enabled: true,
1180 metrics_enabled: true,
1181 metrics_port: 9090,
1182 },
1183 voice: VoiceConfig::default(),
1184 channels: ChannelsConfig {
1185 runtime: ChannelRuntimeConfig::default(),
1186 telegram: TelegramConfig {
1187 enabled: false,
1188 token: String::new(),
1189 api_base_url: None,
1190 mode: TelegramMode::Polling,
1191 webhook_url: None,
1192 webhook_port: None,
1193 allowed_users: Vec::new(),
1194 rate_limit_per_second: 30,
1195 },
1196 discord: DiscordConfig {
1197 enabled: false,
1198 token: String::new(),
1199 application_id: String::new(),
1200 interaction_public_key: None,
1201 api_base_url: None,
1202 attachment_download_dir: None,
1203 rate_limit_requests_per_second: 5,
1204 allowed_guilds: Vec::new(),
1205 allowed_channels: Vec::new(),
1206 dm_enabled: true,
1207 },
1208 slack: SlackConfig {
1209 enabled: false,
1210 token: String::new(),
1211 api_base_url: None,
1212 app_token: None,
1213 signing_secret: None,
1214 mode: SlackMode::SocketMode,
1215 socket_mode: true,
1216 rate_limit_requests_per_second: 10,
1217 allowed_workspaces: Vec::new(),
1218 app_home_enabled: true,
1219 },
1220 whatsapp: WhatsAppConfig {
1221 enabled: false,
1222 session_path: "./data/whatsapp-session".to_string(),
1223 pairing_mode: false,
1224 allowlist: Vec::new(),
1225 webhook_url: None,
1226 bridge_path: "./crates/channels/baileys-bridge/index.js".to_string(),
1227 rate_limit_per_second: 10,
1228 max_reconnect_attempts: 10,
1229 reconnect_delay_secs: 5,
1230 },
1231 teams: TeamsConfig {
1232 enabled: false,
1233 app_id: String::new(),
1234 app_password: String::new(),
1235 tenant_id: None,
1236 webhook_path: "/webhooks/teams".to_string(),
1237 allowlist: Vec::new(),
1238 group_policy: TeamsGroupPolicy::Mention,
1239 rate_limit_requests_per_second: 10,
1240 adaptive_cards_enabled: true,
1241 attachment_download_dir: None,
1242 },
1243 mattermost: MattermostConfig {
1244 enabled: false,
1245 server_url: String::new(),
1246 bot_token: String::new(),
1247 webhook_path: "/webhooks/mattermost".to_string(),
1248 webhook_token: None,
1249 bot_username: None,
1250 allowlist: Vec::new(),
1251 allowed_channels: Vec::new(),
1252 rate_limit_requests_per_second: 10,
1253 },
1254 google_chat: GoogleChatConfig {
1255 enabled: false,
1256 service_account_key: String::new(),
1257 project_id: String::new(),
1258 webhook_url: None,
1259 pubsub_subscription: None,
1260 allowlist: Vec::new(),
1261 allowed_spaces: Vec::new(),
1262 rate_limit_requests_per_second: 10,
1263 cards_enabled: true,
1264 attachment_download_dir: None,
1265 response_mode: GoogleChatResponseMode::Mention,
1266 },
1267 google_meet: GoogleMeetConfig {
1268 enabled: false,
1269 service_account_key_path: String::new(),
1270 delegated_user_email: String::new(),
1271 webhook_path: "/webhooks/google-meet/events".to_string(),
1272 allowed_spaces: Vec::new(),
1273 rate_limit_requests_per_second: 5,
1274 api_base_url: None,
1275 oauth_token_url: None,
1276 additional_scopes: Vec::new(),
1277 hydrate_transcript_events: true,
1278 },
1279 gmail_pubsub: GmailPubSubConfig {
1280 enabled: false,
1281 project_id: String::new(),
1282 subscription_name: String::new(),
1283 topic_name: None,
1284 service_account_key_path: String::new(),
1285 user_email: String::new(),
1286 label_filters: vec!["INBOX".to_string(), "UNREAD".to_string()],
1287 query_filter: None,
1288 auto_reply: false,
1289 max_history_fetch: 100,
1290 rate_limit_requests_per_second: 10,
1291 api_base_url: None,
1292 oauth_token_url: None,
1293 },
1294 signal: SignalConfig {
1295 enabled: false,
1296 phone_number: String::new(),
1297 data_dir: std::path::PathBuf::from("./data/signal"),
1298 allowlist: Vec::new(),
1299 allowed_groups: Vec::new(),
1300 signal_cli_path: None,
1301 use_libsignal: false,
1302 rate_limit_per_minute: 20,
1303 require_allowlist: true,
1304 },
1305 matrix: MatrixConfig {
1306 enabled: false,
1307 homeserver: String::new(),
1308 user_id: String::new(),
1309 access_token: None,
1310 password: None,
1311 device_id: None,
1312 data_dir: "./data/matrix".to_string(),
1313 allowlist: Vec::new(),
1314 room_allowlist: Vec::new(),
1315 auto_join_rooms: true,
1316 enable_encryption: true,
1317 rate_limit_per_second: 10,
1318 },
1319 x: XConfig {
1320 enabled: false,
1321 bearer_token: String::new(),
1322 api_key: String::new(),
1323 api_secret: String::new(),
1324 access_token: String::new(),
1325 access_token_secret: String::new(),
1326 bot_user_id: String::new(),
1327 allowlist: Vec::new(),
1328 respond_to_mentions: true,
1329 respond_to_dms: true,
1330 max_tweet_length: 280,
1331 mention_poll_interval_secs: 60,
1332 dm_poll_interval_secs: 120,
1333 rate_limit_per_minute: 10,
1334 },
1335 twilio: TwilioConfig {
1336 enabled: false,
1337 account_sid: String::new(),
1338 auth_token: String::new(),
1339 phone_number: String::new(),
1340 webhook_url: None,
1341 allowlist: Vec::new(),
1342 max_message_length: 1600,
1343 enable_mms: true,
1344 rate_limit_per_second: 10,
1345 },
1346 meta: MetaConfig {
1347 enabled: false,
1348 app_id: String::new(),
1349 app_secret: String::new(),
1350 page_access_token: String::new(),
1351 verify_token: String::new(),
1352 webhook_path: "/webhooks/meta".to_string(),
1353 page_id: String::new(),
1354 instagram_account_id: None,
1355 allowlist: Vec::new(),
1356 respond_to_messenger: true,
1357 respond_to_instagram: true,
1358 show_typing_indicator: true,
1359 rate_limit_per_second: 10,
1360 },
1361 imessage: IMessageConfig {
1362 enabled: false,
1363 bridge_mode: IMessageBridgeMode::MacOSDirect,
1364 allowlist: Vec::new(),
1365 enable_tapbacks: true,
1366 enable_typing_indicator: false,
1367 },
1368 line: LineConfig {
1369 enabled: false,
1370 channel_access_token: String::new(),
1371 channel_secret: String::new(),
1372 webhook_path: "/webhook/line".to_string(),
1373 allowlist: Vec::new(),
1374 rate_limit_per_second: 1000,
1375 enable_rich_menu: true,
1376 enable_quick_replies: true,
1377 },
1378 viber: ViberConfig {
1379 enabled: false,
1380 auth_token: String::new(),
1381 webhook_url: None,
1382 webhook_path: "/webhook/viber".to_string(),
1383 allowlist: Vec::new(),
1384 rate_limit_per_minute: 300,
1385 allow_broadcast: false,
1386 welcome_message: None,
1387 enable_keyboards: true,
1388 },
1389 wechat: WeChatConfig {
1390 enabled: false,
1391 app_type: "work".to_string(),
1392 corp_id: String::new(),
1393 corp_secret: String::new(),
1394 agent_id: String::new(),
1395 app_id: String::new(),
1396 app_secret: String::new(),
1397 token: String::new(),
1398 encoding_aes_key: None,
1399 webhook_path: "/webhook/wechat".to_string(),
1400 allowlist: Vec::new(),
1401 rate_limit_per_second: 20,
1402 enable_encryption: false,
1403 },
1404 },
1405 skills: None,
1406 }
1407 }
1408}