1use std::{env as process_env, ffi::OsString};
4
5pub use super::overrides::{EnvironmentConfig, RemoteCliEnvironmentConfig};
6use super::{
7 AgentEnvOverrides, EnvError, FileIndexEnvOverrides, NetworkEnvOverrides, PathEnvOverrides,
8 PlatformKind, RemoteCliEnvOverrides, RetrievalEnvOverrides, TelemetryEnvOverrides,
9 UpdateEnvOverrides, WatcherEnvOverrides, WorkerEnvOverrides,
10 platform::{normalize_key, platform_environment},
11 value_parser::{
12 EnvironmentValues, bool_var, first_bool_var, first_string_var, path_var, positive_u64_var,
13 positive_usize_var, string_var,
14 },
15 variables::*,
16};
17
18impl RemoteCliEnvironmentConfig {
19 pub fn from_process() -> Result<Self, EnvError> {
21 Self::from_pairs(PlatformKind::current(), process_env::vars_os())
22 }
23
24 pub fn from_pairs<I, K, V>(platform: PlatformKind, pairs: I) -> Result<Self, EnvError>
26 where
27 I: IntoIterator<Item = (K, V)>,
28 K: Into<OsString>,
29 V: Into<OsString>,
30 {
31 let values = values_from_pairs(platform, pairs);
32
33 Ok(Self {
34 network: parse_network_overrides(&values)?,
35 remote_cli: parse_remote_cli_overrides(&values)?,
36 })
37 }
38}
39
40impl EnvironmentConfig {
41 pub fn from_process() -> Result<Self, EnvError> {
43 Self::from_pairs(PlatformKind::current(), process_env::vars_os())
44 }
45
46 pub fn from_pairs<I, K, V>(platform: PlatformKind, pairs: I) -> Result<Self, EnvError>
48 where
49 I: IntoIterator<Item = (K, V)>,
50 K: Into<OsString>,
51 V: Into<OsString>,
52 {
53 let values = values_from_pairs(platform, pairs);
54
55 Ok(Self {
56 platform: platform_environment(&values, platform)?,
57 paths: parse_path_overrides(&values)?,
58 network: parse_network_overrides(&values)?,
59 remote_cli: parse_remote_cli_overrides(&values)?,
60 agent: parse_agent_overrides(&values)?,
61 retrieval: parse_retrieval_overrides(&values)?,
62 workers: parse_worker_overrides(&values)?,
63 file_index: parse_file_index_overrides(&values)?,
64 updates: parse_update_overrides(&values)?,
65 telemetry: parse_telemetry_overrides(&values)?,
66 watcher: parse_watcher_overrides(&values)?,
67 storage_topology: string_var(&values, RELAY_KNOWLEDGE_STORAGE_TOPOLOGY)?,
68 })
69 }
70}
71
72fn values_from_pairs<I, K, V>(platform: PlatformKind, pairs: I) -> EnvironmentValues
73where
74 I: IntoIterator<Item = (K, V)>,
75 K: Into<OsString>,
76 V: Into<OsString>,
77{
78 pairs
79 .into_iter()
80 .map(|(key, value)| (normalize_key(platform, key.into()), value.into()))
81 .collect()
82}
83
84fn parse_path_overrides(values: &EnvironmentValues) -> Result<PathEnvOverrides, EnvError> {
85 Ok(PathEnvOverrides {
86 home: path_var(values, RELAY_KNOWLEDGE_HOME)?,
87 config_dir: path_var(values, RELAY_KNOWLEDGE_CONFIG_DIR)?,
88 data_dir: path_var(values, RELAY_KNOWLEDGE_DATA_DIR)?,
89 state_dir: path_var(values, RELAY_KNOWLEDGE_STATE_DIR)?,
90 cache_dir: path_var(values, RELAY_KNOWLEDGE_CACHE_DIR)?,
91 log_dir: path_var(values, RELAY_KNOWLEDGE_LOG_DIR)?,
92 temp_dir: path_var(values, RELAY_KNOWLEDGE_TEMP_DIR)?,
93 runtime_dir: path_var(values, RELAY_KNOWLEDGE_RUNTIME_DIR)?,
94 service_dir: path_var(values, RELAY_KNOWLEDGE_SERVICE_DIR)?,
95 })
96}
97
98fn parse_network_overrides(values: &EnvironmentValues) -> Result<NetworkEnvOverrides, EnvError> {
99 Ok(NetworkEnvOverrides {
100 http_bind: string_var(values, RELAY_KNOWLEDGE_HTTP_BIND)?,
101 http_request_timeout_ms: positive_u64_var(values, RELAY_KNOWLEDGE_HTTP_REQUEST_TIMEOUT_MS)?,
102 http_shutdown_timeout_ms: positive_u64_var(
103 values,
104 RELAY_KNOWLEDGE_HTTP_SHUTDOWN_TIMEOUT_MS,
105 )?,
106 http_max_body_bytes: positive_u64_var(values, RELAY_KNOWLEDGE_HTTP_MAX_BODY_BYTES)?,
107 proxy: first_string_var(
108 values,
109 &[
110 HTTPS_PROXY,
111 HTTPS_PROXY_LOWER,
112 HTTP_PROXY,
113 HTTP_PROXY_LOWER,
114 ALL_PROXY,
115 ALL_PROXY_LOWER,
116 ],
117 )?,
118 no_proxy: first_string_var(values, &[NO_PROXY, NO_PROXY_LOWER])?,
119 ssl_verify: first_bool_var(values, &[SSL_VERIFY, SSL_VERIFY_LOWER])?,
120 qos_max_connections: positive_usize_var(values, RELAY_KNOWLEDGE_QOS_MAX_CONNECTIONS)?,
121 qos_max_in_flight_requests: positive_usize_var(
122 values,
123 RELAY_KNOWLEDGE_QOS_MAX_IN_FLIGHT_REQUESTS,
124 )?,
125 qos_max_queue_depth: positive_usize_var(values, RELAY_KNOWLEDGE_QOS_MAX_QUEUE_DEPTH)?,
126 })
127}
128
129fn parse_remote_cli_overrides(
130 values: &EnvironmentValues,
131) -> Result<RemoteCliEnvOverrides, EnvError> {
132 Ok(RemoteCliEnvOverrides {
133 base_url: string_var(values, RELAY_KNOWLEDGE_REMOTE_BASE_URL)?,
134 })
135}
136
137fn parse_agent_overrides(values: &EnvironmentValues) -> Result<AgentEnvOverrides, EnvError> {
138 Ok(AgentEnvOverrides {
139 mcp_streamable_http_enabled: bool_var(values, RELAY_KNOWLEDGE_MCP_STREAMABLE_HTTP_ENABLED)?,
140 mcp_endpoint: string_var(values, RELAY_KNOWLEDGE_MCP_ENDPOINT)?,
141 mcp_allowed_origins: string_var(values, RELAY_KNOWLEDGE_MCP_ALLOWED_ORIGINS)?,
142 mcp_allowed_scopes: string_var(values, RELAY_KNOWLEDGE_MCP_ALLOWED_SCOPES)?,
143 mcp_allow_unspecified_scope: bool_var(values, RELAY_KNOWLEDGE_MCP_ALLOW_UNSPECIFIED_SCOPE)?,
144 mcp_max_limit: positive_usize_var(values, RELAY_KNOWLEDGE_MCP_MAX_LIMIT)?,
145 mcp_max_context_bytes: positive_usize_var(values, RELAY_KNOWLEDGE_MCP_MAX_CONTEXT_BYTES)?,
146 mcp_allow_remote_clients: bool_var(values, RELAY_KNOWLEDGE_MCP_ALLOW_REMOTE_CLIENTS)?,
147 audit_sink_enabled: bool_var(values, RELAY_KNOWLEDGE_AGENT_AUDIT_SINK_ENABLED)?,
148 audit_queue_depth: positive_usize_var(values, RELAY_KNOWLEDGE_AGENT_AUDIT_QUEUE_DEPTH)?,
149 })
150}
151
152fn parse_retrieval_overrides(
153 values: &EnvironmentValues,
154) -> Result<RetrievalEnvOverrides, EnvError> {
155 Ok(RetrievalEnvOverrides {
156 semantic_backend: string_var(values, RELAY_KNOWLEDGE_SEMANTIC_BACKEND)?,
157 vector_backend: string_var(values, RELAY_KNOWLEDGE_VECTOR_BACKEND)?,
158 llm_provider: string_var(values, RELAY_KNOWLEDGE_LLM_PROVIDER)?,
159 embedding_base_url: string_var(values, RELAY_KNOWLEDGE_EMBEDDING_BASE_URL)?,
160 embedding_api_key: string_var(values, RELAY_KNOWLEDGE_EMBEDDING_API_KEY)?,
161 text_embedding_model: string_var(values, RELAY_KNOWLEDGE_TEXT_EMBEDDING_MODEL)?,
162 image_embedding_model: string_var(values, RELAY_KNOWLEDGE_IMAGE_EMBEDDING_MODEL)?,
163 embedding_dimension: positive_usize_var(values, RELAY_KNOWLEDGE_EMBEDDING_DIMENSION)?,
164 embedding_batch_size: positive_usize_var(values, RELAY_KNOWLEDGE_EMBEDDING_BATCH_SIZE)?,
165 embedding_timeout_ms: positive_u64_var(values, RELAY_KNOWLEDGE_EMBEDDING_TIMEOUT_MS)?,
166 embedding_max_concurrency: positive_usize_var(
167 values,
168 RELAY_KNOWLEDGE_EMBEDDING_MAX_CONCURRENCY,
169 )?,
170 rerank_backend: string_var(values, RELAY_KNOWLEDGE_RERANK_BACKEND)?,
171 rerank_model: string_var(values, RELAY_KNOWLEDGE_RERANK_MODEL)?,
172 rerank_timeout_ms: positive_u64_var(values, RELAY_KNOWLEDGE_RERANK_TIMEOUT_MS)?,
173 rerank_candidate_multiplier: positive_usize_var(
174 values,
175 RELAY_KNOWLEDGE_RERANK_CANDIDATE_MULTIPLIER,
176 )?,
177 rerank_max_candidates: positive_usize_var(values, RELAY_KNOWLEDGE_RERANK_MAX_CANDIDATES)?,
178 })
179}
180
181fn parse_worker_overrides(values: &EnvironmentValues) -> Result<WorkerEnvOverrides, EnvError> {
182 Ok(WorkerEnvOverrides {
183 embedding_endpoint: string_var(values, RELAY_KNOWLEDGE_WORKER_EMBEDDING_ENDPOINT)?,
184 ocr_endpoint: string_var(values, RELAY_KNOWLEDGE_WORKER_OCR_ENDPOINT)?,
185 vision_endpoint: string_var(values, RELAY_KNOWLEDGE_WORKER_VISION_ENDPOINT)?,
186 extractor_endpoint: string_var(values, RELAY_KNOWLEDGE_WORKER_EXTRACTOR_ENDPOINT)?,
187 max_in_flight: positive_usize_var(values, RELAY_KNOWLEDGE_WORKER_MAX_IN_FLIGHT)?,
188 code_index_max_in_flight: positive_usize_var(
189 values,
190 RELAY_KNOWLEDGE_CODE_INDEX_MAX_IN_FLIGHT,
191 )?,
192 code_index_max_indexed_repositories: positive_usize_var(
193 values,
194 RELAY_KNOWLEDGE_CODE_INDEX_MAX_INDEXED_REPOSITORIES,
195 )?,
196 silent_updates_enabled: bool_var(values, RELAY_KNOWLEDGE_SILENT_UPDATES_ENABLED)?,
197 })
198}
199
200fn parse_file_index_overrides(
201 values: &EnvironmentValues,
202) -> Result<FileIndexEnvOverrides, EnvError> {
203 Ok(FileIndexEnvOverrides {
204 enabled: bool_var(values, RELAY_KNOWLEDGE_FILE_INDEX_ENABLED)?,
205 roots: string_var(values, RELAY_KNOWLEDGE_FILE_INDEX_ROOTS)?,
206 excludes: string_var(values, RELAY_KNOWLEDGE_FILE_INDEX_EXCLUDES)?,
207 max_depth: positive_usize_var(values, RELAY_KNOWLEDGE_FILE_INDEX_MAX_DEPTH)?,
208 max_file_bytes: positive_u64_var(values, RELAY_KNOWLEDGE_FILE_INDEX_MAX_FILE_BYTES)?,
209 scan_interval_ms: positive_u64_var(values, RELAY_KNOWLEDGE_FILE_INDEX_SCAN_INTERVAL_MS)?,
210 scan_timeout_ms: positive_u64_var(values, RELAY_KNOWLEDGE_FILE_INDEX_SCAN_TIMEOUT_MS)?,
211 max_files_per_root: positive_usize_var(
212 values,
213 RELAY_KNOWLEDGE_FILE_INDEX_MAX_FILES_PER_ROOT,
214 )?,
215 query_timeout_ms: positive_u64_var(values, RELAY_KNOWLEDGE_FILE_QUERY_TIMEOUT_MS)?,
216 })
217}
218
219fn parse_update_overrides(values: &EnvironmentValues) -> Result<UpdateEnvOverrides, EnvError> {
220 let enabled = bool_var(values, RELAY_KNOWLEDGE_UPDATE_CHECK_ENABLED)?;
221 let check_interval_ms = if enabled == Some(false) {
222 None
223 } else {
224 positive_u64_var(values, RELAY_KNOWLEDGE_UPDATE_CHECK_INTERVAL_MS)?
225 };
226
227 Ok(UpdateEnvOverrides {
228 enabled,
229 sources: string_var(values, RELAY_KNOWLEDGE_UPDATE_SOURCES)?,
230 check_interval_ms,
231 github_repo: string_var(values, RELAY_KNOWLEDGE_UPDATE_GITHUB_REPO)?,
232 })
233}
234
235fn parse_telemetry_overrides(
236 values: &EnvironmentValues,
237) -> Result<TelemetryEnvOverrides, EnvError> {
238 Ok(TelemetryEnvOverrides {
239 otel_endpoint: string_var(values, RELAY_OTEL_ENDPOINT)?,
240 otel_traces: bool_var(values, RELAY_OTEL_TRACES)?,
241 otel_metrics: bool_var(values, RELAY_OTEL_METRICS)?,
242 export_timeout_ms: positive_u64_var(values, RELAY_OTEL_EXPORT_TIMEOUT_MS)?,
243 service_environment: string_var(values, RELAY_OTEL_SERVICE_ENVIRONMENT)?,
244 })
245}
246
247fn parse_watcher_overrides(values: &EnvironmentValues) -> Result<WatcherEnvOverrides, EnvError> {
248 Ok(WatcherEnvOverrides {
249 enabled: bool_var(values, RELAY_KNOWLEDGE_WATCHER_ENABLED)?,
250 debounce_ms: positive_u64_var(values, RELAY_KNOWLEDGE_WATCHER_DEBOUNCE_MS)?,
251 commit_reconcile_interval_ms: positive_u64_var(
252 values,
253 RELAY_KNOWLEDGE_WATCHER_COMMIT_RECONCILE_INTERVAL_MS,
254 )?,
255 max_watch_dirs: positive_usize_var(values, RELAY_KNOWLEDGE_WATCHER_MAX_WATCH_DIRS)?,
256 hash_cache_capacity: positive_usize_var(
257 values,
258 RELAY_KNOWLEDGE_WATCHER_HASH_CACHE_CAPACITY,
259 )?,
260 })
261}
262
263#[cfg(test)]
264#[path = "config_tests.rs"]
265mod config_tests;