1use std::str::FromStr;
4
5use agentics_domain::models::challenge::TargetAccelerator;
6use serde::{Deserialize, Deserializer};
7
8use crate::ENV_AGENTICS_RUNNER_NAMESPACE;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum RunnerWritableStorageMode {
13 Unbounded,
15 XfsProjectQuotaSlots,
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum AgentRegistrationMode {
22 PioneerCode,
24 Public,
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
30#[serde(rename_all = "snake_case")]
31pub enum HostProbeMode {
32 Off,
34 Warn,
36 Require,
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
42#[serde(rename_all = "snake_case")]
43pub enum RunnerSecurityProfile {
44 Development,
46 Production,
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum OfficialLogRedactionMode {
53 ContractBased,
55 Always,
57}
58
59#[derive(Debug, Clone, PartialEq, Eq, Hash)]
61pub struct RunnerNamespace(String);
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
65#[serde(rename_all = "snake_case")]
66pub enum WorkerAccelerators {
67 None,
69 Gpu,
71}
72
73impl HostProbeMode {
74 pub fn as_str(self) -> &'static str {
76 match self {
77 Self::Off => "off",
78 Self::Warn => "warn",
79 Self::Require => "require",
80 }
81 }
82}
83
84impl FromStr for HostProbeMode {
85 type Err = anyhow::Error;
86
87 fn from_str(value: &str) -> anyhow::Result<Self> {
89 match value.trim() {
90 "off" => Ok(Self::Off),
91 "warn" => Ok(Self::Warn),
92 "require" => Ok(Self::Require),
93 other => anyhow::bail!(
94 "AGENTICS_HOST_PROBE_MODE must be `off`, `warn`, or `require`, got `{other}`"
95 ),
96 }
97 }
98}
99
100impl RunnerSecurityProfile {
101 pub fn as_str(self) -> &'static str {
103 match self {
104 Self::Development => "development",
105 Self::Production => "production",
106 }
107 }
108}
109
110impl FromStr for RunnerSecurityProfile {
111 type Err = anyhow::Error;
112
113 fn from_str(value: &str) -> anyhow::Result<Self> {
115 match value.trim() {
116 "development" => Ok(Self::Development),
117 "production" => Ok(Self::Production),
118 other => anyhow::bail!(
119 "AGENTICS_RUNNER_SECURITY_PROFILE must be `development` or `production`, got `{other}`"
120 ),
121 }
122 }
123}
124
125impl OfficialLogRedactionMode {
126 pub fn as_str(self) -> &'static str {
128 match self {
129 Self::ContractBased => "contract_based",
130 Self::Always => "always",
131 }
132 }
133}
134
135impl FromStr for OfficialLogRedactionMode {
136 type Err = anyhow::Error;
137
138 fn from_str(value: &str) -> anyhow::Result<Self> {
140 match value.trim() {
141 "contract_based" => Ok(Self::ContractBased),
142 "always" => Ok(Self::Always),
143 other => anyhow::bail!(
144 "AGENTICS_OFFICIAL_LOG_REDACTION must be `contract_based` or `always`, got `{other}`"
145 ),
146 }
147 }
148}
149
150impl<'de> Deserialize<'de> for OfficialLogRedactionMode {
151 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
153 where
154 D: Deserializer<'de>,
155 {
156 let value = String::deserialize(deserializer)?;
157 Self::from_str(&value).map_err(serde::de::Error::custom)
158 }
159}
160
161impl WorkerAccelerators {
162 pub fn as_str(self) -> &'static str {
164 match self {
165 Self::None => "none",
166 Self::Gpu => "gpu",
167 }
168 }
169
170 pub fn supports(self, accelerator: TargetAccelerator) -> bool {
172 match (self, accelerator) {
173 (_, TargetAccelerator::None) | (Self::Gpu, TargetAccelerator::Gpu) => true,
174 (Self::None, TargetAccelerator::Gpu) => false,
175 }
176 }
177
178 pub fn heartbeat_values(self) -> Vec<String> {
180 match self {
181 Self::None => vec!["none".to_string()],
182 Self::Gpu => vec!["none".to_string(), "gpu".to_string()],
183 }
184 }
185}
186
187impl FromStr for WorkerAccelerators {
188 type Err = anyhow::Error;
189
190 fn from_str(value: &str) -> anyhow::Result<Self> {
192 match value.trim() {
193 "none" => Ok(Self::None),
194 "gpu" => Ok(Self::Gpu),
195 other => {
196 anyhow::bail!("AGENTICS_WORKER_ACCELERATORS must be `none` or `gpu`, got `{other}`")
197 }
198 }
199 }
200}
201
202impl AgentRegistrationMode {
203 pub fn as_str(self) -> &'static str {
205 match self {
206 Self::PioneerCode => "pioneer_code",
207 Self::Public => "public",
208 }
209 }
210}
211
212impl RunnerNamespace {
213 pub fn try_new(value: impl Into<String>) -> anyhow::Result<Self> {
215 let value = value.into();
216 let trimmed = value.trim();
217 if trimmed.is_empty() {
218 anyhow::bail!("{ENV_AGENTICS_RUNNER_NAMESPACE} must not be empty");
219 }
220 if trimmed.len() > 63 {
221 anyhow::bail!("{ENV_AGENTICS_RUNNER_NAMESPACE} must be at most 63 bytes");
222 }
223 if !trimmed
224 .bytes()
225 .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'_' | b'-'))
226 {
227 anyhow::bail!(
228 "{ENV_AGENTICS_RUNNER_NAMESPACE} may contain only ASCII letters, digits, '.', '_', and '-'"
229 );
230 }
231 Ok(Self(trimmed.to_string()))
232 }
233
234 pub fn as_str(&self) -> &str {
236 &self.0
237 }
238}
239
240impl FromStr for RunnerNamespace {
241 type Err = anyhow::Error;
242
243 fn from_str(value: &str) -> anyhow::Result<Self> {
245 Self::try_new(value)
246 }
247}
248
249impl<'de> Deserialize<'de> for RunnerNamespace {
250 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
252 where
253 D: Deserializer<'de>,
254 {
255 let value = String::deserialize(deserializer)?;
256 Self::from_str(&value).map_err(serde::de::Error::custom)
257 }
258}
259
260impl FromStr for AgentRegistrationMode {
261 type Err = anyhow::Error;
262
263 fn from_str(value: &str) -> anyhow::Result<Self> {
265 match value.trim() {
266 "pioneer_code" => Ok(Self::PioneerCode),
267 "public" => Ok(Self::Public),
268 other => anyhow::bail!(
269 "AGENTICS_AGENT_REGISTRATION_MODE must be `pioneer_code` or `public`, got `{other}`"
270 ),
271 }
272 }
273}
274
275impl<'de> Deserialize<'de> for AgentRegistrationMode {
276 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
278 where
279 D: Deserializer<'de>,
280 {
281 let value = String::deserialize(deserializer)?;
282 Self::from_str(&value).map_err(serde::de::Error::custom)
283 }
284}
285
286impl RunnerWritableStorageMode {
287 pub fn as_str(self) -> &'static str {
289 match self {
290 Self::Unbounded => "unbounded",
291 Self::XfsProjectQuotaSlots => "xfs-project-quota-slots",
292 }
293 }
294}
295
296impl FromStr for RunnerWritableStorageMode {
297 type Err = anyhow::Error;
298
299 fn from_str(value: &str) -> anyhow::Result<Self> {
301 match value.trim() {
302 "unbounded" => Ok(Self::Unbounded),
303 "xfs-project-quota-slots" => Ok(Self::XfsProjectQuotaSlots),
304 other => anyhow::bail!(
305 "AGENTICS_RUNNER_WRITABLE_STORAGE_MODE must be `unbounded` or `xfs-project-quota-slots`, got `{other}`"
306 ),
307 }
308 }
309}
310
311impl<'de> Deserialize<'de> for RunnerWritableStorageMode {
312 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
314 where
315 D: Deserializer<'de>,
316 {
317 let value = String::deserialize(deserializer)?;
318 Self::from_str(&value).map_err(serde::de::Error::custom)
319 }
320}