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::{ContextStrategy, DigestConfig, PersonaConfig};
29pub use zeph_config::{DiagnosticSeverity, DiagnosticsConfig, HoverConfig, LspConfig};
30
31pub use zeph_config::{
32 ContentIsolationConfig, CustomPiiPattern, ExfiltrationGuardConfig, MemoryWriteValidationConfig,
33 PiiFilterConfig, QuarantineConfig,
34};
35pub use zeph_config::{GuardrailAction, GuardrailConfig, GuardrailFailStrategy};
36
37pub use zeph_config::A2aServerConfig;
38pub use zeph_config::ChannelSkillsConfig;
39pub use zeph_config::{FileChangedConfig, HooksConfig};
40
41pub use zeph_config::{
42 DEFAULT_DEBUG_DIR, DEFAULT_LOG_FILE, DEFAULT_SKILLS_DIR, DEFAULT_SQLITE_PATH,
43 default_debug_dir, default_log_file_path, default_skills_dir, default_sqlite_path,
44 is_legacy_default_debug_dir, is_legacy_default_log_file, is_legacy_default_skills_path,
45 is_legacy_default_sqlite_path,
46};
47
48pub use zeph_config::providers::{default_stt_language, default_stt_provider, validate_pool};
49
50pub mod migrate {
51 pub use zeph_config::migrate::*;
52}
53
54use crate::vault::{Secret, VaultProvider};
55
56pub trait SecretResolver {
61 fn resolve_secrets(
67 &mut self,
68 vault: &dyn VaultProvider,
69 ) -> impl std::future::Future<Output = Result<(), ConfigError>> + Send;
70}
71
72impl SecretResolver for Config {
73 async fn resolve_secrets(&mut self, vault: &dyn VaultProvider) -> Result<(), ConfigError> {
74 if let Some(val) = vault.get_secret("ZEPH_CLAUDE_API_KEY").await? {
75 self.secrets.claude_api_key = Some(Secret::new(val));
76 }
77 if let Some(val) = vault.get_secret("ZEPH_OPENAI_API_KEY").await? {
78 self.secrets.openai_api_key = Some(Secret::new(val));
79 }
80 if let Some(val) = vault.get_secret("ZEPH_GEMINI_API_KEY").await? {
81 self.secrets.gemini_api_key = Some(Secret::new(val));
82 }
83 if let Some(val) = vault.get_secret("ZEPH_TELEGRAM_TOKEN").await?
84 && let Some(tg) = self.telegram.as_mut()
85 {
86 tg.token = Some(val);
87 }
88 if let Some(val) = vault.get_secret("ZEPH_A2A_AUTH_TOKEN").await? {
89 self.a2a.auth_token = Some(val);
90 }
91 for entry in &self.llm.providers {
92 if entry.provider_type == crate::config::ProviderKind::Compatible
93 && let Some(ref name) = entry.name
94 {
95 let env_key = format!("ZEPH_COMPATIBLE_{}_API_KEY", name.to_uppercase());
96 if let Some(val) = vault.get_secret(&env_key).await? {
97 self.secrets
98 .compatible_api_keys
99 .insert(name.clone(), Secret::new(val));
100 }
101 }
102 }
103 if let Some(val) = vault.get_secret("ZEPH_HF_TOKEN").await? {
104 self.classifiers.hf_token = Some(val.clone());
105 if let Some(candle) = self.llm.candle.as_mut() {
106 candle.hf_token = Some(val);
107 }
108 }
109 if let Some(val) = vault.get_secret("ZEPH_GATEWAY_TOKEN").await? {
110 self.gateway.auth_token = Some(val);
111 }
112 if let Some(val) = vault.get_secret("ZEPH_DATABASE_URL").await? {
113 self.memory.database_url = Some(val);
114 }
115 if let Some(val) = vault.get_secret("ZEPH_DISCORD_TOKEN").await?
116 && let Some(dc) = self.discord.as_mut()
117 {
118 dc.token = Some(val);
119 }
120 if let Some(val) = vault.get_secret("ZEPH_DISCORD_APP_ID").await?
121 && let Some(dc) = self.discord.as_mut()
122 {
123 dc.application_id = Some(val);
124 }
125 if let Some(val) = vault.get_secret("ZEPH_SLACK_BOT_TOKEN").await?
126 && let Some(sl) = self.slack.as_mut()
127 {
128 sl.bot_token = Some(val);
129 }
130 if let Some(val) = vault.get_secret("ZEPH_SLACK_SIGNING_SECRET").await?
131 && let Some(sl) = self.slack.as_mut()
132 {
133 sl.signing_secret = Some(val);
134 }
135 for key in vault.list_keys() {
136 if let Some(custom_name) = key.strip_prefix("ZEPH_SECRET_")
137 && !custom_name.is_empty()
138 && let Some(val) = vault.get_secret(&key).await?
139 {
140 let normalized = custom_name.to_lowercase().replace('-', "_");
144 self.secrets.custom.insert(normalized, Secret::new(val));
145 }
146 }
147 Ok(())
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154
155 #[tokio::test]
156 #[cfg(any(test, feature = "mock"))]
157 async fn resolve_secrets_with_mock_vault() {
158 use crate::vault::MockVaultProvider;
159
160 let vault = MockVaultProvider::new()
161 .with_secret("ZEPH_CLAUDE_API_KEY", "sk-test-123")
162 .with_secret("ZEPH_TELEGRAM_TOKEN", "tg-token-456");
163
164 let mut config = Config::load(std::path::Path::new("/nonexistent/config.toml")).unwrap();
165 config.resolve_secrets(&vault).await.unwrap();
166
167 assert_eq!(
168 config.secrets.claude_api_key.as_ref().unwrap().expose(),
169 "sk-test-123"
170 );
171 if let Some(tg) = config.telegram {
172 assert_eq!(tg.token.as_deref(), Some("tg-token-456"));
173 }
174 }
175}