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,
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
79impl SecretResolver for Config {
83 async fn resolve_secrets(&mut self, vault: &dyn VaultProvider) -> Result<(), ConfigError> {
84 if let Some(val) = vault.get_secret("ZEPH_CLAUDE_API_KEY").await? {
85 self.secrets.claude_api_key = Some(Secret::new(val));
86 }
87 if let Some(val) = vault.get_secret("ZEPH_OPENAI_API_KEY").await? {
88 self.secrets.openai_api_key = Some(Secret::new(val));
89 }
90 if let Some(val) = vault.get_secret("ZEPH_GEMINI_API_KEY").await? {
91 self.secrets.gemini_api_key = Some(Secret::new(val));
92 }
93 if let Some(val) = vault.get_secret("ZEPH_TELEGRAM_TOKEN").await?
94 && let Some(tg) = self.telegram.as_mut()
95 {
96 tg.token = Some(val);
97 }
98 if let Some(val) = vault.get_secret("ZEPH_A2A_AUTH_TOKEN").await? {
99 self.a2a.auth_token = Some(val);
100 }
101 for entry in &self.llm.providers {
102 if entry.provider_type == crate::config::ProviderKind::Compatible
103 && let Some(ref name) = entry.name
104 {
105 let env_key = format!("ZEPH_COMPATIBLE_{}_API_KEY", name.to_uppercase());
106 if let Some(val) = vault.get_secret(&env_key).await? {
107 self.secrets
108 .compatible_api_keys
109 .insert(name.clone(), Secret::new(val));
110 }
111 }
112 }
113 if let Some(val) = vault.get_secret("ZEPH_HF_TOKEN").await? {
114 self.classifiers.hf_token = Some(val.clone());
115 if let Some(candle) = self.llm.candle.as_mut() {
116 candle.hf_token = Some(val);
117 }
118 }
119 if let Some(val) = vault.get_secret("ZEPH_GATEWAY_TOKEN").await? {
120 self.gateway.auth_token = Some(val);
121 }
122 if let Some(val) = vault.get_secret("ZEPH_DATABASE_URL").await? {
123 self.memory.database_url = Some(val);
124 }
125 if let Some(val) = vault.get_secret("ZEPH_QDRANT_API_KEY").await? {
126 self.memory.qdrant_api_key = Some(Secret::new(val));
127 }
128 if let Some(val) = vault.get_secret("ZEPH_DISCORD_TOKEN").await?
129 && let Some(dc) = self.discord.as_mut()
130 {
131 dc.token = Some(val);
132 }
133 if let Some(val) = vault.get_secret("ZEPH_DISCORD_APP_ID").await?
134 && let Some(dc) = self.discord.as_mut()
135 {
136 dc.application_id = Some(val);
137 }
138 if let Some(val) = vault.get_secret("ZEPH_SLACK_BOT_TOKEN").await?
139 && let Some(sl) = self.slack.as_mut()
140 {
141 sl.bot_token = Some(val);
142 }
143 if let Some(val) = vault.get_secret("ZEPH_SLACK_SIGNING_SECRET").await?
144 && let Some(sl) = self.slack.as_mut()
145 {
146 sl.signing_secret = Some(val);
147 }
148 for key in vault.list_keys() {
149 if let Some(custom_name) = key.strip_prefix("ZEPH_SECRET_")
150 && !custom_name.is_empty()
151 && let Some(val) = vault.get_secret(&key).await?
152 {
153 let normalized = custom_name.to_lowercase().replace('-', "_");
157 self.secrets.custom.insert(normalized, Secret::new(val));
158 }
159 }
160 Ok(())
161 }
162}
163
164#[cfg(test)]
165mod tests {
166 use super::*;
167
168 #[tokio::test]
169 #[cfg(any(test, feature = "mock"))]
170 async fn resolve_secrets_with_mock_vault() {
171 use crate::vault::MockVaultProvider;
172
173 let vault = MockVaultProvider::new()
174 .with_secret("ZEPH_CLAUDE_API_KEY", "sk-test-123")
175 .with_secret("ZEPH_TELEGRAM_TOKEN", "tg-token-456");
176
177 let mut config = Config::load(std::path::Path::new("/nonexistent/config.toml")).unwrap();
178 config.resolve_secrets(&vault).await.unwrap();
179
180 assert_eq!(
181 config.secrets.claude_api_key.as_ref().unwrap().expose(),
182 "sk-test-123"
183 );
184 if let Some(tg) = config.telegram {
185 assert_eq!(tg.token.as_deref(), Some("tg-token-456"));
186 }
187 }
188}