use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AppConfig {
pub gateway: GatewayConfig,
pub database: DatabaseConfig,
pub providers: ProvidersConfig,
#[serde(default)]
pub external_backends: ExternalBackendsConfig,
pub memory: MemoryConfig,
pub session_routing: SessionRoutingConfig,
pub scheduler: SchedulerConfig,
pub security: SecurityConfig,
pub sidecar: SidecarConfig,
pub observability: ObservabilityConfig,
pub channels: ChannelsConfig,
pub voice: VoiceConfig,
pub skills: Option<SkillsConfig>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionRoutingConfig {
#[serde(default = "default_direct_strategy")]
pub direct_strategy: String,
#[serde(default = "default_group_strategy")]
pub group_strategy: String,
#[serde(default = "default_thread_overrides_channel")]
pub thread_overrides_channel: bool,
#[serde(default = "default_pairing_approval_required")]
pub pairing_approval_required: bool,
#[serde(default = "default_group_activation_mode")]
pub default_group_activation: String,
#[serde(default = "default_send_mode")]
pub default_send_mode: String,
#[serde(default = "default_chunk_chars")]
pub default_chunk_chars: usize,
#[serde(default)]
pub default_chunk_delay_ms: u64,
}
fn default_direct_strategy() -> String {
"shared_main".to_string()
}
fn default_group_strategy() -> String {
"isolated".to_string()
}
fn default_thread_overrides_channel() -> bool {
true
}
fn default_pairing_approval_required() -> bool {
false
}
fn default_group_activation_mode() -> String {
"mention".to_string()
}
fn default_send_mode() -> String {
"blocks".to_string()
}
fn default_chunk_chars() -> usize {
1600
}
fn default_true() -> bool {
true
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GatewayConfig {
#[serde(default = "default_gateway_network_mode")]
pub network_mode: String,
pub host: String,
pub port: u16,
pub allowed_origins: Vec<String>,
}
fn default_gateway_network_mode() -> String {
"loopback".to_string()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DatabaseConfig {
pub url: String,
pub wal_mode: bool,
pub max_connections: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProvidersConfig {
pub default_provider: String,
pub fallback_chain: Vec<String>,
#[serde(default)]
pub control_plane_provider: Option<String>,
#[serde(default)]
pub control_plane_fallback_chain: Vec<String>,
pub anthropic: AnthropicConfig,
pub openai: OpenAiConfig,
pub openrouter: OpenRouterConfig,
pub ollama: OllamaConfig,
#[serde(default)]
pub gemini: GeminiConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AnthropicConfig {
pub model: String,
#[serde(default)]
pub api_key_env: Option<String>,
pub api_version: String,
pub strict_tools: bool,
pub streaming_tool_deltas: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OpenAiConfig {
pub model: String,
#[serde(default = "default_codex_model")]
pub codex_model: String,
#[serde(default)]
pub api_key_env: Option<String>,
pub use_responses_api: bool,
pub strict_tools: bool,
}
fn default_codex_model() -> String {
"gpt-5.3-codex".to_string()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OpenRouterConfig {
pub model: String,
#[serde(default)]
pub api_key_env: Option<String>,
pub route_strategy: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OllamaConfig {
pub base_url: String,
pub model: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GeminiConfig {
pub model: String,
#[serde(default)]
pub api_key_env: Option<String>,
#[serde(default)]
pub base_url: Option<String>,
}
impl Default for GeminiConfig {
fn default() -> Self {
Self {
model: "gemini-1.5-pro".to_string(),
api_key_env: Some("GEMINI_API_KEY".to_string()),
base_url: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ExternalBackendsConfig {
#[serde(default = "default_allowed_external_backends")]
pub allowed_backends: Vec<String>,
#[serde(default = "default_true")]
pub allow_local_cli_wrappers: bool,
#[serde(default)]
pub allow_cloud_agent_execution: bool,
#[serde(default = "default_external_backend_audit_log_path")]
pub audit_log_path: String,
#[serde(default = "default_external_backend_env_allowlist")]
pub command_env_allowlist: Vec<String>,
}
fn default_allowed_external_backends() -> Vec<String> {
vec![
"agent_browser_cli".to_string(),
"claude_code".to_string(),
"codex".to_string(),
"gemini_cli".to_string(),
]
}
fn default_external_backend_audit_log_path() -> String {
".claw/control/external-backends-audit.jsonl".to_string()
}
fn default_external_backend_env_allowlist() -> Vec<String> {
vec![
"PATH".to_string(),
"HOME".to_string(),
"USER".to_string(),
"USERNAME".to_string(),
"TMPDIR".to_string(),
"TMP".to_string(),
"TEMP".to_string(),
"LANG".to_string(),
"LC_ALL".to_string(),
"SSL_CERT_FILE".to_string(),
"SSL_CERT_DIR".to_string(),
"XDG_RUNTIME_DIR".to_string(),
"XDG_CACHE_HOME".to_string(),
"XDG_CONFIG_HOME".to_string(),
]
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MemoryConfig {
pub core_memory_max_tokens: usize,
pub core_memory_max_entries: usize,
pub embedding_concurrency: usize,
pub dedupe_cosine_threshold: f32,
pub decay_half_life_days: f64,
pub ttl: MemoryTtlConfig,
pub consolidation: ConsolidationConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MemoryTtlConfig {
pub episodic_days: u64,
pub semantic_days: u64,
pub procedural_days: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConsolidationConfig {
pub enabled: bool,
pub threshold_entries: usize,
pub schedule_interval_hours: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SchedulerConfig {
pub poll_interval_ms: u64,
pub lease_duration_secs: u64,
pub max_retries: u32,
pub base_retry_delay_secs: u64,
pub max_retry_delay_secs: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SecurityConfig {
pub require_auth: bool,
pub origin_validation: bool,
#[serde(default)]
pub control_api_token_env: Option<String>,
#[serde(default)]
pub trusted_proxy_token_env: Option<String>,
pub prompt_injection_defense: bool,
pub skill_signature_required: bool,
pub skill_verifying_key: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SidecarConfig {
pub grpc_port: u16,
pub python_path: String,
#[serde(default)]
pub role: SidecarRole,
pub auto_start: bool,
pub restart_on_crash: bool,
}
impl SidecarConfig {
pub fn supports_compat_dispatch(&self) -> bool {
matches!(self.role, SidecarRole::Compatibility)
}
pub fn supports_experimental_lane(&self) -> bool {
matches!(self.role, SidecarRole::Experimental)
}
pub fn is_disabled(&self) -> bool {
matches!(self.role, SidecarRole::Disabled)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum SidecarRole {
#[default]
Compatibility,
Experimental,
Disabled,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ObservabilityConfig {
pub langsmith_enabled: bool,
pub tracing_enabled: bool,
pub metrics_enabled: bool,
pub metrics_port: u16,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct VoiceConfig {
#[serde(default)]
pub enabled: bool,
#[serde(default)]
pub wake_word: VoiceWakeWordConfig,
#[serde(default)]
pub stt: VoiceSttRuntimeConfig,
#[serde(default)]
pub tts: VoiceTtsRuntimeConfig,
#[serde(default)]
pub talk_mode: VoiceTalkModeRuntimeConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VoiceWakeWordConfig {
#[serde(default = "default_true")]
pub enabled: bool,
#[serde(default)]
pub model_path: Option<String>,
#[serde(default = "default_wake_word_sensitivity")]
pub sensitivity: f32,
}
impl Default for VoiceWakeWordConfig {
fn default() -> Self {
Self {
enabled: true,
model_path: None,
sensitivity: default_wake_word_sensitivity(),
}
}
}
fn default_wake_word_sensitivity() -> f32 {
0.7
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VoiceSttRuntimeConfig {
#[serde(default = "default_voice_stt_provider")]
pub provider: String,
#[serde(default = "default_voice_stt_model")]
pub model: String,
#[serde(default = "default_voice_language")]
pub language: String,
#[serde(default)]
pub api_base_url: Option<String>,
#[serde(default)]
pub api_key_env: Option<String>,
#[serde(default)]
pub prompt: Option<String>,
#[serde(default)]
pub transcribe_inbound_notes: bool,
#[serde(default)]
pub download_dir: Option<String>,
#[serde(default = "default_voice_max_audio_bytes")]
pub max_audio_bytes: usize,
#[serde(default = "default_voice_timeout_secs")]
pub timeout_secs: u64,
}
impl Default for VoiceSttRuntimeConfig {
fn default() -> Self {
Self {
provider: default_voice_stt_provider(),
model: default_voice_stt_model(),
language: default_voice_language(),
api_base_url: None,
api_key_env: None,
prompt: None,
transcribe_inbound_notes: false,
download_dir: None,
max_audio_bytes: default_voice_max_audio_bytes(),
timeout_secs: default_voice_timeout_secs(),
}
}
}
fn default_voice_stt_provider() -> String {
"openai".to_string()
}
fn default_voice_stt_model() -> String {
"whisper-1".to_string()
}
fn default_voice_language() -> String {
"auto".to_string()
}
fn default_voice_max_audio_bytes() -> usize {
25 * 1024 * 1024
}
fn default_voice_timeout_secs() -> u64 {
60
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VoiceTtsRuntimeConfig {
#[serde(default = "default_voice_tts_provider")]
pub provider: String,
#[serde(default = "default_voice_tts_model")]
pub model: String,
#[serde(default = "default_voice_tts_voice")]
pub voice: String,
#[serde(default)]
pub api_base_url: Option<String>,
#[serde(default)]
pub api_key_env: Option<String>,
}
impl Default for VoiceTtsRuntimeConfig {
fn default() -> Self {
Self {
provider: default_voice_tts_provider(),
model: default_voice_tts_model(),
voice: default_voice_tts_voice(),
api_base_url: None,
api_key_env: None,
}
}
}
fn default_voice_tts_provider() -> String {
"openai".to_string()
}
fn default_voice_tts_model() -> String {
"tts-1".to_string()
}
fn default_voice_tts_voice() -> String {
"alloy".to_string()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VoiceTalkModeRuntimeConfig {
#[serde(default = "default_true")]
pub enabled: bool,
#[serde(default = "default_talk_timeout_secs")]
pub timeout_secs: u64,
}
impl Default for VoiceTalkModeRuntimeConfig {
fn default() -> Self {
Self {
enabled: true,
timeout_secs: default_talk_timeout_secs(),
}
}
}
fn default_talk_timeout_secs() -> u64 {
30
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChannelRuntimeConfig {
#[serde(default = "default_true")]
pub health_monitor_enabled: bool,
#[serde(default = "default_channel_probe_interval_secs")]
pub probe_interval_secs: u64,
#[serde(default)]
pub auto_restart_on_failure: bool,
#[serde(default = "default_channel_failure_threshold")]
pub failure_threshold: usize,
}
impl Default for ChannelRuntimeConfig {
fn default() -> Self {
Self {
health_monitor_enabled: true,
probe_interval_secs: default_channel_probe_interval_secs(),
auto_restart_on_failure: false,
failure_threshold: default_channel_failure_threshold(),
}
}
}
fn default_channel_probe_interval_secs() -> u64 {
300
}
fn default_channel_failure_threshold() -> usize {
3
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChannelsConfig {
#[serde(default)]
pub runtime: ChannelRuntimeConfig,
pub telegram: TelegramConfig,
pub discord: DiscordConfig,
pub slack: SlackConfig,
pub whatsapp: WhatsAppConfig,
pub teams: TeamsConfig,
pub mattermost: MattermostConfig,
pub google_chat: GoogleChatConfig,
pub google_meet: GoogleMeetConfig,
pub gmail_pubsub: GmailPubSubConfig,
pub signal: SignalConfig,
pub matrix: MatrixConfig,
pub x: XConfig,
pub twilio: TwilioConfig,
pub meta: MetaConfig,
pub imessage: IMessageConfig,
pub line: LineConfig,
pub viber: ViberConfig,
pub wechat: WeChatConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MattermostConfig {
pub enabled: bool,
pub server_url: String,
pub bot_token: String,
pub webhook_path: String,
pub webhook_token: Option<String>,
pub bot_username: Option<String>,
pub allowlist: Vec<String>,
pub allowed_channels: Vec<String>,
pub rate_limit_requests_per_second: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SkillsConfig {
pub registry_url: Option<String>,
pub auto_update: Option<bool>,
pub skill_dirs: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TelegramConfig {
pub enabled: bool,
pub token: String,
#[serde(default)]
pub api_base_url: Option<String>,
pub mode: TelegramMode,
pub webhook_url: Option<String>,
pub webhook_port: Option<u16>,
pub allowed_users: Vec<String>,
pub rate_limit_per_second: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TelegramMode {
Polling,
Webhook,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DiscordConfig {
pub enabled: bool,
pub token: String,
pub application_id: String,
#[serde(default)]
pub interaction_public_key: Option<String>,
#[serde(default)]
pub api_base_url: Option<String>,
#[serde(default)]
pub attachment_download_dir: Option<String>,
pub rate_limit_requests_per_second: u32,
pub allowed_guilds: Vec<String>,
pub allowed_channels: Vec<String>,
pub dm_enabled: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SlackConfig {
pub enabled: bool,
pub token: String,
#[serde(default)]
pub api_base_url: Option<String>,
pub app_token: Option<String>,
pub signing_secret: Option<String>,
pub mode: SlackMode,
pub socket_mode: bool,
pub rate_limit_requests_per_second: u32,
pub allowed_workspaces: Vec<String>,
pub app_home_enabled: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SlackMode {
Http,
SocketMode,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WhatsAppConfig {
pub enabled: bool,
pub session_path: String,
pub pairing_mode: bool,
pub allowlist: Vec<String>,
pub webhook_url: Option<String>,
pub bridge_path: String,
pub rate_limit_per_second: u32,
pub max_reconnect_attempts: u32,
pub reconnect_delay_secs: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TeamsConfig {
pub enabled: bool,
pub app_id: String,
pub app_password: String,
pub tenant_id: Option<String>,
pub webhook_path: String,
pub allowlist: Vec<String>,
pub group_policy: TeamsGroupPolicy,
pub rate_limit_requests_per_second: u32,
pub adaptive_cards_enabled: bool,
#[serde(default)]
pub attachment_download_dir: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TeamsGroupPolicy {
Mention,
Open,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GoogleChatConfig {
pub enabled: bool,
pub service_account_key: String,
pub project_id: String,
pub webhook_url: Option<String>,
pub pubsub_subscription: Option<String>,
pub allowlist: Vec<String>,
pub allowed_spaces: Vec<String>,
pub rate_limit_requests_per_second: u32,
pub cards_enabled: bool,
#[serde(default)]
pub attachment_download_dir: Option<String>,
pub response_mode: GoogleChatResponseMode,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum GoogleChatResponseMode {
Mention,
Open,
SlashCommands,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GoogleMeetConfig {
pub enabled: bool,
pub service_account_key_path: String,
pub delegated_user_email: String,
pub webhook_path: String,
pub allowed_spaces: Vec<String>,
pub rate_limit_requests_per_second: u32,
pub api_base_url: Option<String>,
pub oauth_token_url: Option<String>,
pub additional_scopes: Vec<String>,
pub hydrate_transcript_events: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GmailPubSubConfig {
pub enabled: bool,
pub project_id: String,
pub subscription_name: String,
pub topic_name: Option<String>,
pub service_account_key_path: String,
pub user_email: String,
pub label_filters: Vec<String>,
pub query_filter: Option<String>,
pub auto_reply: bool,
pub max_history_fetch: u32,
pub rate_limit_requests_per_second: u32,
pub api_base_url: Option<String>,
pub oauth_token_url: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SignalConfig {
pub enabled: bool,
pub phone_number: String,
pub data_dir: PathBuf,
pub allowlist: Vec<String>,
pub allowed_groups: Vec<String>,
pub signal_cli_path: Option<PathBuf>,
pub use_libsignal: bool,
pub rate_limit_per_minute: u32,
pub require_allowlist: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MatrixConfig {
pub enabled: bool,
pub homeserver: String,
pub user_id: String,
pub access_token: Option<String>,
pub password: Option<String>,
pub device_id: Option<String>,
pub data_dir: String,
pub allowlist: Vec<String>,
pub room_allowlist: Vec<String>,
pub auto_join_rooms: bool,
pub enable_encryption: bool,
pub rate_limit_per_second: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct XConfig {
pub enabled: bool,
pub bearer_token: String,
pub api_key: String,
pub api_secret: String,
pub access_token: String,
pub access_token_secret: String,
pub bot_user_id: String,
pub allowlist: Vec<String>,
pub respond_to_mentions: bool,
pub respond_to_dms: bool,
pub max_tweet_length: usize,
pub mention_poll_interval_secs: u64,
pub dm_poll_interval_secs: u64,
pub rate_limit_per_minute: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TwilioConfig {
pub enabled: bool,
pub account_sid: String,
pub auth_token: String,
pub phone_number: String,
pub webhook_url: Option<String>,
pub allowlist: Vec<String>,
pub max_message_length: usize,
pub enable_mms: bool,
pub rate_limit_per_second: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LineConfig {
pub enabled: bool,
pub channel_access_token: String,
pub channel_secret: String,
pub webhook_path: String,
pub allowlist: Vec<String>,
pub rate_limit_per_second: u32,
pub enable_rich_menu: bool,
pub enable_quick_replies: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ViberConfig {
pub enabled: bool,
pub auth_token: String,
pub webhook_url: Option<String>,
pub webhook_path: String,
pub allowlist: Vec<String>,
pub rate_limit_per_minute: u32,
pub allow_broadcast: bool,
pub welcome_message: Option<String>,
pub enable_keyboards: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WeChatConfig {
pub enabled: bool,
pub app_type: String,
pub corp_id: String,
pub corp_secret: String,
pub agent_id: String,
pub app_id: String,
pub app_secret: String,
pub token: String,
pub encoding_aes_key: Option<String>,
pub webhook_path: String,
pub allowlist: Vec<String>,
pub rate_limit_per_second: u32,
pub enable_encryption: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MetaConfig {
pub enabled: bool,
pub app_id: String,
pub app_secret: String,
pub page_access_token: String,
pub verify_token: String,
pub webhook_path: String,
pub page_id: String,
pub instagram_account_id: Option<String>,
pub allowlist: Vec<String>,
pub respond_to_messenger: bool,
pub respond_to_instagram: bool,
pub show_typing_indicator: bool,
pub rate_limit_per_second: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IMessageConfig {
pub enabled: bool,
pub bridge_mode: IMessageBridgeMode,
pub allowlist: Vec<String>,
pub enable_tapbacks: bool,
pub enable_typing_indicator: bool,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", tag = "mode")]
pub enum IMessageBridgeMode {
#[serde(rename = "bluebubbles", alias = "blue_bubbles")]
BlueBubbles {
server_url: String,
password: String,
},
#[default]
#[serde(rename = "macos_direct", alias = "mac_o_s_direct")]
MacOSDirect,
PrivateApi,
}
impl AppConfig {
pub fn load() -> Result<Self, config::ConfigError> {
let config = config::Config::builder()
.add_source(config::File::with_name("config/default").required(false))
.add_source(
config::Environment::with_prefix("OPENRUSTCLAW")
.separator("__")
.try_parsing(true),
)
.build()?;
config.try_deserialize()
}
pub fn load_from(path: &str) -> Result<Self, config::ConfigError> {
let config = config::Config::builder()
.add_source(config::File::with_name(path))
.add_source(
config::Environment::with_prefix("OPENRUSTCLAW")
.separator("__")
.try_parsing(true),
)
.build()?;
config.try_deserialize()
}
}
impl Default for AppConfig {
fn default() -> Self {
Self {
gateway: GatewayConfig {
network_mode: default_gateway_network_mode(),
host: "127.0.0.1".to_string(),
port: 18789,
allowed_origins: vec![
"http://localhost:3000".to_string(),
"http://127.0.0.1:3000".to_string(),
],
},
database: DatabaseConfig {
url: "sqlite://data/openrustclaw.db".to_string(),
wal_mode: true,
max_connections: 10,
},
providers: ProvidersConfig {
default_provider: "anthropic".to_string(),
fallback_chain: vec![
"anthropic".to_string(),
"openai".to_string(),
"openrouter".to_string(),
],
control_plane_provider: Some("openrouter".to_string()),
control_plane_fallback_chain: vec!["ollama".to_string(), "anthropic".to_string()],
anthropic: AnthropicConfig {
model: "claude-sonnet-4-20250514".to_string(),
api_key_env: None,
api_version: "2023-06-01".to_string(),
strict_tools: true,
streaming_tool_deltas: true,
},
openai: OpenAiConfig {
model: "gpt-4o".to_string(),
codex_model: default_codex_model(),
api_key_env: None,
use_responses_api: true,
strict_tools: true,
},
openrouter: OpenRouterConfig {
model: "anthropic/claude-sonnet-4".to_string(),
api_key_env: None,
route_strategy: "quality".to_string(),
},
ollama: OllamaConfig {
base_url: "http://localhost:11434".to_string(),
model: "llama3.1".to_string(),
},
gemini: GeminiConfig::default(),
},
external_backends: ExternalBackendsConfig {
allowed_backends: default_allowed_external_backends(),
allow_local_cli_wrappers: true,
allow_cloud_agent_execution: false,
audit_log_path: default_external_backend_audit_log_path(),
command_env_allowlist: default_external_backend_env_allowlist(),
},
memory: MemoryConfig {
core_memory_max_tokens: 500,
core_memory_max_entries: 20,
embedding_concurrency: 4,
dedupe_cosine_threshold: 0.92,
decay_half_life_days: 30.0,
ttl: MemoryTtlConfig {
episodic_days: 90,
semantic_days: 0,
procedural_days: 0,
},
consolidation: ConsolidationConfig {
enabled: true,
threshold_entries: 1000,
schedule_interval_hours: 24,
},
},
session_routing: SessionRoutingConfig {
direct_strategy: "shared_main".to_string(),
group_strategy: "isolated".to_string(),
thread_overrides_channel: true,
pairing_approval_required: false,
default_group_activation: "mention".to_string(),
default_send_mode: "blocks".to_string(),
default_chunk_chars: 1600,
default_chunk_delay_ms: 250,
},
scheduler: SchedulerConfig {
poll_interval_ms: 1000,
lease_duration_secs: 60,
max_retries: 3,
base_retry_delay_secs: 5,
max_retry_delay_secs: 300,
},
security: SecurityConfig {
require_auth: true,
origin_validation: true,
control_api_token_env: None,
trusted_proxy_token_env: None,
prompt_injection_defense: true,
skill_signature_required: false,
skill_verifying_key: None,
},
sidecar: SidecarConfig {
grpc_port: 50051,
python_path: "python3".to_string(),
role: SidecarRole::Compatibility,
auto_start: false,
restart_on_crash: true,
},
observability: ObservabilityConfig {
langsmith_enabled: false,
tracing_enabled: true,
metrics_enabled: true,
metrics_port: 9090,
},
voice: VoiceConfig::default(),
channels: ChannelsConfig {
runtime: ChannelRuntimeConfig::default(),
telegram: TelegramConfig {
enabled: false,
token: String::new(),
api_base_url: None,
mode: TelegramMode::Polling,
webhook_url: None,
webhook_port: None,
allowed_users: Vec::new(),
rate_limit_per_second: 30,
},
discord: DiscordConfig {
enabled: false,
token: String::new(),
application_id: String::new(),
interaction_public_key: None,
api_base_url: None,
attachment_download_dir: None,
rate_limit_requests_per_second: 5,
allowed_guilds: Vec::new(),
allowed_channels: Vec::new(),
dm_enabled: true,
},
slack: SlackConfig {
enabled: false,
token: String::new(),
api_base_url: None,
app_token: None,
signing_secret: None,
mode: SlackMode::SocketMode,
socket_mode: true,
rate_limit_requests_per_second: 10,
allowed_workspaces: Vec::new(),
app_home_enabled: true,
},
whatsapp: WhatsAppConfig {
enabled: false,
session_path: "./data/whatsapp-session".to_string(),
pairing_mode: false,
allowlist: Vec::new(),
webhook_url: None,
bridge_path: "./crates/channels/baileys-bridge/index.js".to_string(),
rate_limit_per_second: 10,
max_reconnect_attempts: 10,
reconnect_delay_secs: 5,
},
teams: TeamsConfig {
enabled: false,
app_id: String::new(),
app_password: String::new(),
tenant_id: None,
webhook_path: "/webhooks/teams".to_string(),
allowlist: Vec::new(),
group_policy: TeamsGroupPolicy::Mention,
rate_limit_requests_per_second: 10,
adaptive_cards_enabled: true,
attachment_download_dir: None,
},
mattermost: MattermostConfig {
enabled: false,
server_url: String::new(),
bot_token: String::new(),
webhook_path: "/webhooks/mattermost".to_string(),
webhook_token: None,
bot_username: None,
allowlist: Vec::new(),
allowed_channels: Vec::new(),
rate_limit_requests_per_second: 10,
},
google_chat: GoogleChatConfig {
enabled: false,
service_account_key: String::new(),
project_id: String::new(),
webhook_url: None,
pubsub_subscription: None,
allowlist: Vec::new(),
allowed_spaces: Vec::new(),
rate_limit_requests_per_second: 10,
cards_enabled: true,
attachment_download_dir: None,
response_mode: GoogleChatResponseMode::Mention,
},
google_meet: GoogleMeetConfig {
enabled: false,
service_account_key_path: String::new(),
delegated_user_email: String::new(),
webhook_path: "/webhooks/google-meet/events".to_string(),
allowed_spaces: Vec::new(),
rate_limit_requests_per_second: 5,
api_base_url: None,
oauth_token_url: None,
additional_scopes: Vec::new(),
hydrate_transcript_events: true,
},
gmail_pubsub: GmailPubSubConfig {
enabled: false,
project_id: String::new(),
subscription_name: String::new(),
topic_name: None,
service_account_key_path: String::new(),
user_email: String::new(),
label_filters: vec!["INBOX".to_string(), "UNREAD".to_string()],
query_filter: None,
auto_reply: false,
max_history_fetch: 100,
rate_limit_requests_per_second: 10,
api_base_url: None,
oauth_token_url: None,
},
signal: SignalConfig {
enabled: false,
phone_number: String::new(),
data_dir: std::path::PathBuf::from("./data/signal"),
allowlist: Vec::new(),
allowed_groups: Vec::new(),
signal_cli_path: None,
use_libsignal: false,
rate_limit_per_minute: 20,
require_allowlist: true,
},
matrix: MatrixConfig {
enabled: false,
homeserver: String::new(),
user_id: String::new(),
access_token: None,
password: None,
device_id: None,
data_dir: "./data/matrix".to_string(),
allowlist: Vec::new(),
room_allowlist: Vec::new(),
auto_join_rooms: true,
enable_encryption: true,
rate_limit_per_second: 10,
},
x: XConfig {
enabled: false,
bearer_token: String::new(),
api_key: String::new(),
api_secret: String::new(),
access_token: String::new(),
access_token_secret: String::new(),
bot_user_id: String::new(),
allowlist: Vec::new(),
respond_to_mentions: true,
respond_to_dms: true,
max_tweet_length: 280,
mention_poll_interval_secs: 60,
dm_poll_interval_secs: 120,
rate_limit_per_minute: 10,
},
twilio: TwilioConfig {
enabled: false,
account_sid: String::new(),
auth_token: String::new(),
phone_number: String::new(),
webhook_url: None,
allowlist: Vec::new(),
max_message_length: 1600,
enable_mms: true,
rate_limit_per_second: 10,
},
meta: MetaConfig {
enabled: false,
app_id: String::new(),
app_secret: String::new(),
page_access_token: String::new(),
verify_token: String::new(),
webhook_path: "/webhooks/meta".to_string(),
page_id: String::new(),
instagram_account_id: None,
allowlist: Vec::new(),
respond_to_messenger: true,
respond_to_instagram: true,
show_typing_indicator: true,
rate_limit_per_second: 10,
},
imessage: IMessageConfig {
enabled: false,
bridge_mode: IMessageBridgeMode::MacOSDirect,
allowlist: Vec::new(),
enable_tapbacks: true,
enable_typing_indicator: false,
},
line: LineConfig {
enabled: false,
channel_access_token: String::new(),
channel_secret: String::new(),
webhook_path: "/webhook/line".to_string(),
allowlist: Vec::new(),
rate_limit_per_second: 1000,
enable_rich_menu: true,
enable_quick_replies: true,
},
viber: ViberConfig {
enabled: false,
auth_token: String::new(),
webhook_url: None,
webhook_path: "/webhook/viber".to_string(),
allowlist: Vec::new(),
rate_limit_per_minute: 300,
allow_broadcast: false,
welcome_message: None,
enable_keyboards: true,
},
wechat: WeChatConfig {
enabled: false,
app_type: "work".to_string(),
corp_id: String::new(),
corp_secret: String::new(),
agent_id: String::new(),
app_id: String::new(),
app_secret: String::new(),
token: String::new(),
encoding_aes_key: None,
webhook_path: "/webhook/wechat".to_string(),
allowlist: Vec::new(),
rate_limit_per_second: 20,
enable_encryption: false,
},
},
skills: None,
}
}
}