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