pub use zeph_config::{
AcpConfig, AcpLspConfig, AcpTransport, AgentConfig, CandleConfig, CascadeClassifierMode,
CascadeConfig, CloudLlmConfig, CompatibleConfig, CompressionConfig, CompressionStrategy,
Config, ConfigError, CostConfig, DaemonConfig, DebugConfig, DetectorMode, DiscordConfig,
DocumentConfig, DumpFormat, ExperimentConfig, ExperimentSchedule, FocusConfig, GatewayConfig,
GeminiConfig, GenerationParams, GraphConfig, HookDef, HookMatcher, HookType, IndexConfig,
LearningConfig, LlmConfig, LogRotation, LoggingConfig, MAX_TOKENS_CAP, McpConfig,
McpOAuthConfig, McpServerConfig, MemoryConfig, MemoryScope, NoteLinkingConfig,
OAuthTokenStorage, ObservabilityConfig, OllamaConfig, OpenAiConfig, OrchestrationConfig,
OrchestratorConfig, OrchestratorProviderConfig, PermissionMode, ProviderKind, PruningStrategy,
RateLimitConfig, ResolvedSecrets, RouterConfig, RouterStrategyConfig, RoutingConfig,
RoutingStrategy, ScheduledTaskConfig, ScheduledTaskKind, SchedulerConfig, SecurityConfig,
SemanticConfig, SessionsConfig, SidequestConfig, SkillFilter, SkillPromptMode, SkillsConfig,
SlackConfig, SttConfig, SubAgentConfig, SubAgentLifecycleHooks, SubagentHooks, TelegramConfig,
TimeoutConfig, ToolPolicy, TraceConfig, TrustConfig, TuiConfig, VaultConfig, VectorBackend,
};
#[cfg(feature = "lsp-context")]
pub use zeph_config::{DiagnosticSeverity, DiagnosticsConfig, HoverConfig, LspConfig};
pub use zeph_config::{
ContentIsolationConfig, CustomPiiPattern, ExfiltrationGuardConfig, MemoryWriteValidationConfig,
PiiFilterConfig, QuarantineConfig,
};
#[cfg(feature = "guardrail")]
pub use zeph_config::{GuardrailAction, GuardrailConfig, GuardrailFailStrategy};
pub use zeph_config::A2aServerConfig;
pub use zeph_config::{
DEFAULT_DEBUG_DIR, DEFAULT_LOG_FILE, DEFAULT_SKILLS_DIR, DEFAULT_SQLITE_PATH,
default_debug_dir, default_log_file_path, default_skills_dir, default_sqlite_path,
is_legacy_default_debug_dir, is_legacy_default_log_file, is_legacy_default_skills_path,
is_legacy_default_sqlite_path,
};
pub use zeph_config::providers::{default_stt_language, default_stt_model, default_stt_provider};
pub mod migrate {
pub use zeph_config::migrate::*;
}
use crate::vault::{Secret, VaultProvider};
pub trait SecretResolver {
fn resolve_secrets(
&mut self,
vault: &dyn VaultProvider,
) -> impl std::future::Future<Output = Result<(), ConfigError>> + Send;
}
impl SecretResolver for Config {
async fn resolve_secrets(&mut self, vault: &dyn VaultProvider) -> Result<(), ConfigError> {
if let Some(val) = vault.get_secret("ZEPH_CLAUDE_API_KEY").await? {
self.secrets.claude_api_key = Some(Secret::new(val));
}
if let Some(val) = vault.get_secret("ZEPH_OPENAI_API_KEY").await? {
self.secrets.openai_api_key = Some(Secret::new(val));
}
if let Some(val) = vault.get_secret("ZEPH_GEMINI_API_KEY").await? {
self.secrets.gemini_api_key = Some(Secret::new(val));
}
if let Some(val) = vault.get_secret("ZEPH_TELEGRAM_TOKEN").await? {
let tg = self.telegram.get_or_insert(TelegramConfig {
token: None,
allowed_users: Vec::new(),
});
tg.token = Some(val);
}
if let Some(val) = vault.get_secret("ZEPH_A2A_AUTH_TOKEN").await? {
self.a2a.auth_token = Some(val);
}
if let Some(ref entries) = self.llm.compatible {
for entry in entries {
let env_key = format!("ZEPH_COMPATIBLE_{}_API_KEY", entry.name.to_uppercase());
if let Some(val) = vault.get_secret(&env_key).await? {
self.secrets
.compatible_api_keys
.insert(entry.name.clone(), Secret::new(val));
}
}
}
if let Some(val) = vault.get_secret("ZEPH_GATEWAY_TOKEN").await? {
self.gateway.auth_token = Some(val);
}
if let Some(val) = vault.get_secret("ZEPH_DISCORD_TOKEN").await? {
let dc = self.discord.get_or_insert(DiscordConfig {
token: None,
application_id: None,
allowed_user_ids: Vec::new(),
allowed_role_ids: Vec::new(),
allowed_channel_ids: Vec::new(),
});
dc.token = Some(val);
}
if let Some(val) = vault.get_secret("ZEPH_DISCORD_APP_ID").await?
&& let Some(dc) = self.discord.as_mut()
{
dc.application_id = Some(val);
}
if let Some(val) = vault.get_secret("ZEPH_SLACK_BOT_TOKEN").await? {
let sl = self.slack.get_or_insert(SlackConfig {
bot_token: None,
signing_secret: None,
webhook_host: "127.0.0.1".into(),
port: 3000,
allowed_user_ids: Vec::new(),
allowed_channel_ids: Vec::new(),
});
sl.bot_token = Some(val);
}
if let Some(val) = vault.get_secret("ZEPH_SLACK_SIGNING_SECRET").await?
&& let Some(sl) = self.slack.as_mut()
{
sl.signing_secret = Some(val);
}
for key in vault.list_keys() {
if let Some(custom_name) = key.strip_prefix("ZEPH_SECRET_")
&& !custom_name.is_empty()
&& let Some(val) = vault.get_secret(&key).await?
{
let normalized = custom_name.to_lowercase().replace('-', "_");
self.secrets.custom.insert(normalized, Secret::new(val));
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
#[cfg(any(test, feature = "mock"))]
async fn resolve_secrets_with_mock_vault() {
use crate::vault::MockVaultProvider;
let vault = MockVaultProvider::new()
.with_secret("ZEPH_CLAUDE_API_KEY", "sk-test-123")
.with_secret("ZEPH_TELEGRAM_TOKEN", "tg-token-456");
let mut config = Config::load(std::path::Path::new("/nonexistent/config.toml")).unwrap();
config.resolve_secrets(&vault).await.unwrap();
assert_eq!(
config.secrets.claude_api_key.as_ref().unwrap().expose(),
"sk-test-123"
);
if let Some(tg) = config.telegram {
assert_eq!(tg.token.as_deref(), Some("tg-token-456"));
}
}
}