refactor: simplify config resolution and consolidate main.rs init (#287)

* refactor: simplify config resolution and consolidate main.rs init into AppBuilder

- Add parse_bool_env() and parse_string_env() helpers to eliminate repetitive
  5-line optional_env/parse/map_err/unwrap_or boilerplate across 12 config files
- Add EmbeddingsConfig::create_provider() to centralize embeddings construction
  (fixes hardcoded 1536 dimensions and missing Ollama provider in app.rs)
- Extract init_cli_tracing(), setup_wasm_channels(), start_tunnel(),
  run_memory_command(), run_worker(), run_claude_bridge() from main.rs
- Replace ~600 lines of inline init in main.rs with AppBuilder::build_all()
- Expose catalog_entries from AppComponents for gateway registry entries
- Net reduction: ~738 lines across 15 files

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* fix: propagate dev_loaded_tool_names from AppBuilder and add parse_option_env helper

Address PR review feedback:

- Capture dev_loaded_tool_names from WASM loading in init_extensions()
  and expose via AppComponents so bootstrap_hooks receives the actual
  dev tool names instead of an empty slice (fixes silent hook skip)
- Add parse_option_env<T>() helper for Option<T> config fields,
  simplifying max_cost_per_day_cents and max_actions_per_hour in agent.rs

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* fix: fetch real NEAR AI pricing and unify cost calculation path

CostGuard was independently looking up pricing via costs::model_cost(),
falling back to GPT-4o default rates when NEAR AI model names didn't
match the static table — causing ~3x cost overestimates in logs.

- Add pricing map to NearAiChatProvider that fetches real rates from
  /v1/model/list at startup (background, non-blocking)
- Update cost_per_token() to check fetched pricing first, then static
  table, then default
- Add cost_per_token parameter to CostGuard::record_llm_call() so the
  dispatcher passes provider-sourced rates directly

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* chore: update default NEAR AI model to GLM-latest

Replace fireworks llama4-maverick-instruct-basic with zai-org/GLM-latest
as the default model in config and setup wizard.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* fix: align wizard default model name with config

Change "zai/GLM-latest" to "zai-org/GLM-latest" in wizard.rs to match
the default in config/llm.rs.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

---------

Co-authored-by: Claude Opus 4.6 <[email protected]>
This commit is contained in:
Illia Polosukhin
2026-02-22 02:54:31 +00:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 91b602790a
commit c3ce26278a
21 changed files with 1004 additions and 1460 deletions
+38 -104
View File
@@ -1,6 +1,6 @@
use std::time::Duration;
use crate::config::helpers::optional_env;
use crate::config::helpers::{parse_bool_env, parse_option_env, parse_optional_env};
use crate::error::ConfigError;
use crate::settings::Settings;
@@ -32,109 +32,43 @@ pub struct AgentConfig {
impl AgentConfig {
pub(crate) fn resolve(settings: &Settings) -> Result<Self, ConfigError> {
Ok(Self {
name: optional_env("AGENT_NAME")?.unwrap_or_else(|| settings.agent.name.clone()),
max_parallel_jobs: optional_env("AGENT_MAX_PARALLEL_JOBS")?
.map(|s| s.parse())
.transpose()
.map_err(|e| ConfigError::InvalidValue {
key: "AGENT_MAX_PARALLEL_JOBS".to_string(),
message: format!("must be a positive integer: {e}"),
})?
.unwrap_or(settings.agent.max_parallel_jobs as usize),
job_timeout: Duration::from_secs(
optional_env("AGENT_JOB_TIMEOUT_SECS")?
.map(|s| s.parse())
.transpose()
.map_err(|e| ConfigError::InvalidValue {
key: "AGENT_JOB_TIMEOUT_SECS".to_string(),
message: format!("must be a positive integer: {e}"),
})?
.unwrap_or(settings.agent.job_timeout_secs),
),
stuck_threshold: Duration::from_secs(
optional_env("AGENT_STUCK_THRESHOLD_SECS")?
.map(|s| s.parse())
.transpose()
.map_err(|e| ConfigError::InvalidValue {
key: "AGENT_STUCK_THRESHOLD_SECS".to_string(),
message: format!("must be a positive integer: {e}"),
})?
.unwrap_or(settings.agent.stuck_threshold_secs),
),
repair_check_interval: Duration::from_secs(
optional_env("SELF_REPAIR_CHECK_INTERVAL_SECS")?
.map(|s| s.parse())
.transpose()
.map_err(|e| ConfigError::InvalidValue {
key: "SELF_REPAIR_CHECK_INTERVAL_SECS".to_string(),
message: format!("must be a positive integer: {e}"),
})?
.unwrap_or(settings.agent.repair_check_interval_secs),
),
max_repair_attempts: optional_env("SELF_REPAIR_MAX_ATTEMPTS")?
.map(|s| s.parse())
.transpose()
.map_err(|e| ConfigError::InvalidValue {
key: "SELF_REPAIR_MAX_ATTEMPTS".to_string(),
message: format!("must be a positive integer: {e}"),
})?
.unwrap_or(settings.agent.max_repair_attempts),
use_planning: optional_env("AGENT_USE_PLANNING")?
.map(|s| s.parse())
.transpose()
.map_err(|e| ConfigError::InvalidValue {
key: "AGENT_USE_PLANNING".to_string(),
message: format!("must be 'true' or 'false': {e}"),
})?
.unwrap_or(settings.agent.use_planning),
session_idle_timeout: Duration::from_secs(
optional_env("SESSION_IDLE_TIMEOUT_SECS")?
.map(|s| s.parse())
.transpose()
.map_err(|e| ConfigError::InvalidValue {
key: "SESSION_IDLE_TIMEOUT_SECS".to_string(),
message: format!("must be a positive integer: {e}"),
})?
.unwrap_or(settings.agent.session_idle_timeout_secs),
),
allow_local_tools: optional_env("ALLOW_LOCAL_TOOLS")?
.map(|s| s.parse())
.transpose()
.map_err(|e| ConfigError::InvalidValue {
key: "ALLOW_LOCAL_TOOLS".to_string(),
message: format!("must be 'true' or 'false': {e}"),
})?
.unwrap_or(false),
max_cost_per_day_cents: optional_env("MAX_COST_PER_DAY_CENTS")?
.map(|s| s.parse())
.transpose()
.map_err(|e| ConfigError::InvalidValue {
key: "MAX_COST_PER_DAY_CENTS".to_string(),
message: format!("must be a positive integer: {e}"),
})?,
max_actions_per_hour: optional_env("MAX_ACTIONS_PER_HOUR")?
.map(|s| s.parse())
.transpose()
.map_err(|e| ConfigError::InvalidValue {
key: "MAX_ACTIONS_PER_HOUR".to_string(),
message: format!("must be a positive integer: {e}"),
})?,
max_tool_iterations: optional_env("AGENT_MAX_TOOL_ITERATIONS")?
.map(|s| s.parse())
.transpose()
.map_err(|e| ConfigError::InvalidValue {
key: "AGENT_MAX_TOOL_ITERATIONS".to_string(),
message: format!("must be a positive integer: {e}"),
})?
.unwrap_or(settings.agent.max_tool_iterations),
auto_approve_tools: optional_env("AGENT_AUTO_APPROVE_TOOLS")?
.map(|s| s.parse())
.transpose()
.map_err(|e| ConfigError::InvalidValue {
key: "AGENT_AUTO_APPROVE_TOOLS".to_string(),
message: format!("must be 'true' or 'false': {e}"),
})?
.unwrap_or(settings.agent.auto_approve_tools),
name: parse_optional_env("AGENT_NAME", settings.agent.name.clone())?,
max_parallel_jobs: parse_optional_env(
"AGENT_MAX_PARALLEL_JOBS",
settings.agent.max_parallel_jobs as usize,
)?,
job_timeout: Duration::from_secs(parse_optional_env(
"AGENT_JOB_TIMEOUT_SECS",
settings.agent.job_timeout_secs,
)?),
stuck_threshold: Duration::from_secs(parse_optional_env(
"AGENT_STUCK_THRESHOLD_SECS",
settings.agent.stuck_threshold_secs,
)?),
repair_check_interval: Duration::from_secs(parse_optional_env(
"SELF_REPAIR_CHECK_INTERVAL_SECS",
settings.agent.repair_check_interval_secs,
)?),
max_repair_attempts: parse_optional_env(
"SELF_REPAIR_MAX_ATTEMPTS",
settings.agent.max_repair_attempts,
)?,
use_planning: parse_bool_env("AGENT_USE_PLANNING", settings.agent.use_planning)?,
session_idle_timeout: Duration::from_secs(parse_optional_env(
"SESSION_IDLE_TIMEOUT_SECS",
settings.agent.session_idle_timeout_secs,
)?),
allow_local_tools: parse_bool_env("ALLOW_LOCAL_TOOLS", false)?,
max_cost_per_day_cents: parse_option_env("MAX_COST_PER_DAY_CENTS")?,
max_actions_per_hour: parse_option_env("MAX_ACTIONS_PER_HOUR")?,
max_tool_iterations: parse_optional_env(
"AGENT_MAX_TOOL_ITERATIONS",
settings.agent.max_tool_iterations,
)?,
auto_approve_tools: parse_bool_env(
"AGENT_AUTO_APPROVE_TOOLS",
settings.agent.auto_approve_tools,
)?,
})
}
}
+3 -17
View File
@@ -1,7 +1,7 @@
use std::path::PathBuf;
use std::time::Duration;
use crate::config::helpers::{optional_env, parse_optional_env};
use crate::config::helpers::{optional_env, parse_bool_env, parse_optional_env};
use crate::error::ConfigError;
/// Builder mode configuration.
@@ -34,25 +34,11 @@ impl Default for BuilderModeConfig {
impl BuilderModeConfig {
pub(crate) fn resolve() -> Result<Self, ConfigError> {
Ok(Self {
enabled: optional_env("BUILDER_ENABLED")?
.map(|s| s.parse())
.transpose()
.map_err(|e| ConfigError::InvalidValue {
key: "BUILDER_ENABLED".to_string(),
message: format!("must be 'true' or 'false': {e}"),
})?
.unwrap_or(true),
enabled: parse_bool_env("BUILDER_ENABLED", true)?,
build_dir: optional_env("BUILDER_DIR")?.map(PathBuf::from),
max_iterations: parse_optional_env("BUILDER_MAX_ITERATIONS", 20)?,
timeout_secs: parse_optional_env("BUILDER_TIMEOUT_SECS", 600)?,
auto_register: optional_env("BUILDER_AUTO_REGISTER")?
.map(|s| s.parse())
.transpose()
.map_err(|e| ConfigError::InvalidValue {
key: "BUILDER_AUTO_REGISTER".to_string(),
message: format!("must be 'true' or 'false': {e}"),
})?
.unwrap_or(true),
auto_register: parse_bool_env("BUILDER_AUTO_REGISTER", true)?,
})
}
+7 -30
View File
@@ -2,7 +2,7 @@ use std::path::PathBuf;
use secrecy::SecretString;
use crate::config::helpers::optional_env;
use crate::config::helpers::{optional_env, parse_bool_env, parse_optional_env};
use crate::error::ConfigError;
use crate::settings::Settings;
@@ -48,14 +48,7 @@ impl ChannelsConfig {
let http = if optional_env("HTTP_PORT")?.is_some() || optional_env("HTTP_HOST")?.is_some() {
Some(HttpConfig {
host: optional_env("HTTP_HOST")?.unwrap_or_else(|| "0.0.0.0".to_string()),
port: optional_env("HTTP_PORT")?
.map(|s| s.parse())
.transpose()
.map_err(|e| ConfigError::InvalidValue {
key: "HTTP_PORT".to_string(),
message: format!("must be a valid port number: {e}"),
})?
.unwrap_or(8080),
port: parse_optional_env("HTTP_PORT", 8080)?,
webhook_secret: optional_env("HTTP_WEBHOOK_SECRET")?.map(SecretString::from),
user_id: optional_env("HTTP_USER_ID")?.unwrap_or_else(|| "http".to_string()),
})
@@ -63,20 +56,11 @@ impl ChannelsConfig {
None
};
let gateway = if optional_env("GATEWAY_ENABLED")?
.map(|s| s.to_lowercase() == "true" || s == "1")
.unwrap_or(true)
{
let gateway_enabled = parse_bool_env("GATEWAY_ENABLED", true)?;
let gateway = if gateway_enabled {
Some(GatewayConfig {
host: optional_env("GATEWAY_HOST")?.unwrap_or_else(|| "127.0.0.1".to_string()),
port: optional_env("GATEWAY_PORT")?
.map(|s| s.parse())
.transpose()
.map_err(|e| ConfigError::InvalidValue {
key: "GATEWAY_PORT".to_string(),
message: format!("must be a valid port number: {e}"),
})?
.unwrap_or(3000),
port: parse_optional_env("GATEWAY_PORT", 3000)?,
auth_token: optional_env("GATEWAY_AUTH_TOKEN")?,
user_id: optional_env("GATEWAY_USER_ID")?.unwrap_or_else(|| "default".to_string()),
})
@@ -97,18 +81,11 @@ impl ChannelsConfig {
wasm_channels_dir: optional_env("WASM_CHANNELS_DIR")?
.map(PathBuf::from)
.unwrap_or_else(default_channels_dir),
wasm_channels_enabled: optional_env("WASM_CHANNELS_ENABLED")?
.map(|s| s.parse())
.transpose()
.map_err(|e| ConfigError::InvalidValue {
key: "WASM_CHANNELS_ENABLED".to_string(),
message: format!("must be 'true' or 'false': {e}"),
})?
.unwrap_or(true),
wasm_channels_enabled: parse_bool_env("WASM_CHANNELS_ENABLED", true)?,
telegram_owner_id: optional_env("TELEGRAM_OWNER_ID")?
.map(|s| s.parse())
.transpose()
.map_err(|e| ConfigError::InvalidValue {
.map_err(|e: std::num::ParseIntError| ConfigError::InvalidValue {
key: "TELEGRAM_OWNER_ID".to_string(),
message: format!("must be an integer: {e}"),
})?
+67 -17
View File
@@ -1,8 +1,12 @@
use std::sync::Arc;
use secrecy::{ExposeSecret, SecretString};
use crate::config::helpers::optional_env;
use crate::config::helpers::{optional_env, parse_bool_env, parse_optional_env};
use crate::error::ConfigError;
use crate::llm::SessionManager;
use crate::settings::Settings;
use crate::workspace::EmbeddingProvider;
/// Embeddings provider configuration.
#[derive(Debug, Clone)]
@@ -65,23 +69,10 @@ impl EmbeddingsConfig {
.or_else(|| settings.ollama_base_url.clone())
.unwrap_or_else(|| "http://localhost:11434".to_string());
let dimension = optional_env("EMBEDDING_DIMENSION")?
.map(|s| s.parse::<usize>())
.transpose()
.map_err(|e| ConfigError::InvalidValue {
key: "EMBEDDING_DIMENSION".to_string(),
message: format!("must be a positive integer: {e}"),
})?
.unwrap_or_else(|| default_dimension_for_model(&model));
let dimension =
parse_optional_env("EMBEDDING_DIMENSION", default_dimension_for_model(&model))?;
let enabled = optional_env("EMBEDDING_ENABLED")?
.map(|s| s.parse())
.transpose()
.map_err(|e| ConfigError::InvalidValue {
key: "EMBEDDING_ENABLED".to_string(),
message: format!("must be 'true' or 'false': {e}"),
})?
.unwrap_or(settings.embeddings.enabled);
let enabled = parse_bool_env("EMBEDDING_ENABLED", settings.embeddings.enabled)?;
Ok(Self {
enabled,
@@ -97,6 +88,65 @@ impl EmbeddingsConfig {
pub fn openai_api_key(&self) -> Option<&str> {
self.openai_api_key.as_ref().map(|s| s.expose_secret())
}
/// Create the appropriate embedding provider based on configuration.
///
/// Returns `None` if embeddings are disabled or the required credentials
/// are missing. The `nearai_base_url` and `session` are needed only for
/// the NEAR AI provider but must be passed unconditionally.
pub fn create_provider(
&self,
nearai_base_url: &str,
session: Arc<SessionManager>,
) -> Option<Arc<dyn EmbeddingProvider>> {
if !self.enabled {
tracing::info!("Embeddings disabled (set EMBEDDING_ENABLED=true to enable)");
return None;
}
match self.provider.as_str() {
"nearai" => {
tracing::info!(
"Embeddings enabled via NEAR AI (model: {}, dim: {})",
self.model,
self.dimension,
);
Some(Arc::new(
crate::workspace::NearAiEmbeddings::new(nearai_base_url, session)
.with_model(&self.model, self.dimension),
))
}
"ollama" => {
tracing::info!(
"Embeddings enabled via Ollama (model: {}, url: {}, dim: {})",
self.model,
self.ollama_base_url,
self.dimension,
);
Some(Arc::new(
crate::workspace::OllamaEmbeddings::new(&self.ollama_base_url)
.with_model(&self.model, self.dimension),
))
}
_ => {
if let Some(api_key) = self.openai_api_key() {
tracing::info!(
"Embeddings enabled via OpenAI (model: {}, dim: {})",
self.model,
self.dimension,
);
Some(Arc::new(crate::workspace::OpenAiEmbeddings::with_model(
api_key,
&self.model,
self.dimension,
)))
} else {
tracing::warn!("Embeddings configured but OPENAI_API_KEY not set");
None
}
}
}
}
}
#[cfg(test)]
+6 -17
View File
@@ -1,4 +1,4 @@
use crate::config::helpers::optional_env;
use crate::config::helpers::{optional_env, parse_bool_env, parse_optional_env};
use crate::error::ConfigError;
use crate::settings::Settings;
@@ -29,22 +29,11 @@ impl Default for HeartbeatConfig {
impl HeartbeatConfig {
pub(crate) fn resolve(settings: &Settings) -> Result<Self, ConfigError> {
Ok(Self {
enabled: optional_env("HEARTBEAT_ENABLED")?
.map(|s| s.parse())
.transpose()
.map_err(|e| ConfigError::InvalidValue {
key: "HEARTBEAT_ENABLED".to_string(),
message: format!("must be 'true' or 'false': {e}"),
})?
.unwrap_or(settings.heartbeat.enabled),
interval_secs: optional_env("HEARTBEAT_INTERVAL_SECS")?
.map(|s| s.parse())
.transpose()
.map_err(|e| ConfigError::InvalidValue {
key: "HEARTBEAT_INTERVAL_SECS".to_string(),
message: format!("must be a positive integer: {e}"),
})?
.unwrap_or(settings.heartbeat.interval_secs),
enabled: parse_bool_env("HEARTBEAT_ENABLED", settings.heartbeat.enabled)?,
interval_secs: parse_optional_env(
"HEARTBEAT_INTERVAL_SECS",
settings.heartbeat.interval_secs,
)?,
notify_channel: optional_env("HEARTBEAT_NOTIFY_CHANNEL")?
.or_else(|| settings.heartbeat.notify_channel.clone()),
notify_user: optional_env("HEARTBEAT_NOTIFY_USER")?
+42
View File
@@ -47,3 +47,45 @@ where
.transpose()
.map(|opt| opt.unwrap_or(default))
}
/// Parse a boolean from an env var with a default.
///
/// Accepts "true"/"1" as true, "false"/"0" as false.
pub(crate) fn parse_bool_env(key: &str, default: bool) -> Result<bool, ConfigError> {
match optional_env(key)? {
Some(s) => match s.to_lowercase().as_str() {
"true" | "1" => Ok(true),
"false" | "0" => Ok(false),
_ => Err(ConfigError::InvalidValue {
key: key.to_string(),
message: format!("must be 'true' or 'false', got '{s}'"),
}),
},
None => Ok(default),
}
}
/// Parse an env var into `Option<T>` — returns `None` when unset,
/// `Some(parsed)` when set to a valid value.
pub(crate) fn parse_option_env<T>(key: &str) -> Result<Option<T>, ConfigError>
where
T: std::str::FromStr,
T::Err: std::fmt::Display,
{
optional_env(key)?
.map(|s| {
s.parse().map_err(|e| ConfigError::InvalidValue {
key: key.to_string(),
message: format!("{e}"),
})
})
.transpose()
}
/// Parse a string from an env var with a default.
pub(crate) fn parse_string_env(
key: &str,
default: impl Into<String>,
) -> Result<String, ConfigError> {
Ok(optional_env(key)?.unwrap_or_else(|| default.into()))
}
+4 -25
View File
@@ -1,4 +1,4 @@
use crate::config::helpers::optional_env;
use crate::config::helpers::{parse_bool_env, parse_optional_env};
use crate::error::ConfigError;
/// Memory hygiene configuration.
@@ -28,30 +28,9 @@ impl Default for HygieneConfig {
impl HygieneConfig {
pub(crate) fn resolve() -> Result<Self, ConfigError> {
Ok(Self {
enabled: optional_env("MEMORY_HYGIENE_ENABLED")?
.map(|s| s.parse())
.transpose()
.map_err(|e| ConfigError::InvalidValue {
key: "MEMORY_HYGIENE_ENABLED".to_string(),
message: format!("must be 'true' or 'false': {e}"),
})?
.unwrap_or(true),
retention_days: optional_env("MEMORY_HYGIENE_RETENTION_DAYS")?
.map(|s| s.parse())
.transpose()
.map_err(|e| ConfigError::InvalidValue {
key: "MEMORY_HYGIENE_RETENTION_DAYS".to_string(),
message: format!("must be a positive integer: {e}"),
})?
.unwrap_or(30),
cadence_hours: optional_env("MEMORY_HYGIENE_CADENCE_HOURS")?
.map(|s| s.parse())
.transpose()
.map_err(|e| ConfigError::InvalidValue {
key: "MEMORY_HYGIENE_CADENCE_HOURS".to_string(),
message: format!("must be a positive integer: {e}"),
})?
.unwrap_or(12),
enabled: parse_bool_env("MEMORY_HYGIENE_ENABLED", true)?,
retention_days: parse_optional_env("MEMORY_HYGIENE_RETENTION_DAYS", 30)?,
cadence_hours: parse_optional_env("MEMORY_HYGIENE_CADENCE_HOURS", 12)?,
})
}
+1 -4
View File
@@ -206,10 +206,7 @@ impl LlmConfig {
let nearai = NearAiConfig {
model: optional_env("NEARAI_MODEL")?
.or_else(|| settings.selected_model.clone())
.unwrap_or_else(|| {
"fireworks::accounts/fireworks/models/llama4-maverick-instruct-basic"
.to_string()
}),
.unwrap_or_else(|| "zai-org/GLM-latest".to_string()),
cheap_model: optional_env("NEARAI_CHEAP_MODEL")?,
base_url: optional_env("NEARAI_BASE_URL")?.unwrap_or_else(|| {
if nearai_api_key.is_some() {
+2 -9
View File
@@ -1,4 +1,4 @@
use crate::config::helpers::{optional_env, parse_optional_env};
use crate::config::helpers::{parse_bool_env, parse_optional_env};
use crate::error::ConfigError;
/// Routines configuration.
@@ -31,14 +31,7 @@ impl Default for RoutineConfig {
impl RoutineConfig {
pub(crate) fn resolve() -> Result<Self, ConfigError> {
Ok(Self {
enabled: optional_env("ROUTINES_ENABLED")?
.map(|s| s.parse())
.transpose()
.map_err(|e| ConfigError::InvalidValue {
key: "ROUTINES_ENABLED".to_string(),
message: format!("must be 'true' or 'false': {e}"),
})?
.unwrap_or(true),
enabled: parse_bool_env("ROUTINES_ENABLED", true)?,
cron_check_interval_secs: parse_optional_env("ROUTINES_CRON_INTERVAL", 15)?,
max_concurrent_routines: parse_optional_env("ROUTINES_MAX_CONCURRENT", 10)?,
default_cooldown_secs: parse_optional_env("ROUTINES_DEFAULT_COOLDOWN", 300)?,
+2 -9
View File
@@ -1,4 +1,4 @@
use crate::config::helpers::{optional_env, parse_optional_env};
use crate::config::helpers::{parse_bool_env, parse_optional_env};
use crate::error::ConfigError;
/// Safety configuration.
@@ -12,14 +12,7 @@ impl SafetyConfig {
pub(crate) fn resolve() -> Result<Self, ConfigError> {
Ok(Self {
max_output_length: parse_optional_env("SAFETY_MAX_OUTPUT_LENGTH", 100_000)?,
injection_check_enabled: optional_env("SAFETY_INJECTION_CHECK_ENABLED")?
.map(|s| s.parse())
.transpose()
.map_err(|e| ConfigError::InvalidValue {
key: "SAFETY_INJECTION_CHECK_ENABLED".to_string(),
message: format!("must be 'true' or 'false': {e}"),
})?
.unwrap_or(true),
injection_check_enabled: parse_bool_env("SAFETY_INJECTION_CHECK_ENABLED", true)?,
})
}
}
+7 -29
View File
@@ -1,4 +1,4 @@
use crate::config::helpers::{optional_env, parse_optional_env};
use crate::config::helpers::{optional_env, parse_bool_env, parse_optional_env, parse_string_env};
use crate::error::ConfigError;
/// Docker sandbox configuration.
@@ -44,28 +44,13 @@ impl SandboxModeConfig {
.unwrap_or_default();
Ok(Self {
enabled: optional_env("SANDBOX_ENABLED")?
.map(|s| s.parse())
.transpose()
.map_err(|e| ConfigError::InvalidValue {
key: "SANDBOX_ENABLED".to_string(),
message: format!("must be 'true' or 'false': {e}"),
})?
.unwrap_or(true),
policy: optional_env("SANDBOX_POLICY")?.unwrap_or_else(|| "readonly".to_string()),
enabled: parse_bool_env("SANDBOX_ENABLED", true)?,
policy: parse_string_env("SANDBOX_POLICY", "readonly")?,
timeout_secs: parse_optional_env("SANDBOX_TIMEOUT_SECS", 120)?,
memory_limit_mb: parse_optional_env("SANDBOX_MEMORY_LIMIT_MB", 2048)?,
cpu_shares: parse_optional_env("SANDBOX_CPU_SHARES", 1024)?,
image: optional_env("SANDBOX_IMAGE")?
.unwrap_or_else(|| "ironclaw-worker:latest".to_string()),
auto_pull_image: optional_env("SANDBOX_AUTO_PULL")?
.map(|s| s.parse())
.transpose()
.map_err(|e| ConfigError::InvalidValue {
key: "SANDBOX_AUTO_PULL".to_string(),
message: format!("must be 'true' or 'false': {e}"),
})?
.unwrap_or(true),
image: parse_string_env("SANDBOX_IMAGE", "ironclaw-worker:latest")?,
auto_pull_image: parse_bool_env("SANDBOX_AUTO_PULL", true)?,
extra_allowed_domains: extra_domains,
})
}
@@ -221,18 +206,11 @@ impl ClaudeCodeConfig {
pub(crate) fn resolve() -> Result<Self, ConfigError> {
let defaults = Self::default();
Ok(Self {
enabled: optional_env("CLAUDE_CODE_ENABLED")?
.map(|s| s.parse())
.transpose()
.map_err(|e| ConfigError::InvalidValue {
key: "CLAUDE_CODE_ENABLED".to_string(),
message: format!("must be 'true' or 'false': {e}"),
})?
.unwrap_or(defaults.enabled),
enabled: parse_bool_env("CLAUDE_CODE_ENABLED", defaults.enabled)?,
config_dir: optional_env("CLAUDE_CONFIG_DIR")?
.map(std::path::PathBuf::from)
.unwrap_or(defaults.config_dir),
model: optional_env("CLAUDE_CODE_MODEL")?.unwrap_or(defaults.model),
model: parse_string_env("CLAUDE_CODE_MODEL", defaults.model)?,
max_turns: parse_optional_env("CLAUDE_CODE_MAX_TURNS", defaults.max_turns)?,
memory_limit_mb: parse_optional_env(
"CLAUDE_CODE_MEMORY_LIMIT_MB",
+2 -9
View File
@@ -1,6 +1,6 @@
use std::path::PathBuf;
use crate::config::helpers::{optional_env, parse_optional_env};
use crate::config::helpers::{optional_env, parse_bool_env, parse_optional_env};
use crate::error::ConfigError;
/// Skills system configuration.
@@ -38,14 +38,7 @@ fn default_skills_dir() -> PathBuf {
impl SkillsConfig {
pub(crate) fn resolve() -> Result<Self, ConfigError> {
Ok(Self {
enabled: optional_env("SKILLS_ENABLED")?
.map(|s| s.parse())
.transpose()
.map_err(|e| ConfigError::InvalidValue {
key: "SKILLS_ENABLED".to_string(),
message: format!("must be 'true' or 'false': {e}"),
})?
.unwrap_or(false),
enabled: parse_bool_env("SKILLS_ENABLED", false)?,
local_dir: optional_env("SKILLS_DIR")?
.map(PathBuf::from)
.unwrap_or_else(default_skills_dir),
+3 -17
View File
@@ -1,7 +1,7 @@
use std::path::PathBuf;
use std::time::Duration;
use crate::config::helpers::{optional_env, parse_optional_env};
use crate::config::helpers::{optional_env, parse_bool_env, parse_optional_env};
use crate::error::ConfigError;
/// WASM sandbox configuration.
@@ -48,14 +48,7 @@ fn default_tools_dir() -> PathBuf {
impl WasmConfig {
pub(crate) fn resolve() -> Result<Self, ConfigError> {
Ok(Self {
enabled: optional_env("WASM_ENABLED")?
.map(|s| s.parse())
.transpose()
.map_err(|e| ConfigError::InvalidValue {
key: "WASM_ENABLED".to_string(),
message: format!("must be 'true' or 'false': {e}"),
})?
.unwrap_or(true),
enabled: parse_bool_env("WASM_ENABLED", true)?,
tools_dir: optional_env("WASM_TOOLS_DIR")?
.map(PathBuf::from)
.unwrap_or_else(default_tools_dir),
@@ -65,14 +58,7 @@ impl WasmConfig {
)?,
default_timeout_secs: parse_optional_env("WASM_DEFAULT_TIMEOUT_SECS", 60)?,
default_fuel_limit: parse_optional_env("WASM_DEFAULT_FUEL_LIMIT", 10_000_000)?,
cache_compiled: optional_env("WASM_CACHE_COMPILED")?
.map(|s| s.parse())
.transpose()
.map_err(|e| ConfigError::InvalidValue {
key: "WASM_CACHE_COMPILED".to_string(),
message: format!("must be 'true' or 'false': {e}"),
})?
.unwrap_or(true),
cache_compiled: parse_bool_env("WASM_CACHE_COMPILED", true)?,
cache_dir: optional_env("WASM_CACHE_DIR")?.map(PathBuf::from),
})
}