1use super::env_keys::{llm, observability as obv_keys, sandbox as sb_keys};
6use super::loader::{env_bool, env_optional, env_or};
7use std::path::PathBuf;
8
9#[derive(Debug, Clone)]
11pub struct LlmConfig {
12 pub api_base: String,
13 pub api_key: String,
14 pub model: String,
15}
16
17impl LlmConfig {
18 pub fn from_env() -> Self {
20 super::loader::load_dotenv();
21 Self {
22 api_base: env_or(llm::API_BASE, llm::API_BASE_ALIASES, || {
23 "https://api.openai.com/v1".to_string()
24 }),
25 api_key: env_or(llm::API_KEY, llm::API_KEY_ALIASES, String::new),
26 model: env_or(llm::MODEL, llm::MODEL_ALIASES, || "gpt-4o".to_string()),
27 }
28 }
29
30 pub fn try_from_env() -> Option<Self> {
32 let cfg = Self::from_env();
33 if cfg.api_key.trim().is_empty() || cfg.api_base.trim().is_empty() {
34 None
35 } else {
36 Some(cfg)
37 }
38 }
39
40 pub fn default_model_for_base(api_base: &str) -> &'static str {
42 if api_base.contains("localhost:11434") || api_base.contains("127.0.0.1:11434") {
43 "qwen2.5:7b"
44 } else if api_base.contains("api.openai.com") {
45 "gpt-4o"
46 } else if api_base.contains("api.deepseek.com") {
47 "deepseek-chat"
48 } else if api_base.contains("dashscope.aliyuncs.com") {
49 "qwen-plus"
50 } else if api_base.contains("minimax") {
51 "MiniMax-M2.5"
52 } else {
53 "gpt-4o"
54 }
55 }
56}
57
58#[derive(Debug, Clone)]
60pub struct PathsConfig {
61 pub workspace: String,
62 pub output_dir: Option<String>,
63 pub skills_repo: String,
64 pub skills_root: Option<String>,
66 pub data_dir: PathBuf,
67}
68
69impl PathsConfig {
70 pub fn from_env() -> Self {
71 let default_data_dir = crate::paths::data_root();
72 super::loader::load_dotenv();
73 let workspace =
74 super::loader::env_optional(super::env_keys::paths::SKILLLITE_WORKSPACE, &[])
75 .unwrap_or_else(|| {
76 std::env::current_dir()
77 .unwrap_or_else(|_| PathBuf::from("."))
78 .to_string_lossy()
79 .to_string()
80 });
81
82 let output_dir =
83 super::loader::env_optional(super::env_keys::paths::SKILLLITE_OUTPUT_DIR, &[]);
84
85 let skills_repo =
86 super::loader::env_or(super::env_keys::paths::SKILLLITE_SKILLS_REPO, &[], || {
87 "EXboys/skilllite".to_string()
88 });
89
90 let skills_root =
91 super::loader::env_optional(super::env_keys::paths::SKILLBOX_SKILLS_ROOT, &[]);
92
93 Self {
94 workspace,
95 output_dir,
96 skills_repo,
97 skills_root,
98 data_dir: default_data_dir,
99 }
100 }
101}
102
103#[derive(Debug, Clone)]
105pub struct AgentFeatureFlags {
106 pub enable_memory: bool,
107 pub enable_task_planning: bool,
108 pub enable_memory_vector: bool,
110}
111
112impl AgentFeatureFlags {
113 pub fn from_env() -> Self {
114 Self {
115 enable_memory: env_bool("SKILLLITE_ENABLE_MEMORY", &[], true),
116 enable_task_planning: env_bool("SKILLLITE_ENABLE_TASK_PLANNING", &[], true),
117 enable_memory_vector: env_bool("SKILLLITE_ENABLE_MEMORY_VECTOR", &[], false),
118 }
119 }
120}
121
122#[derive(Debug, Clone)]
124#[allow(dead_code)] pub struct EmbeddingConfig {
126 pub model: String,
127 pub dimension: usize,
128 pub api_base: String,
129 pub api_key: String,
130}
131
132impl EmbeddingConfig {
133 pub fn from_env() -> Self {
134 super::loader::load_dotenv();
135 let api_base = super::loader::env_or(
137 "SKILLLITE_EMBEDDING_BASE_URL",
138 &["EMBEDDING_BASE_URL"],
139 || {
140 super::loader::env_or(llm::API_BASE, llm::API_BASE_ALIASES, || {
141 "https://api.openai.com/v1".to_string()
142 })
143 },
144 );
145 let api_key = super::loader::env_or(
146 "SKILLLITE_EMBEDDING_API_KEY",
147 &["EMBEDDING_API_KEY"],
148 || super::loader::env_or(llm::API_KEY, llm::API_KEY_ALIASES, || "".to_string()),
149 );
150 let (default_model, default_dim) = Self::default_for_base(&api_base);
151 let model =
152 super::loader::env_or("SKILLLITE_EMBEDDING_MODEL", &["EMBEDDING_MODEL"], || {
153 default_model.to_string()
154 });
155 let dimension = super::loader::env_optional("SKILLLITE_EMBEDDING_DIMENSION", &[])
156 .and_then(|s| s.parse::<usize>().ok())
157 .unwrap_or(default_dim);
158 Self {
159 model,
160 dimension,
161 api_base,
162 api_key,
163 }
164 }
165
166 fn default_for_base(api_base: &str) -> (&'static str, usize) {
168 let base_lower = api_base.to_lowercase();
169 if base_lower.contains("dashscope.aliyuncs.com") {
170 ("text-embedding-v3", 1024)
172 } else if base_lower.contains("api.deepseek.com") {
173 ("deepseek-embedding", 1536)
174 } else if base_lower.contains("generativelanguage.googleapis.com") {
175 ("gemini-embedding-001", 3072)
177 } else if base_lower.contains("localhost:11434") || base_lower.contains("127.0.0.1:11434") {
178 ("nomic-embed-text", 768)
180 } else if base_lower.contains("minimax") {
181 ("text-embedding-01", 1536)
183 } else {
184 ("text-embedding-3-small", 1536)
185 }
186 }
187}
188
189#[derive(Debug, Clone)]
191pub struct ObservabilityConfig {
192 pub quiet: bool,
193 pub log_level: String,
194 pub log_json: bool,
195 pub audit_log: Option<String>,
196 pub security_events_log: Option<String>,
197}
198
199impl ObservabilityConfig {
200 pub fn from_env() -> &'static Self {
201 use std::sync::OnceLock;
202 static CACHE: OnceLock<ObservabilityConfig> = OnceLock::new();
203 CACHE.get_or_init(|| {
204 super::loader::load_dotenv();
205 let quiet = env_bool(obv_keys::SKILLLITE_QUIET, obv_keys::QUIET_ALIASES, false);
206 let log_level = env_or(
207 obv_keys::SKILLLITE_LOG_LEVEL,
208 obv_keys::LOG_LEVEL_ALIASES,
209 || "skilllite=info,skilllite_swarm=info".to_string(),
210 );
211 let log_json = env_bool(
212 obv_keys::SKILLLITE_LOG_JSON,
213 obv_keys::LOG_JSON_ALIASES,
214 false,
215 );
216 let audit_disabled = env_bool(obv_keys::SKILLLITE_AUDIT_DISABLED, &[], false);
217 let audit_log = if audit_disabled {
218 None
219 } else {
220 env_optional(obv_keys::SKILLLITE_AUDIT_LOG, obv_keys::AUDIT_LOG_ALIASES).or_else(
221 || {
222 Some(
223 crate::paths::data_root()
224 .join("audit")
225 .to_string_lossy()
226 .into_owned(),
227 )
228 },
229 )
230 };
231 let security_events_log = env_optional(obv_keys::SKILLLITE_SECURITY_EVENTS_LOG, &[]);
232 Self {
233 quiet,
234 log_level,
235 log_json,
236 audit_log,
237 security_events_log,
238 }
239 })
240 }
241}
242
243#[derive(Debug, Clone)]
248pub struct SandboxEnvConfig {
249 pub sandbox_level: u8,
251 pub max_memory_mb: u64,
253 pub timeout_secs: u64,
255 pub auto_approve: bool,
257 pub no_sandbox: bool,
259 pub allow_playwright: bool,
261 pub script_args: Option<String>,
263}
264
265impl SandboxEnvConfig {
266 pub fn from_env() -> Self {
267 super::loader::load_dotenv();
268 let sandbox_level = env_or(
269 sb_keys::SKILLLITE_SANDBOX_LEVEL,
270 sb_keys::SANDBOX_LEVEL_ALIASES,
271 || "3".to_string(),
272 )
273 .parse::<u8>()
274 .ok()
275 .and_then(|n| if (1..=3).contains(&n) { Some(n) } else { None })
276 .unwrap_or(3);
277
278 let max_memory_mb = env_or(
279 sb_keys::SKILLLITE_MAX_MEMORY_MB,
280 sb_keys::MAX_MEMORY_MB_ALIASES,
281 || "256".to_string(),
282 )
283 .parse::<u64>()
284 .ok()
285 .unwrap_or(256);
286
287 let timeout_secs = env_or(
288 sb_keys::SKILLLITE_TIMEOUT_SECS,
289 sb_keys::TIMEOUT_SECS_ALIASES,
290 || "30".to_string(),
291 )
292 .parse::<u64>()
293 .ok()
294 .unwrap_or(30);
295
296 let auto_approve = env_bool(
297 sb_keys::SKILLLITE_AUTO_APPROVE,
298 sb_keys::AUTO_APPROVE_ALIASES,
299 false,
300 );
301 let no_sandbox = env_bool(
302 sb_keys::SKILLLITE_NO_SANDBOX,
303 sb_keys::NO_SANDBOX_ALIASES,
304 false,
305 );
306 let allow_playwright = env_bool(
307 sb_keys::SKILLLITE_ALLOW_PLAYWRIGHT,
308 sb_keys::ALLOW_PLAYWRIGHT_ALIASES,
309 false,
310 );
311 let script_args =
312 env_optional(sb_keys::SKILLLITE_SCRIPT_ARGS, sb_keys::SCRIPT_ARGS_ALIASES);
313
314 Self {
315 sandbox_level,
316 max_memory_mb,
317 timeout_secs,
318 auto_approve,
319 no_sandbox,
320 allow_playwright,
321 script_args,
322 }
323 }
324}
325
326#[derive(Debug, Clone)]
328pub struct CacheConfig;
329
330impl CacheConfig {
331 pub fn cache_dir() -> Option<String> {
332 super::loader::load_dotenv();
333 env_optional(
334 super::env_keys::cache::SKILLLITE_CACHE_DIR,
335 super::env_keys::cache::CACHE_DIR_ALIASES,
336 )
337 }
338}