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, OrchestrationConfig, PermissionMode, ProviderEntry,
19 ProviderKind, ProviderName, PruningStrategy, RateLimitConfig, ResolvedSecrets, RouterConfig,
20 RouterStrategyConfig, ScheduledTaskConfig, ScheduledTaskKind, SchedulerConfig, SecurityConfig,
21 SemanticConfig, SessionsConfig, SidequestConfig, SkillFilter, SkillPromptMode, SkillsConfig,
22 SlackConfig, StoreRoutingConfig, StoreRoutingStrategy, SttConfig, SubAgentConfig,
23 SubAgentLifecycleHooks, SubagentHooks, TaskSupervisorConfig, TelegramConfig, TimeoutConfig,
24 ToolDiscoveryConfig, ToolDiscoveryStrategyConfig, ToolFilterConfig, ToolPolicy, TraceConfig,
25 TrustConfig, TuiConfig, 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};
33pub use zeph_config::{QualityConfig, TriggerPolicy};
34pub use zeph_config::{TelemetryBackend, TelemetryConfig};
35
36pub use zeph_config::{
37 ContentIsolationConfig, CustomPiiPattern, ExfiltrationGuardConfig, MemoryWriteValidationConfig,
38 PiiFilterConfig, QuarantineConfig,
39};
40pub use zeph_config::{GuardrailAction, GuardrailConfig, GuardrailFailStrategy};
41
42pub use zeph_config::A2aServerConfig;
43pub use zeph_config::ChannelSkillsConfig;
44pub use zeph_config::{FileChangedConfig, HooksConfig};
45
46pub use zeph_config::{
47 DEFAULT_DEBUG_DIR, DEFAULT_LOG_FILE, DEFAULT_SKILLS_DIR, DEFAULT_SQLITE_PATH,
48 default_debug_dir, default_log_file_path, default_skills_dir, default_sqlite_path,
49 is_legacy_default_debug_dir, is_legacy_default_log_file, is_legacy_default_skills_path,
50 is_legacy_default_sqlite_path,
51};
52
53pub use zeph_config::providers::{default_stt_language, default_stt_provider, validate_pool};
54
55pub mod migrate {
56 pub use zeph_config::migrate::*;
57}
58
59use crate::vault::{Secret, VaultProvider};
60
61pub trait SecretResolver {
66 fn resolve_secrets(
72 &mut self,
73 vault: &dyn VaultProvider,
74 ) -> impl std::future::Future<Output = Result<(), ConfigError>> + Send;
75}
76
77impl SecretResolver for Config {
78 async fn resolve_secrets(&mut self, vault: &dyn VaultProvider) -> Result<(), ConfigError> {
79 if let Some(val) = vault.get_secret("ZEPH_CLAUDE_API_KEY").await? {
80 self.secrets.claude_api_key = Some(Secret::new(val));
81 }
82 if let Some(val) = vault.get_secret("ZEPH_OPENAI_API_KEY").await? {
83 self.secrets.openai_api_key = Some(Secret::new(val));
84 }
85 if let Some(val) = vault.get_secret("ZEPH_GEMINI_API_KEY").await? {
86 self.secrets.gemini_api_key = Some(Secret::new(val));
87 }
88 if let Some(val) = vault.get_secret("ZEPH_TELEGRAM_TOKEN").await?
89 && let Some(tg) = self.telegram.as_mut()
90 {
91 tg.token = Some(val);
92 }
93 if let Some(val) = vault.get_secret("ZEPH_A2A_AUTH_TOKEN").await? {
94 self.a2a.auth_token = Some(val);
95 }
96 for entry in &self.llm.providers {
97 if entry.provider_type == crate::config::ProviderKind::Compatible
98 && let Some(ref name) = entry.name
99 {
100 let env_key = format!("ZEPH_COMPATIBLE_{}_API_KEY", name.to_uppercase());
101 if let Some(val) = vault.get_secret(&env_key).await? {
102 self.secrets
103 .compatible_api_keys
104 .insert(name.clone(), Secret::new(val));
105 }
106 }
107 }
108 if let Some(val) = vault.get_secret("ZEPH_HF_TOKEN").await? {
109 self.classifiers.hf_token = Some(val.clone());
110 if let Some(candle) = self.llm.candle.as_mut() {
111 candle.hf_token = Some(val);
112 }
113 }
114 if let Some(val) = vault.get_secret("ZEPH_GATEWAY_TOKEN").await? {
115 self.gateway.auth_token = Some(val);
116 }
117 if let Some(val) = vault.get_secret("ZEPH_DATABASE_URL").await? {
118 self.memory.database_url = Some(val);
119 }
120 if let Some(val) = vault.get_secret("ZEPH_DISCORD_TOKEN").await?
121 && let Some(dc) = self.discord.as_mut()
122 {
123 dc.token = Some(val);
124 }
125 if let Some(val) = vault.get_secret("ZEPH_DISCORD_APP_ID").await?
126 && let Some(dc) = self.discord.as_mut()
127 {
128 dc.application_id = Some(val);
129 }
130 if let Some(val) = vault.get_secret("ZEPH_SLACK_BOT_TOKEN").await?
131 && let Some(sl) = self.slack.as_mut()
132 {
133 sl.bot_token = Some(val);
134 }
135 if let Some(val) = vault.get_secret("ZEPH_SLACK_SIGNING_SECRET").await?
136 && let Some(sl) = self.slack.as_mut()
137 {
138 sl.signing_secret = Some(val);
139 }
140 for key in vault.list_keys() {
141 if let Some(custom_name) = key.strip_prefix("ZEPH_SECRET_")
142 && !custom_name.is_empty()
143 && let Some(val) = vault.get_secret(&key).await?
144 {
145 let normalized = custom_name.to_lowercase().replace('-', "_");
149 self.secrets.custom.insert(normalized, Secret::new(val));
150 }
151 }
152 Ok(())
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159
160 #[tokio::test]
161 #[cfg(any(test, feature = "mock"))]
162 async fn resolve_secrets_with_mock_vault() {
163 use crate::vault::MockVaultProvider;
164
165 let vault = MockVaultProvider::new()
166 .with_secret("ZEPH_CLAUDE_API_KEY", "sk-test-123")
167 .with_secret("ZEPH_TELEGRAM_TOKEN", "tg-token-456");
168
169 let mut config = Config::load(std::path::Path::new("/nonexistent/config.toml")).unwrap();
170 config.resolve_secrets(&vault).await.unwrap();
171
172 assert_eq!(
173 config.secrets.claude_api_key.as_ref().unwrap().expose(),
174 "sk-test-123"
175 );
176 if let Some(tg) = config.telegram {
177 assert_eq!(tg.token.as_deref(), Some("tg-token-456"));
178 }
179 }
180}