use super::env_keys::{llm, observability as obv_keys, sandbox as sb_keys};
use super::loader::{env_bool, env_optional, env_or};
use std::path::PathBuf;
#[derive(Debug, Clone)]
pub struct LlmConfig {
pub api_base: String,
pub api_key: String,
pub model: String,
}
impl LlmConfig {
pub fn from_env() -> Self {
super::loader::load_dotenv();
Self {
api_base: env_or(llm::API_BASE, llm::API_BASE_ALIASES, || {
"https://api.openai.com/v1".to_string()
}),
api_key: env_or(llm::API_KEY, llm::API_KEY_ALIASES, String::new),
model: env_or(llm::MODEL, llm::MODEL_ALIASES, || "gpt-4o".to_string()),
}
}
pub fn try_from_env() -> Option<Self> {
let cfg = Self::from_env();
if cfg.api_key.trim().is_empty() || cfg.api_base.trim().is_empty() {
None
} else {
Some(cfg)
}
}
pub fn default_model_for_base(api_base: &str) -> &'static str {
if api_base.contains("localhost:11434") || api_base.contains("127.0.0.1:11434") {
"qwen2.5:7b"
} else if api_base.contains("api.openai.com") {
"gpt-4o"
} else if api_base.contains("api.deepseek.com") {
"deepseek-chat"
} else if api_base.contains("dashscope.aliyuncs.com") {
"qwen-plus"
} else if api_base.contains("minimax") {
"MiniMax-M2.5"
} else {
"gpt-4o"
}
}
}
#[derive(Debug, Clone)]
pub struct PathsConfig {
pub workspace: String,
pub output_dir: Option<String>,
pub skills_repo: String,
pub skills_root: Option<String>,
pub data_dir: PathBuf,
}
impl PathsConfig {
pub fn from_env() -> Self {
let default_data_dir = crate::paths::data_root();
super::loader::load_dotenv();
let workspace =
super::loader::env_optional(super::env_keys::paths::SKILLLITE_WORKSPACE, &[])
.unwrap_or_else(|| {
std::env::current_dir()
.unwrap_or_else(|_| PathBuf::from("."))
.to_string_lossy()
.to_string()
});
let output_dir =
super::loader::env_optional(super::env_keys::paths::SKILLLITE_OUTPUT_DIR, &[]);
let skills_repo =
super::loader::env_or(super::env_keys::paths::SKILLLITE_SKILLS_REPO, &[], || {
"EXboys/skilllite".to_string()
});
let skills_root =
super::loader::env_optional(super::env_keys::paths::SKILLBOX_SKILLS_ROOT, &[]);
Self {
workspace,
output_dir,
skills_repo,
skills_root,
data_dir: default_data_dir,
}
}
}
#[derive(Debug, Clone)]
pub struct AgentFeatureFlags {
pub enable_memory: bool,
pub enable_task_planning: bool,
pub enable_memory_vector: bool,
}
impl AgentFeatureFlags {
pub fn from_env() -> Self {
Self {
enable_memory: env_bool("SKILLLITE_ENABLE_MEMORY", &[], true),
enable_task_planning: env_bool("SKILLLITE_ENABLE_TASK_PLANNING", &[], true),
enable_memory_vector: env_bool("SKILLLITE_ENABLE_MEMORY_VECTOR", &[], false),
}
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)] pub struct EmbeddingConfig {
pub model: String,
pub dimension: usize,
pub api_base: String,
pub api_key: String,
}
impl EmbeddingConfig {
pub fn from_env() -> Self {
super::loader::load_dotenv();
let api_base = super::loader::env_or(
"SKILLLITE_EMBEDDING_BASE_URL",
&["EMBEDDING_BASE_URL"],
|| {
super::loader::env_or(llm::API_BASE, llm::API_BASE_ALIASES, || {
"https://api.openai.com/v1".to_string()
})
},
);
let api_key = super::loader::env_or(
"SKILLLITE_EMBEDDING_API_KEY",
&["EMBEDDING_API_KEY"],
|| super::loader::env_or(llm::API_KEY, llm::API_KEY_ALIASES, || "".to_string()),
);
let (default_model, default_dim) = Self::default_for_base(&api_base);
let model =
super::loader::env_or("SKILLLITE_EMBEDDING_MODEL", &["EMBEDDING_MODEL"], || {
default_model.to_string()
});
let dimension = super::loader::env_optional("SKILLLITE_EMBEDDING_DIMENSION", &[])
.and_then(|s| s.parse::<usize>().ok())
.unwrap_or(default_dim);
Self {
model,
dimension,
api_base,
api_key,
}
}
fn default_for_base(api_base: &str) -> (&'static str, usize) {
let base_lower = api_base.to_lowercase();
if base_lower.contains("dashscope.aliyuncs.com") {
("text-embedding-v3", 1024)
} else if base_lower.contains("api.deepseek.com") {
("deepseek-embedding", 1536)
} else if base_lower.contains("generativelanguage.googleapis.com") {
("gemini-embedding-001", 3072)
} else if base_lower.contains("localhost:11434") || base_lower.contains("127.0.0.1:11434") {
("nomic-embed-text", 768)
} else if base_lower.contains("minimax") {
("text-embedding-01", 1536)
} else {
("text-embedding-3-small", 1536)
}
}
}
#[derive(Debug, Clone)]
pub struct ObservabilityConfig {
pub quiet: bool,
pub log_level: String,
pub log_json: bool,
pub audit_log: Option<String>,
pub security_events_log: Option<String>,
}
impl ObservabilityConfig {
pub fn from_env() -> &'static Self {
use std::sync::OnceLock;
static CACHE: OnceLock<ObservabilityConfig> = OnceLock::new();
CACHE.get_or_init(|| {
super::loader::load_dotenv();
let quiet = env_bool(obv_keys::SKILLLITE_QUIET, obv_keys::QUIET_ALIASES, false);
let log_level = env_or(
obv_keys::SKILLLITE_LOG_LEVEL,
obv_keys::LOG_LEVEL_ALIASES,
|| "skilllite=info,skilllite_swarm=info".to_string(),
);
let log_json = env_bool(
obv_keys::SKILLLITE_LOG_JSON,
obv_keys::LOG_JSON_ALIASES,
false,
);
let audit_disabled = env_bool(obv_keys::SKILLLITE_AUDIT_DISABLED, &[], false);
let audit_log = if audit_disabled {
None
} else {
env_optional(obv_keys::SKILLLITE_AUDIT_LOG, obv_keys::AUDIT_LOG_ALIASES).or_else(
|| {
Some(
crate::paths::data_root()
.join("audit")
.to_string_lossy()
.into_owned(),
)
},
)
};
let security_events_log = env_optional(obv_keys::SKILLLITE_SECURITY_EVENTS_LOG, &[]);
Self {
quiet,
log_level,
log_json,
audit_log,
security_events_log,
}
})
}
}
#[derive(Debug, Clone)]
pub struct SandboxEnvConfig {
pub sandbox_level: u8,
pub max_memory_mb: u64,
pub timeout_secs: u64,
pub auto_approve: bool,
pub no_sandbox: bool,
pub allow_playwright: bool,
pub script_args: Option<String>,
}
impl SandboxEnvConfig {
pub fn from_env() -> Self {
super::loader::load_dotenv();
let sandbox_level = env_or(
sb_keys::SKILLLITE_SANDBOX_LEVEL,
sb_keys::SANDBOX_LEVEL_ALIASES,
|| "3".to_string(),
)
.parse::<u8>()
.ok()
.and_then(|n| if (1..=3).contains(&n) { Some(n) } else { None })
.unwrap_or(3);
let max_memory_mb = env_or(
sb_keys::SKILLLITE_MAX_MEMORY_MB,
sb_keys::MAX_MEMORY_MB_ALIASES,
|| "256".to_string(),
)
.parse::<u64>()
.ok()
.unwrap_or(256);
let timeout_secs = env_or(
sb_keys::SKILLLITE_TIMEOUT_SECS,
sb_keys::TIMEOUT_SECS_ALIASES,
|| "30".to_string(),
)
.parse::<u64>()
.ok()
.unwrap_or(30);
let auto_approve = env_bool(
sb_keys::SKILLLITE_AUTO_APPROVE,
sb_keys::AUTO_APPROVE_ALIASES,
false,
);
let no_sandbox = env_bool(
sb_keys::SKILLLITE_NO_SANDBOX,
sb_keys::NO_SANDBOX_ALIASES,
false,
);
let allow_playwright = env_bool(
sb_keys::SKILLLITE_ALLOW_PLAYWRIGHT,
sb_keys::ALLOW_PLAYWRIGHT_ALIASES,
false,
);
let script_args =
env_optional(sb_keys::SKILLLITE_SCRIPT_ARGS, sb_keys::SCRIPT_ARGS_ALIASES);
Self {
sandbox_level,
max_memory_mb,
timeout_secs,
auto_approve,
no_sandbox,
allow_playwright,
script_args,
}
}
}
#[derive(Debug, Clone)]
pub struct CacheConfig;
impl CacheConfig {
pub fn cache_dir() -> Option<String> {
super::loader::load_dotenv();
env_optional(
super::env_keys::cache::SKILLLITE_CACHE_DIR,
super::env_keys::cache::CACHE_DIR_ALIASES,
)
}
}