@@ -127,16 +127,27 @@ impl<'de> serde::Deserialize<'de> for ApiType {
127127}
128128
129129/// Configuration for a single LLM provider.
130- #[ derive( Debug , Clone ) ]
130+ #[ derive( Clone ) ]
131131pub struct ProviderConfig {
132132 pub api_type : ApiType ,
133133 pub base_url : String ,
134134 pub api_key : String ,
135135 pub name : Option < String > ,
136136}
137137
138+ impl std:: fmt:: Debug for ProviderConfig {
139+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
140+ f. debug_struct ( "ProviderConfig" )
141+ . field ( "api_type" , & self . api_type )
142+ . field ( "base_url" , & self . base_url )
143+ . field ( "api_key" , & "[REDACTED]" )
144+ . field ( "name" , & self . name )
145+ . finish ( )
146+ }
147+ }
148+
138149/// LLM provider credentials (instance-level).
139- #[ derive( Debug , Clone ) ]
150+ #[ derive( Clone ) ]
140151pub struct LlmConfig {
141152 pub anthropic_key : Option < String > ,
142153 pub openai_key : Option < String > ,
@@ -159,6 +170,32 @@ pub struct LlmConfig {
159170 pub providers : HashMap < String , ProviderConfig > ,
160171}
161172
173+ impl std:: fmt:: Debug for LlmConfig {
174+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
175+ f. debug_struct ( "LlmConfig" )
176+ . field ( "anthropic_key" , & self . anthropic_key . as_ref ( ) . map ( |_| "[REDACTED]" ) )
177+ . field ( "openai_key" , & self . openai_key . as_ref ( ) . map ( |_| "[REDACTED]" ) )
178+ . field ( "openrouter_key" , & self . openrouter_key . as_ref ( ) . map ( |_| "[REDACTED]" ) )
179+ . field ( "zhipu_key" , & self . zhipu_key . as_ref ( ) . map ( |_| "[REDACTED]" ) )
180+ . field ( "groq_key" , & self . groq_key . as_ref ( ) . map ( |_| "[REDACTED]" ) )
181+ . field ( "together_key" , & self . together_key . as_ref ( ) . map ( |_| "[REDACTED]" ) )
182+ . field ( "fireworks_key" , & self . fireworks_key . as_ref ( ) . map ( |_| "[REDACTED]" ) )
183+ . field ( "deepseek_key" , & self . deepseek_key . as_ref ( ) . map ( |_| "[REDACTED]" ) )
184+ . field ( "xai_key" , & self . xai_key . as_ref ( ) . map ( |_| "[REDACTED]" ) )
185+ . field ( "mistral_key" , & self . mistral_key . as_ref ( ) . map ( |_| "[REDACTED]" ) )
186+ . field ( "gemini_key" , & self . gemini_key . as_ref ( ) . map ( |_| "[REDACTED]" ) )
187+ . field ( "ollama_key" , & self . ollama_key . as_ref ( ) . map ( |_| "[REDACTED]" ) )
188+ . field ( "ollama_base_url" , & self . ollama_base_url )
189+ . field ( "opencode_zen_key" , & self . opencode_zen_key . as_ref ( ) . map ( |_| "[REDACTED]" ) )
190+ . field ( "nvidia_key" , & self . nvidia_key . as_ref ( ) . map ( |_| "[REDACTED]" ) )
191+ . field ( "minimax_key" , & self . minimax_key . as_ref ( ) . map ( |_| "[REDACTED]" ) )
192+ . field ( "moonshot_key" , & self . moonshot_key . as_ref ( ) . map ( |_| "[REDACTED]" ) )
193+ . field ( "zai_coding_plan_key" , & self . zai_coding_plan_key . as_ref ( ) . map ( |_| "[REDACTED]" ) )
194+ . field ( "providers" , & self . providers )
195+ . finish ( )
196+ }
197+ }
198+
162199impl LlmConfig {
163200 /// Check if any provider configuration is set.
164201 pub fn has_any_key ( & self ) -> bool {
@@ -199,7 +236,7 @@ pub(crate) const GEMINI_PROVIDER_BASE_URL: &str =
199236 "https://generativelanguage.googleapis.com/v1beta/openai" ;
200237
201238/// Defaults inherited by all agents. Individual agents can override any field.
202- #[ derive( Debug , Clone ) ]
239+ #[ derive( Clone ) ]
203240pub struct DefaultsConfig {
204241 pub routing : RoutingConfig ,
205242 pub max_concurrent_branches : usize ,
@@ -223,6 +260,31 @@ pub struct DefaultsConfig {
223260 pub worker_log_mode : crate :: settings:: WorkerLogMode ,
224261}
225262
263+ impl std:: fmt:: Debug for DefaultsConfig {
264+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
265+ f. debug_struct ( "DefaultsConfig" )
266+ . field ( "routing" , & self . routing )
267+ . field ( "max_concurrent_branches" , & self . max_concurrent_branches )
268+ . field ( "max_concurrent_workers" , & self . max_concurrent_workers )
269+ . field ( "max_turns" , & self . max_turns )
270+ . field ( "branch_max_turns" , & self . branch_max_turns )
271+ . field ( "context_window" , & self . context_window )
272+ . field ( "compaction" , & self . compaction )
273+ . field ( "memory_persistence" , & self . memory_persistence )
274+ . field ( "coalesce" , & self . coalesce )
275+ . field ( "ingestion" , & self . ingestion )
276+ . field ( "cortex" , & self . cortex )
277+ . field ( "browser" , & self . browser )
278+ . field ( "mcp" , & self . mcp )
279+ . field ( "brave_search_key" , & self . brave_search_key . as_ref ( ) . map ( |_| "[REDACTED]" ) )
280+ . field ( "history_backfill_count" , & self . history_backfill_count )
281+ . field ( "cron" , & self . cron )
282+ . field ( "opencode" , & self . opencode )
283+ . field ( "worker_log_mode" , & self . worker_log_mode )
284+ . finish ( )
285+ }
286+ }
287+
226288/// MCP server configuration.
227289#[ derive( Debug , Clone , PartialEq , Eq ) ]
228290pub struct McpServerConfig {
@@ -786,7 +848,7 @@ pub struct MessagingConfig {
786848 pub twitch : Option < TwitchConfig > ,
787849}
788850
789- #[ derive( Debug , Clone ) ]
851+ #[ derive( Clone ) ]
790852pub struct DiscordConfig {
791853 pub enabled : bool ,
792854 pub token : String ,
@@ -796,6 +858,17 @@ pub struct DiscordConfig {
796858 pub allow_bot_messages : bool ,
797859}
798860
861+ impl std:: fmt:: Debug for DiscordConfig {
862+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
863+ f. debug_struct ( "DiscordConfig" )
864+ . field ( "enabled" , & self . enabled )
865+ . field ( "token" , & "[REDACTED]" )
866+ . field ( "dm_allowed_users" , & self . dm_allowed_users )
867+ . field ( "allow_bot_messages" , & self . allow_bot_messages )
868+ . finish ( )
869+ }
870+ }
871+
799872/// A single slash command definition for the Slack adapter.
800873///
801874/// Maps a Slack slash command (e.g. `/ask`) to a target agent.
@@ -810,7 +883,7 @@ pub struct SlackCommandConfig {
810883 pub description : Option < String > ,
811884}
812885
813- #[ derive( Debug , Clone ) ]
886+ #[ derive( Clone ) ]
814887pub struct SlackConfig {
815888 pub enabled : bool ,
816889 pub bot_token : String ,
@@ -821,6 +894,18 @@ pub struct SlackConfig {
821894 pub commands : Vec < SlackCommandConfig > ,
822895}
823896
897+ impl std:: fmt:: Debug for SlackConfig {
898+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
899+ f. debug_struct ( "SlackConfig" )
900+ . field ( "enabled" , & self . enabled )
901+ . field ( "bot_token" , & "[REDACTED]" )
902+ . field ( "app_token" , & "[REDACTED]" )
903+ . field ( "dm_allowed_users" , & self . dm_allowed_users )
904+ . field ( "commands" , & self . commands )
905+ . finish ( )
906+ }
907+ }
908+
824909/// Hot-reloadable Discord permission filters.
825910///
826911/// Derived from bindings + discord config. Shared with the Discord adapter
@@ -953,14 +1038,24 @@ impl DiscordPermissions {
9531038 }
9541039}
9551040
956- #[ derive( Debug , Clone ) ]
1041+ #[ derive( Clone ) ]
9571042pub struct TelegramConfig {
9581043 pub enabled : bool ,
9591044 pub token : String ,
9601045 /// User IDs allowed to DM the bot. If empty, DMs are ignored entirely.
9611046 pub dm_allowed_users : Vec < String > ,
9621047}
9631048
1049+ impl std:: fmt:: Debug for TelegramConfig {
1050+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
1051+ f. debug_struct ( "TelegramConfig" )
1052+ . field ( "enabled" , & self . enabled )
1053+ . field ( "token" , & "[REDACTED]" )
1054+ . field ( "dm_allowed_users" , & self . dm_allowed_users )
1055+ . finish ( )
1056+ }
1057+ }
1058+
9641059/// Hot-reloadable Telegram permission filters.
9651060///
9661061/// Shared with the Telegram adapter via `Arc<ArcSwap<..>>` for hot-reloading.
@@ -1015,7 +1110,7 @@ impl TelegramPermissions {
10151110 }
10161111}
10171112
1018- #[ derive( Debug , Clone ) ]
1113+ #[ derive( Clone ) ]
10191114pub struct TwitchConfig {
10201115 pub enabled : bool ,
10211116 pub username : String ,
@@ -1026,6 +1121,18 @@ pub struct TwitchConfig {
10261121 pub trigger_prefix : Option < String > ,
10271122}
10281123
1124+ impl std:: fmt:: Debug for TwitchConfig {
1125+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
1126+ f. debug_struct ( "TwitchConfig" )
1127+ . field ( "enabled" , & self . enabled )
1128+ . field ( "username" , & self . username )
1129+ . field ( "oauth_token" , & "[REDACTED]" )
1130+ . field ( "channels" , & self . channels )
1131+ . field ( "trigger_prefix" , & self . trigger_prefix )
1132+ . finish ( )
1133+ }
1134+ }
1135+
10291136/// Hot-reloadable Twitch permission filters.
10301137///
10311138/// Shared with the Twitch adapter via `Arc<ArcSwap<..>>` for hot-reloading.
@@ -2159,18 +2266,23 @@ impl Config {
21592266 . providers
21602267 . into_iter ( )
21612268 . map ( |( provider_id, config) | {
2162- (
2269+ let api_key = resolve_env_value ( & config. api_key ) . ok_or_else ( || {
2270+ anyhow:: anyhow!(
2271+ "failed to resolve API key for provider '{}'" ,
2272+ provider_id
2273+ )
2274+ } ) ?;
2275+ Ok ( (
21632276 provider_id. to_lowercase ( ) ,
21642277 ProviderConfig {
21652278 api_type : config. api_type ,
21662279 base_url : config. base_url ,
2167- api_key : resolve_env_value ( & config. api_key )
2168- . expect ( "Failed to resolve API key for provider" ) ,
2280+ api_key,
21692281 name : config. name ,
21702282 } ,
2171- )
2283+ ) )
21722284 } )
2173- . collect ( ) ,
2285+ . collect :: < anyhow :: Result < _ > > ( ) ? ,
21742286 } ;
21752287
21762288 if let Some ( anthropic_key) = llm. anthropic_key . clone ( ) {
0 commit comments