1pub use zeph_config::{
11 AcpConfig, AcpLspConfig, AcpTransport, AgentConfig, CandleConfig, CandleInlineConfig,
12 CascadeClassifierMode, CascadeConfig, ClassifiersConfig, CompressionConfig,
13 CompressionStrategy, Config, ConfigError, CostConfig, DaemonConfig, DebugConfig, DetectorMode,
14 DiscordConfig, DocumentConfig, DumpFormat, ExperimentConfig, ExperimentSchedule, FocusConfig,
15 GatewayConfig, GenerationParams, GraphConfig, HookDef, HookMatcher, HookType, IndexConfig,
16 LearningConfig, LlmConfig, LlmRoutingStrategy, LogRotation, LoggingConfig, MAX_TOKENS_CAP,
17 McpConfig, McpOAuthConfig, McpServerConfig, McpTrustLevel, MemoryConfig, MemoryScope,
18 NoteLinkingConfig, OAuthTokenStorage, ObservabilityConfig, OrchestrationConfig, PermissionMode,
19 ProviderEntry, ProviderKind, ProviderName, PruningStrategy, RateLimitConfig, ResolvedSecrets,
20 RouterConfig, RouterStrategyConfig, ScheduledTaskConfig, ScheduledTaskKind, SchedulerConfig,
21 SecurityConfig, SemanticConfig, SessionsConfig, SidequestConfig, SkillFilter, SkillPromptMode,
22 SkillsConfig, SlackConfig, StoreRoutingConfig, StoreRoutingStrategy, SttConfig, SubAgentConfig,
23 SubAgentLifecycleHooks, SubagentHooks, TelegramConfig, TimeoutConfig, ToolDiscoveryConfig,
24 ToolDiscoveryStrategyConfig, ToolFilterConfig, ToolPolicy, TraceConfig, TrustConfig, TuiConfig,
25 VaultConfig, VectorBackend,
26};
27
28pub use zeph_config::{
29 AutoDreamConfig, CategoryConfig, ContextStrategy, DigestConfig, MagicDocsConfig,
30 MicrocompactConfig, PersonaConfig, TrajectoryConfig, TreeConfig,
31};
32pub use zeph_config::{DiagnosticSeverity, DiagnosticsConfig, HoverConfig, LspConfig};
33
34pub use zeph_config::{
35 ContentIsolationConfig, CustomPiiPattern, ExfiltrationGuardConfig, MemoryWriteValidationConfig,
36 PiiFilterConfig, QuarantineConfig,
37};
38pub use zeph_config::{GuardrailAction, GuardrailConfig, GuardrailFailStrategy};
39
40pub use zeph_config::A2aServerConfig;
41pub use zeph_config::ChannelSkillsConfig;
42pub use zeph_config::{FileChangedConfig, HooksConfig};
43
44pub use zeph_config::{
45 DEFAULT_DEBUG_DIR, DEFAULT_LOG_FILE, DEFAULT_SKILLS_DIR, DEFAULT_SQLITE_PATH,
46 default_debug_dir, default_log_file_path, default_skills_dir, default_sqlite_path,
47 is_legacy_default_debug_dir, is_legacy_default_log_file, is_legacy_default_skills_path,
48 is_legacy_default_sqlite_path,
49};
50
51pub use zeph_config::providers::{default_stt_language, default_stt_provider, validate_pool};
52
53pub mod migrate {
54 pub use zeph_config::migrate::*;
55}
56
57use crate::vault::{Secret, VaultProvider};
58
59pub trait SecretResolver {
64 fn resolve_secrets(
70 &mut self,
71 vault: &dyn VaultProvider,
72 ) -> impl std::future::Future<Output = Result<(), ConfigError>> + Send;
73}
74
75impl SecretResolver for Config {
76 async fn resolve_secrets(&mut self, vault: &dyn VaultProvider) -> Result<(), ConfigError> {
77 if let Some(val) = vault.get_secret("ZEPH_CLAUDE_API_KEY").await? {
78 self.secrets.claude_api_key = Some(Secret::new(val));
79 }
80 if let Some(val) = vault.get_secret("ZEPH_OPENAI_API_KEY").await? {
81 self.secrets.openai_api_key = Some(Secret::new(val));
82 }
83 if let Some(val) = vault.get_secret("ZEPH_GEMINI_API_KEY").await? {
84 self.secrets.gemini_api_key = Some(Secret::new(val));
85 }
86 if let Some(val) = vault.get_secret("ZEPH_TELEGRAM_TOKEN").await?
87 && let Some(tg) = self.telegram.as_mut()
88 {
89 tg.token = Some(val);
90 }
91 if let Some(val) = vault.get_secret("ZEPH_A2A_AUTH_TOKEN").await? {
92 self.a2a.auth_token = Some(val);
93 }
94 for entry in &self.llm.providers {
95 if entry.provider_type == crate::config::ProviderKind::Compatible
96 && let Some(ref name) = entry.name
97 {
98 let env_key = format!("ZEPH_COMPATIBLE_{}_API_KEY", name.to_uppercase());
99 if let Some(val) = vault.get_secret(&env_key).await? {
100 self.secrets
101 .compatible_api_keys
102 .insert(name.clone(), Secret::new(val));
103 }
104 }
105 }
106 if let Some(val) = vault.get_secret("ZEPH_HF_TOKEN").await? {
107 self.classifiers.hf_token = Some(val.clone());
108 if let Some(candle) = self.llm.candle.as_mut() {
109 candle.hf_token = Some(val);
110 }
111 }
112 if let Some(val) = vault.get_secret("ZEPH_GATEWAY_TOKEN").await? {
113 self.gateway.auth_token = Some(val);
114 }
115 if let Some(val) = vault.get_secret("ZEPH_DATABASE_URL").await? {
116 self.memory.database_url = Some(val);
117 }
118 if let Some(val) = vault.get_secret("ZEPH_DISCORD_TOKEN").await?
119 && let Some(dc) = self.discord.as_mut()
120 {
121 dc.token = Some(val);
122 }
123 if let Some(val) = vault.get_secret("ZEPH_DISCORD_APP_ID").await?
124 && let Some(dc) = self.discord.as_mut()
125 {
126 dc.application_id = Some(val);
127 }
128 if let Some(val) = vault.get_secret("ZEPH_SLACK_BOT_TOKEN").await?
129 && let Some(sl) = self.slack.as_mut()
130 {
131 sl.bot_token = Some(val);
132 }
133 if let Some(val) = vault.get_secret("ZEPH_SLACK_SIGNING_SECRET").await?
134 && let Some(sl) = self.slack.as_mut()
135 {
136 sl.signing_secret = Some(val);
137 }
138 for key in vault.list_keys() {
139 if let Some(custom_name) = key.strip_prefix("ZEPH_SECRET_")
140 && !custom_name.is_empty()
141 && let Some(val) = vault.get_secret(&key).await?
142 {
143 let normalized = custom_name.to_lowercase().replace('-', "_");
147 self.secrets.custom.insert(normalized, Secret::new(val));
148 }
149 }
150 Ok(())
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157
158 #[tokio::test]
159 #[cfg(any(test, feature = "mock"))]
160 async fn resolve_secrets_with_mock_vault() {
161 use crate::vault::MockVaultProvider;
162
163 let vault = MockVaultProvider::new()
164 .with_secret("ZEPH_CLAUDE_API_KEY", "sk-test-123")
165 .with_secret("ZEPH_TELEGRAM_TOKEN", "tg-token-456");
166
167 let mut config = Config::load(std::path::Path::new("/nonexistent/config.toml")).unwrap();
168 config.resolve_secrets(&vault).await.unwrap();
169
170 assert_eq!(
171 config.secrets.claude_api_key.as_ref().unwrap().expose(),
172 "sk-test-123"
173 );
174 if let Some(tg) = config.telegram {
175 assert_eq!(tg.token.as_deref(), Some("tg-token-456"));
176 }
177 }
178}