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