use std::str::FromStr;
use agentics_domain::models::challenge::TargetAccelerator;
use serde::{Deserialize, Deserializer};
use crate::ENV_AGENTICS_RUNNER_NAMESPACE;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RunnerWritableStorageMode {
Unbounded,
XfsProjectQuotaSlots,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AgentRegistrationMode {
PioneerCode,
Public,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum HostProbeMode {
Off,
Warn,
Require,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RunnerSecurityProfile {
Development,
Production,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OfficialLogRedactionMode {
ContractBased,
Always,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct RunnerNamespace(String);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum WorkerAccelerators {
None,
Gpu,
}
impl HostProbeMode {
pub fn as_str(self) -> &'static str {
match self {
Self::Off => "off",
Self::Warn => "warn",
Self::Require => "require",
}
}
}
impl FromStr for HostProbeMode {
type Err = anyhow::Error;
fn from_str(value: &str) -> anyhow::Result<Self> {
match value.trim() {
"off" => Ok(Self::Off),
"warn" => Ok(Self::Warn),
"require" => Ok(Self::Require),
other => anyhow::bail!(
"AGENTICS_HOST_PROBE_MODE must be `off`, `warn`, or `require`, got `{other}`"
),
}
}
}
impl RunnerSecurityProfile {
pub fn as_str(self) -> &'static str {
match self {
Self::Development => "development",
Self::Production => "production",
}
}
}
impl FromStr for RunnerSecurityProfile {
type Err = anyhow::Error;
fn from_str(value: &str) -> anyhow::Result<Self> {
match value.trim() {
"development" => Ok(Self::Development),
"production" => Ok(Self::Production),
other => anyhow::bail!(
"AGENTICS_RUNNER_SECURITY_PROFILE must be `development` or `production`, got `{other}`"
),
}
}
}
impl OfficialLogRedactionMode {
pub fn as_str(self) -> &'static str {
match self {
Self::ContractBased => "contract_based",
Self::Always => "always",
}
}
}
impl FromStr for OfficialLogRedactionMode {
type Err = anyhow::Error;
fn from_str(value: &str) -> anyhow::Result<Self> {
match value.trim() {
"contract_based" => Ok(Self::ContractBased),
"always" => Ok(Self::Always),
other => anyhow::bail!(
"AGENTICS_OFFICIAL_LOG_REDACTION must be `contract_based` or `always`, got `{other}`"
),
}
}
}
impl<'de> Deserialize<'de> for OfficialLogRedactionMode {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value = String::deserialize(deserializer)?;
Self::from_str(&value).map_err(serde::de::Error::custom)
}
}
impl WorkerAccelerators {
pub fn as_str(self) -> &'static str {
match self {
Self::None => "none",
Self::Gpu => "gpu",
}
}
pub fn supports(self, accelerator: TargetAccelerator) -> bool {
match (self, accelerator) {
(_, TargetAccelerator::None) | (Self::Gpu, TargetAccelerator::Gpu) => true,
(Self::None, TargetAccelerator::Gpu) => false,
}
}
pub fn heartbeat_values(self) -> Vec<String> {
match self {
Self::None => vec!["none".to_string()],
Self::Gpu => vec!["none".to_string(), "gpu".to_string()],
}
}
}
impl FromStr for WorkerAccelerators {
type Err = anyhow::Error;
fn from_str(value: &str) -> anyhow::Result<Self> {
match value.trim() {
"none" => Ok(Self::None),
"gpu" => Ok(Self::Gpu),
other => {
anyhow::bail!("AGENTICS_WORKER_ACCELERATORS must be `none` or `gpu`, got `{other}`")
}
}
}
}
impl AgentRegistrationMode {
pub fn as_str(self) -> &'static str {
match self {
Self::PioneerCode => "pioneer_code",
Self::Public => "public",
}
}
}
impl RunnerNamespace {
pub fn try_new(value: impl Into<String>) -> anyhow::Result<Self> {
let value = value.into();
let trimmed = value.trim();
if trimmed.is_empty() {
anyhow::bail!("{ENV_AGENTICS_RUNNER_NAMESPACE} must not be empty");
}
if trimmed.len() > 63 {
anyhow::bail!("{ENV_AGENTICS_RUNNER_NAMESPACE} must be at most 63 bytes");
}
if !trimmed
.bytes()
.all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'_' | b'-'))
{
anyhow::bail!(
"{ENV_AGENTICS_RUNNER_NAMESPACE} may contain only ASCII letters, digits, '.', '_', and '-'"
);
}
Ok(Self(trimmed.to_string()))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl FromStr for RunnerNamespace {
type Err = anyhow::Error;
fn from_str(value: &str) -> anyhow::Result<Self> {
Self::try_new(value)
}
}
impl<'de> Deserialize<'de> for RunnerNamespace {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value = String::deserialize(deserializer)?;
Self::from_str(&value).map_err(serde::de::Error::custom)
}
}
impl FromStr for AgentRegistrationMode {
type Err = anyhow::Error;
fn from_str(value: &str) -> anyhow::Result<Self> {
match value.trim() {
"pioneer_code" => Ok(Self::PioneerCode),
"public" => Ok(Self::Public),
other => anyhow::bail!(
"AGENTICS_AGENT_REGISTRATION_MODE must be `pioneer_code` or `public`, got `{other}`"
),
}
}
}
impl<'de> Deserialize<'de> for AgentRegistrationMode {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value = String::deserialize(deserializer)?;
Self::from_str(&value).map_err(serde::de::Error::custom)
}
}
impl RunnerWritableStorageMode {
pub fn as_str(self) -> &'static str {
match self {
Self::Unbounded => "unbounded",
Self::XfsProjectQuotaSlots => "xfs-project-quota-slots",
}
}
}
impl FromStr for RunnerWritableStorageMode {
type Err = anyhow::Error;
fn from_str(value: &str) -> anyhow::Result<Self> {
match value.trim() {
"unbounded" => Ok(Self::Unbounded),
"xfs-project-quota-slots" => Ok(Self::XfsProjectQuotaSlots),
other => anyhow::bail!(
"AGENTICS_RUNNER_WRITABLE_STORAGE_MODE must be `unbounded` or `xfs-project-quota-slots`, got `{other}`"
),
}
}
}
impl<'de> Deserialize<'de> for RunnerWritableStorageMode {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value = String::deserialize(deserializer)?;
Self::from_str(&value).map_err(serde::de::Error::custom)
}
}