1pub use zeph_config::{
11 AcpAuthMethod, AcpConfig, AcpLspConfig, AcpTransport, AdditionalDir, AdditionalDirError,
12 AgentConfig, CandleConfig, CandleInlineConfig, CascadeClassifierMode, CascadeConfig,
13 ClassifiersConfig, CompressionConfig, CompressionStrategy, Config, ConfigError, ContextFormat,
14 CostConfig, DaemonConfig, DebugConfig, DetectorMode, DiscordConfig, DocumentConfig, DumpFormat,
15 ExperimentConfig, ExperimentSchedule, FocusConfig, GatewayConfig, GenerationParams, GonkaNode,
16 GraphConfig, HookAction, HookDef, HookMatcher, IndexConfig, LearningConfig, LlmConfig,
17 LlmRoutingStrategy, LogRotation, LoggingConfig, MAX_TOKENS_CAP, McpConfig, McpOAuthConfig,
18 McpServerConfig, McpTrustLevel, MemoryConfig, MemoryScope, NoteLinkingConfig,
19 OAuthTokenStorage, OrchestrationConfig, PermissionMode, ProviderEntry, ProviderKind,
20 ProviderName, PruningStrategy, RateLimitConfig, ResolvedSecrets, RetrievalConfig, RouterConfig,
21 RouterStrategyConfig, ScheduledTaskConfig, ScheduledTaskKind, SchedulerConfig,
22 SchedulerDaemonConfig, SecurityConfig, SemanticConfig, SessionsConfig, SidequestConfig,
23 SkillFilter, SkillPromptMode, SkillsConfig, SlackConfig, StoreRoutingConfig,
24 StoreRoutingStrategy, SttConfig, SubAgentConfig, SubAgentLifecycleHooks, SubagentHooks,
25 TaskSupervisorConfig, TelegramConfig, TimeoutConfig, ToolDiscoveryConfig,
26 ToolDiscoveryStrategyConfig, ToolFilterConfig, ToolPolicy, TraceConfig, TrustConfig, TuiConfig,
27 VaultConfig, VectorBackend,
28};
29
30pub use zeph_config::{
31 AutoDreamConfig, CategoryConfig, ContextStrategy, DigestConfig, MagicDocsConfig,
32 MicrocompactConfig, PersonaConfig, TrajectoryConfig, TreeConfig,
33};
34pub use zeph_config::{DiagnosticSeverity, DiagnosticsConfig, HoverConfig, LspConfig};
35pub use zeph_config::{QualityConfig, TriggerPolicy};
36pub use zeph_config::{TelemetryBackend, TelemetryConfig};
37
38pub use zeph_config::{
39 ContentIsolationConfig, CustomPiiPattern, ExfiltrationGuardConfig, MemoryWriteValidationConfig,
40 PiiFilterConfig, QuarantineConfig,
41};
42pub use zeph_config::{GuardrailAction, GuardrailConfig, GuardrailFailStrategy};
43
44pub use zeph_config::A2aServerConfig;
45pub use zeph_config::ChannelSkillsConfig;
46pub use zeph_config::{FileChangedConfig, HooksConfig};
47
48pub use zeph_config::{
49 DEFAULT_DEBUG_DIR, DEFAULT_LOG_FILE, DEFAULT_SKILLS_DIR, DEFAULT_SQLITE_PATH,
50 default_debug_dir, default_log_file_path, default_skills_dir, default_sqlite_path,
51 is_legacy_default_debug_dir, is_legacy_default_log_file, is_legacy_default_skills_path,
52 is_legacy_default_sqlite_path,
53};
54
55pub use zeph_config::providers::{default_stt_language, default_stt_provider, validate_pool};
56
57pub mod migrate {
58 pub use zeph_config::migrate::*;
59}
60
61use crate::vault::{Secret, VaultProvider};
62
63pub trait SecretResolver {
68 fn resolve_secrets(
74 &mut self,
75 vault: &dyn VaultProvider,
76 ) -> impl std::future::Future<Output = Result<(), ConfigError>> + Send;
77}
78
79fn log_gonka_credential_status(has_key: bool, has_address: bool) {
80 match (has_key, has_address) {
81 (true, true) => tracing::info!("gonka wallet credentials resolved from vault"),
82 (true, false) => tracing::warn!(
83 "ZEPH_GONKA_PRIVATE_KEY is set but ZEPH_GONKA_ADDRESS is missing from vault"
84 ),
85 (false, true) => tracing::warn!(
86 "ZEPH_GONKA_ADDRESS is set but ZEPH_GONKA_PRIVATE_KEY is missing from vault"
87 ),
88 (false, false) => {}
89 }
90}
91
92impl SecretResolver for Config {
96 async fn resolve_secrets(&mut self, vault: &dyn VaultProvider) -> Result<(), ConfigError> {
97 if let Some(val) = vault.get_secret("ZEPH_CLAUDE_API_KEY").await? {
98 self.secrets.claude_api_key = Some(Secret::new(val));
99 }
100 if let Some(val) = vault.get_secret("ZEPH_OPENAI_API_KEY").await? {
101 self.secrets.openai_api_key = Some(Secret::new(val));
102 }
103 if let Some(val) = vault.get_secret("ZEPH_GEMINI_API_KEY").await? {
104 self.secrets.gemini_api_key = Some(Secret::new(val));
105 }
106 if let Some(val) = vault.get_secret("ZEPH_GONKA_PRIVATE_KEY").await? {
107 self.secrets.gonka_private_key = Some(Secret::new(val));
108 }
109 if let Some(val) = vault.get_secret("ZEPH_GONKA_ADDRESS").await? {
110 self.secrets.gonka_address = Some(Secret::new(val));
111 }
112 if let Some(val) = vault.get_secret("ZEPH_COCOON_ACCESS_HASH").await? {
113 self.secrets.cocoon_access_hash = Some(Secret::new(val));
114 }
115 log_gonka_credential_status(
116 self.secrets.gonka_private_key.is_some(),
117 self.secrets.gonka_address.is_some(),
118 );
119 if let Some(val) = vault.get_secret("ZEPH_TELEGRAM_TOKEN").await?
120 && let Some(tg) = self.telegram.as_mut()
121 {
122 tg.token = Some(val);
123 }
124 if let Some(val) = vault.get_secret("ZEPH_A2A_AUTH_TOKEN").await? {
125 self.a2a.auth_token = Some(val);
126 }
127 for entry in &self.llm.providers {
128 if entry.provider_type == crate::config::ProviderKind::Compatible
129 && let Some(ref name) = entry.name
130 {
131 let env_key = format!("ZEPH_COMPATIBLE_{}_API_KEY", name.to_uppercase());
132 if let Some(val) = vault.get_secret(&env_key).await? {
133 self.secrets
134 .compatible_api_keys
135 .insert(name.clone(), Secret::new(val));
136 }
137 }
138 }
139 if let Some(val) = vault.get_secret("ZEPH_HF_TOKEN").await? {
140 self.classifiers.hf_token = Some(val.clone());
141 if let Some(candle) = self.llm.candle.as_mut() {
142 candle.hf_token = Some(val);
143 }
144 }
145 if let Some(val) = vault.get_secret("ZEPH_GATEWAY_TOKEN").await? {
146 self.gateway.auth_token = Some(val);
147 }
148 if let Some(val) = vault.get_secret("ZEPH_DATABASE_URL").await? {
149 self.memory.database_url = Some(val);
150 }
151 if let Some(val) = vault.get_secret("ZEPH_QDRANT_API_KEY").await? {
152 self.memory.qdrant_api_key = Some(Secret::new(val));
153 }
154 if let Some(val) = vault.get_secret("ZEPH_DISCORD_TOKEN").await?
155 && let Some(dc) = self.discord.as_mut()
156 {
157 dc.token = Some(val);
158 }
159 if let Some(val) = vault.get_secret("ZEPH_DISCORD_APP_ID").await?
160 && let Some(dc) = self.discord.as_mut()
161 {
162 dc.application_id = Some(val);
163 }
164 if let Some(val) = vault.get_secret("ZEPH_SLACK_BOT_TOKEN").await?
165 && let Some(sl) = self.slack.as_mut()
166 {
167 sl.bot_token = Some(val);
168 }
169 if let Some(val) = vault.get_secret("ZEPH_SLACK_SIGNING_SECRET").await?
170 && let Some(sl) = self.slack.as_mut()
171 {
172 sl.signing_secret = Some(val);
173 }
174 for key in vault.list_keys() {
175 if let Some(custom_name) = key.strip_prefix("ZEPH_SECRET_")
176 && !custom_name.is_empty()
177 && let Some(val) = vault.get_secret(&key).await?
178 {
179 let normalized = custom_name.to_lowercase().replace('-', "_");
183 self.secrets.custom.insert(normalized, Secret::new(val));
184 }
185 }
186 Ok(())
187 }
188}
189
190#[cfg(test)]
191mod tests {
192 use super::*;
193
194 #[tokio::test]
195 #[cfg(any(test, feature = "mock"))]
196 async fn resolve_secrets_with_mock_vault() {
197 use crate::vault::MockVaultProvider;
198
199 let vault = MockVaultProvider::new()
200 .with_secret("ZEPH_CLAUDE_API_KEY", "sk-test-123")
201 .with_secret("ZEPH_TELEGRAM_TOKEN", "tg-token-456");
202
203 let mut config = Config::load(std::path::Path::new("/nonexistent/config.toml")).unwrap();
204 config.resolve_secrets(&vault).await.unwrap();
205
206 assert_eq!(
207 config.secrets.claude_api_key.as_ref().unwrap().expose(),
208 "sk-test-123"
209 );
210 if let Some(tg) = config.telegram {
211 assert_eq!(tg.token.as_deref(), Some("tg-token-456"));
212 }
213 }
214
215 #[tokio::test]
216 #[cfg(any(test, feature = "mock"))]
217 async fn resolve_gonka_secrets_both_present() {
218 use crate::vault::MockVaultProvider;
219
220 let vault = MockVaultProvider::new()
221 .with_secret("ZEPH_GONKA_PRIVATE_KEY", "gonka-priv-key-abc")
222 .with_secret("ZEPH_GONKA_ADDRESS", "gonka1xyzaddress");
223
224 let mut config = Config::load(std::path::Path::new("/nonexistent/config.toml")).unwrap();
225 config.resolve_secrets(&vault).await.unwrap();
226
227 assert_eq!(
228 config.secrets.gonka_private_key.as_ref().unwrap().expose(),
229 "gonka-priv-key-abc"
230 );
231 assert_eq!(
232 config.secrets.gonka_address.as_ref().unwrap().expose(),
233 "gonka1xyzaddress"
234 );
235 }
236
237 #[tokio::test]
238 #[cfg(any(test, feature = "mock"))]
239 async fn resolve_gonka_partial_only_private_key() {
240 use crate::vault::MockVaultProvider;
241
242 let vault =
243 MockVaultProvider::new().with_secret("ZEPH_GONKA_PRIVATE_KEY", "gonka-priv-key-only");
244
245 let mut config = Config::load(std::path::Path::new("/nonexistent/config.toml")).unwrap();
246 config.resolve_secrets(&vault).await.unwrap();
247
248 assert!(config.secrets.gonka_private_key.is_some());
249 assert!(config.secrets.gonka_address.is_none());
250 }
251}