use std::{env as process_env, ffi::OsString};
pub use super::overrides::{EnvironmentConfig, RemoteCliEnvironmentConfig};
use super::{
AgentEnvOverrides, EnvError, FileIndexEnvOverrides, NetworkEnvOverrides, PathEnvOverrides,
PlatformKind, RemoteCliEnvOverrides, RetrievalEnvOverrides, TelemetryEnvOverrides,
UpdateEnvOverrides, WatcherEnvOverrides, WorkerEnvOverrides,
platform::{normalize_key, platform_environment},
value_parser::{
EnvironmentValues, bool_var, first_bool_var, first_string_var, path_var, positive_u64_var,
positive_usize_var, string_var,
},
variables::*,
};
impl RemoteCliEnvironmentConfig {
pub fn from_process() -> Result<Self, EnvError> {
Self::from_pairs(PlatformKind::current(), process_env::vars_os())
}
pub fn from_pairs<I, K, V>(platform: PlatformKind, pairs: I) -> Result<Self, EnvError>
where
I: IntoIterator<Item = (K, V)>,
K: Into<OsString>,
V: Into<OsString>,
{
let values = values_from_pairs(platform, pairs);
Ok(Self {
network: parse_network_overrides(&values)?,
remote_cli: parse_remote_cli_overrides(&values)?,
})
}
}
impl EnvironmentConfig {
pub fn from_process() -> Result<Self, EnvError> {
Self::from_pairs(PlatformKind::current(), process_env::vars_os())
}
pub fn from_pairs<I, K, V>(platform: PlatformKind, pairs: I) -> Result<Self, EnvError>
where
I: IntoIterator<Item = (K, V)>,
K: Into<OsString>,
V: Into<OsString>,
{
let values = values_from_pairs(platform, pairs);
Ok(Self {
platform: platform_environment(&values, platform)?,
paths: parse_path_overrides(&values)?,
network: parse_network_overrides(&values)?,
remote_cli: parse_remote_cli_overrides(&values)?,
agent: parse_agent_overrides(&values)?,
retrieval: parse_retrieval_overrides(&values)?,
workers: parse_worker_overrides(&values)?,
file_index: parse_file_index_overrides(&values)?,
updates: parse_update_overrides(&values)?,
telemetry: parse_telemetry_overrides(&values)?,
watcher: parse_watcher_overrides(&values)?,
storage_topology: string_var(&values, RELAY_KNOWLEDGE_STORAGE_TOPOLOGY)?,
})
}
}
fn values_from_pairs<I, K, V>(platform: PlatformKind, pairs: I) -> EnvironmentValues
where
I: IntoIterator<Item = (K, V)>,
K: Into<OsString>,
V: Into<OsString>,
{
pairs
.into_iter()
.map(|(key, value)| (normalize_key(platform, key.into()), value.into()))
.collect()
}
fn parse_path_overrides(values: &EnvironmentValues) -> Result<PathEnvOverrides, EnvError> {
Ok(PathEnvOverrides {
home: path_var(values, RELAY_KNOWLEDGE_HOME)?,
config_dir: path_var(values, RELAY_KNOWLEDGE_CONFIG_DIR)?,
data_dir: path_var(values, RELAY_KNOWLEDGE_DATA_DIR)?,
state_dir: path_var(values, RELAY_KNOWLEDGE_STATE_DIR)?,
cache_dir: path_var(values, RELAY_KNOWLEDGE_CACHE_DIR)?,
log_dir: path_var(values, RELAY_KNOWLEDGE_LOG_DIR)?,
temp_dir: path_var(values, RELAY_KNOWLEDGE_TEMP_DIR)?,
runtime_dir: path_var(values, RELAY_KNOWLEDGE_RUNTIME_DIR)?,
service_dir: path_var(values, RELAY_KNOWLEDGE_SERVICE_DIR)?,
})
}
fn parse_network_overrides(values: &EnvironmentValues) -> Result<NetworkEnvOverrides, EnvError> {
Ok(NetworkEnvOverrides {
http_bind: string_var(values, RELAY_KNOWLEDGE_HTTP_BIND)?,
http_request_timeout_ms: positive_u64_var(values, RELAY_KNOWLEDGE_HTTP_REQUEST_TIMEOUT_MS)?,
http_shutdown_timeout_ms: positive_u64_var(
values,
RELAY_KNOWLEDGE_HTTP_SHUTDOWN_TIMEOUT_MS,
)?,
http_max_body_bytes: positive_u64_var(values, RELAY_KNOWLEDGE_HTTP_MAX_BODY_BYTES)?,
proxy: first_string_var(
values,
&[
HTTPS_PROXY,
HTTPS_PROXY_LOWER,
HTTP_PROXY,
HTTP_PROXY_LOWER,
ALL_PROXY,
ALL_PROXY_LOWER,
],
)?,
no_proxy: first_string_var(values, &[NO_PROXY, NO_PROXY_LOWER])?,
ssl_verify: first_bool_var(values, &[SSL_VERIFY, SSL_VERIFY_LOWER])?,
qos_max_connections: positive_usize_var(values, RELAY_KNOWLEDGE_QOS_MAX_CONNECTIONS)?,
qos_max_in_flight_requests: positive_usize_var(
values,
RELAY_KNOWLEDGE_QOS_MAX_IN_FLIGHT_REQUESTS,
)?,
qos_max_queue_depth: positive_usize_var(values, RELAY_KNOWLEDGE_QOS_MAX_QUEUE_DEPTH)?,
})
}
fn parse_remote_cli_overrides(
values: &EnvironmentValues,
) -> Result<RemoteCliEnvOverrides, EnvError> {
Ok(RemoteCliEnvOverrides {
base_url: string_var(values, RELAY_KNOWLEDGE_REMOTE_BASE_URL)?,
})
}
fn parse_agent_overrides(values: &EnvironmentValues) -> Result<AgentEnvOverrides, EnvError> {
Ok(AgentEnvOverrides {
mcp_streamable_http_enabled: bool_var(values, RELAY_KNOWLEDGE_MCP_STREAMABLE_HTTP_ENABLED)?,
mcp_endpoint: string_var(values, RELAY_KNOWLEDGE_MCP_ENDPOINT)?,
mcp_allowed_origins: string_var(values, RELAY_KNOWLEDGE_MCP_ALLOWED_ORIGINS)?,
mcp_allowed_scopes: string_var(values, RELAY_KNOWLEDGE_MCP_ALLOWED_SCOPES)?,
mcp_allow_unspecified_scope: bool_var(values, RELAY_KNOWLEDGE_MCP_ALLOW_UNSPECIFIED_SCOPE)?,
mcp_max_limit: positive_usize_var(values, RELAY_KNOWLEDGE_MCP_MAX_LIMIT)?,
mcp_max_context_bytes: positive_usize_var(values, RELAY_KNOWLEDGE_MCP_MAX_CONTEXT_BYTES)?,
mcp_allow_remote_clients: bool_var(values, RELAY_KNOWLEDGE_MCP_ALLOW_REMOTE_CLIENTS)?,
audit_sink_enabled: bool_var(values, RELAY_KNOWLEDGE_AGENT_AUDIT_SINK_ENABLED)?,
audit_queue_depth: positive_usize_var(values, RELAY_KNOWLEDGE_AGENT_AUDIT_QUEUE_DEPTH)?,
})
}
fn parse_retrieval_overrides(
values: &EnvironmentValues,
) -> Result<RetrievalEnvOverrides, EnvError> {
Ok(RetrievalEnvOverrides {
semantic_backend: string_var(values, RELAY_KNOWLEDGE_SEMANTIC_BACKEND)?,
vector_backend: string_var(values, RELAY_KNOWLEDGE_VECTOR_BACKEND)?,
llm_provider: string_var(values, RELAY_KNOWLEDGE_LLM_PROVIDER)?,
embedding_base_url: string_var(values, RELAY_KNOWLEDGE_EMBEDDING_BASE_URL)?,
embedding_api_key: string_var(values, RELAY_KNOWLEDGE_EMBEDDING_API_KEY)?,
text_embedding_model: string_var(values, RELAY_KNOWLEDGE_TEXT_EMBEDDING_MODEL)?,
image_embedding_model: string_var(values, RELAY_KNOWLEDGE_IMAGE_EMBEDDING_MODEL)?,
embedding_dimension: positive_usize_var(values, RELAY_KNOWLEDGE_EMBEDDING_DIMENSION)?,
embedding_batch_size: positive_usize_var(values, RELAY_KNOWLEDGE_EMBEDDING_BATCH_SIZE)?,
embedding_timeout_ms: positive_u64_var(values, RELAY_KNOWLEDGE_EMBEDDING_TIMEOUT_MS)?,
embedding_max_concurrency: positive_usize_var(
values,
RELAY_KNOWLEDGE_EMBEDDING_MAX_CONCURRENCY,
)?,
rerank_backend: string_var(values, RELAY_KNOWLEDGE_RERANK_BACKEND)?,
rerank_model: string_var(values, RELAY_KNOWLEDGE_RERANK_MODEL)?,
rerank_timeout_ms: positive_u64_var(values, RELAY_KNOWLEDGE_RERANK_TIMEOUT_MS)?,
rerank_candidate_multiplier: positive_usize_var(
values,
RELAY_KNOWLEDGE_RERANK_CANDIDATE_MULTIPLIER,
)?,
rerank_max_candidates: positive_usize_var(values, RELAY_KNOWLEDGE_RERANK_MAX_CANDIDATES)?,
})
}
fn parse_worker_overrides(values: &EnvironmentValues) -> Result<WorkerEnvOverrides, EnvError> {
Ok(WorkerEnvOverrides {
embedding_endpoint: string_var(values, RELAY_KNOWLEDGE_WORKER_EMBEDDING_ENDPOINT)?,
ocr_endpoint: string_var(values, RELAY_KNOWLEDGE_WORKER_OCR_ENDPOINT)?,
vision_endpoint: string_var(values, RELAY_KNOWLEDGE_WORKER_VISION_ENDPOINT)?,
extractor_endpoint: string_var(values, RELAY_KNOWLEDGE_WORKER_EXTRACTOR_ENDPOINT)?,
max_in_flight: positive_usize_var(values, RELAY_KNOWLEDGE_WORKER_MAX_IN_FLIGHT)?,
code_index_max_in_flight: positive_usize_var(
values,
RELAY_KNOWLEDGE_CODE_INDEX_MAX_IN_FLIGHT,
)?,
code_index_max_indexed_repositories: positive_usize_var(
values,
RELAY_KNOWLEDGE_CODE_INDEX_MAX_INDEXED_REPOSITORIES,
)?,
silent_updates_enabled: bool_var(values, RELAY_KNOWLEDGE_SILENT_UPDATES_ENABLED)?,
})
}
fn parse_file_index_overrides(
values: &EnvironmentValues,
) -> Result<FileIndexEnvOverrides, EnvError> {
Ok(FileIndexEnvOverrides {
enabled: bool_var(values, RELAY_KNOWLEDGE_FILE_INDEX_ENABLED)?,
roots: string_var(values, RELAY_KNOWLEDGE_FILE_INDEX_ROOTS)?,
excludes: string_var(values, RELAY_KNOWLEDGE_FILE_INDEX_EXCLUDES)?,
max_depth: positive_usize_var(values, RELAY_KNOWLEDGE_FILE_INDEX_MAX_DEPTH)?,
max_file_bytes: positive_u64_var(values, RELAY_KNOWLEDGE_FILE_INDEX_MAX_FILE_BYTES)?,
scan_interval_ms: positive_u64_var(values, RELAY_KNOWLEDGE_FILE_INDEX_SCAN_INTERVAL_MS)?,
scan_timeout_ms: positive_u64_var(values, RELAY_KNOWLEDGE_FILE_INDEX_SCAN_TIMEOUT_MS)?,
max_files_per_root: positive_usize_var(
values,
RELAY_KNOWLEDGE_FILE_INDEX_MAX_FILES_PER_ROOT,
)?,
query_timeout_ms: positive_u64_var(values, RELAY_KNOWLEDGE_FILE_QUERY_TIMEOUT_MS)?,
})
}
fn parse_update_overrides(values: &EnvironmentValues) -> Result<UpdateEnvOverrides, EnvError> {
let enabled = bool_var(values, RELAY_KNOWLEDGE_UPDATE_CHECK_ENABLED)?;
let check_interval_ms = if enabled == Some(false) {
None
} else {
positive_u64_var(values, RELAY_KNOWLEDGE_UPDATE_CHECK_INTERVAL_MS)?
};
Ok(UpdateEnvOverrides {
enabled,
sources: string_var(values, RELAY_KNOWLEDGE_UPDATE_SOURCES)?,
check_interval_ms,
github_repo: string_var(values, RELAY_KNOWLEDGE_UPDATE_GITHUB_REPO)?,
})
}
fn parse_telemetry_overrides(
values: &EnvironmentValues,
) -> Result<TelemetryEnvOverrides, EnvError> {
Ok(TelemetryEnvOverrides {
otel_endpoint: string_var(values, RELAY_OTEL_ENDPOINT)?,
otel_traces: bool_var(values, RELAY_OTEL_TRACES)?,
otel_metrics: bool_var(values, RELAY_OTEL_METRICS)?,
export_timeout_ms: positive_u64_var(values, RELAY_OTEL_EXPORT_TIMEOUT_MS)?,
service_environment: string_var(values, RELAY_OTEL_SERVICE_ENVIRONMENT)?,
})
}
fn parse_watcher_overrides(values: &EnvironmentValues) -> Result<WatcherEnvOverrides, EnvError> {
Ok(WatcherEnvOverrides {
enabled: bool_var(values, RELAY_KNOWLEDGE_WATCHER_ENABLED)?,
debounce_ms: positive_u64_var(values, RELAY_KNOWLEDGE_WATCHER_DEBOUNCE_MS)?,
commit_reconcile_interval_ms: positive_u64_var(
values,
RELAY_KNOWLEDGE_WATCHER_COMMIT_RECONCILE_INTERVAL_MS,
)?,
max_watch_dirs: positive_usize_var(values, RELAY_KNOWLEDGE_WATCHER_MAX_WATCH_DIRS)?,
hash_cache_capacity: positive_usize_var(
values,
RELAY_KNOWLEDGE_WATCHER_HASH_CACHE_CAPACITY,
)?,
})
}
#[cfg(test)]
#[path = "config_tests.rs"]
mod config_tests;