use std::collections::HashMap; use std::ffi::OsStr; use std::path::{Path, PathBuf}; use zeroclaw::config::schema::ModelProviderConfig; use zeroclaw::Config as ZeroClawConfig; use crate::compat::cron_adapter::configure_embedded_cron; use crate::compat::memory_adapter::configure_embedded_memory; use crate::config::{BrowserBackend, DeepSeekSettings, SgClawSettings}; use crate::runtime::RuntimeProfile; const SGCLAW_ZEROCLAW_WORKSPACE_DIR: &str = ".sgclaw-zeroclaw-workspace"; const SKILLS_DIR_NAME: &str = "skills"; pub fn build_zeroclaw_config( workspace_root: &Path, ) -> Result { let settings = SgClawSettings::from_env()?; Ok(build_zeroclaw_config_from_sgclaw_settings( workspace_root, &settings, )) } pub fn build_zeroclaw_config_from_settings( workspace_root: &Path, settings: &DeepSeekSettings, ) -> ZeroClawConfig { build_zeroclaw_config_from_sgclaw_settings(workspace_root, &SgClawSettings::from(settings)) } pub fn build_zeroclaw_config_from_sgclaw_settings( workspace_root: &Path, settings: &SgClawSettings, ) -> ZeroClawConfig { let workspace_dir = zeroclaw_workspace_dir(workspace_root); let active_provider = settings.active_provider_settings(); let mut config = ZeroClawConfig::default(); config.workspace_dir = workspace_dir.clone(); config.config_path = workspace_dir.join("config.toml"); config.default_provider = Some(active_provider.provider.clone()); config.default_model = Some(active_provider.model.clone()); config.api_key = Some(active_provider.api_key.clone()); config.api_url = active_provider.base_url.clone(); config.api_path = active_provider.api_path.clone(); config.default_temperature = match settings.runtime_profile { RuntimeProfile::BrowserAttached | RuntimeProfile::BrowserHeavy => 0.0, RuntimeProfile::GeneralAssistant => config.default_temperature, }; config.skills.prompt_injection_mode = settings.skills_prompt_mode; config.model_providers = settings .providers .iter() .map(|provider| { ( provider.id.clone(), ModelProviderConfig { name: (!provider.provider.starts_with("custom:")) .then(|| provider.provider.clone()), base_url: provider.base_url.clone(), api_path: provider.api_path.clone(), wire_api: provider.wire_api.clone(), requires_openai_auth: provider.requires_openai_auth, azure_openai_resource: None, azure_openai_deployment: None, azure_openai_api_version: None, max_tokens: None, }, ) }) .collect::>(); config.browser.enabled = !matches!(settings.browser_backend, BrowserBackend::SuperRpa); if let Some(backend) = settings.browser_backend.zeroclaw_backend() { config.browser.backend = backend.to_string(); } configure_embedded_memory(&mut config); configure_embedded_cron(&mut config); config } pub fn zeroclaw_workspace_dir(workspace_root: &Path) -> PathBuf { workspace_root.join(SGCLAW_ZEROCLAW_WORKSPACE_DIR) } pub fn zeroclaw_default_skills_dir(workspace_root: &Path) -> PathBuf { zeroclaw_workspace_dir(workspace_root).join(SKILLS_DIR_NAME) } pub fn resolve_skills_dir(workspace_root: &Path, settings: &DeepSeekSettings) -> PathBuf { settings .skills_dir .as_deref() .map(normalize_configured_skills_dir) .unwrap_or_else(|| zeroclaw_default_skills_dir(workspace_root)) } pub fn resolve_skills_dir_from_sgclaw_settings( workspace_root: &Path, settings: &SgClawSettings, ) -> PathBuf { settings .skills_dir .as_deref() .map(normalize_configured_skills_dir) .unwrap_or_else(|| zeroclaw_default_skills_dir(workspace_root)) } fn normalize_configured_skills_dir(configured_dir: &Path) -> PathBuf { if configured_dir.file_name() == Some(OsStr::new(SKILLS_DIR_NAME)) { return configured_dir.to_path_buf(); } let nested_skills_dir = configured_dir.join(SKILLS_DIR_NAME); if nested_skills_dir.is_dir() { nested_skills_dir } else { configured_dir.to_path_buf() } }