agentics_domain/models/challenge/
targets.rs1use std::borrow::Cow;
2use std::fmt;
3
4use schemars::{Schema, SchemaGenerator, json_schema};
5use serde::de::{Error as DeError, Visitor};
6use serde::{Deserialize, Deserializer, Serialize, Serializer};
7
8use super::super::images::ChallengeImageReference;
9use super::super::names::{ResourceProfileName, TargetName};
10use crate::zip_project::ZipProjectNetworkAccess;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
14pub enum DockerPlatform {
15 #[serde(rename = "linux/arm64")]
16 LinuxArm64,
17 #[serde(rename = "linux/amd64")]
18 LinuxAmd64,
19}
20
21impl DockerPlatform {
22 pub fn as_str(self) -> &'static str {
24 match self {
25 Self::LinuxArm64 => "linux/arm64",
26 Self::LinuxAmd64 => "linux/amd64",
27 }
28 }
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub enum TargetAccelerator {
34 None,
35 Gpu,
36}
37
38impl TargetAccelerator {
39 pub fn as_str(self) -> &'static str {
41 match self {
42 Self::None => "none",
43 Self::Gpu => "gpu",
44 }
45 }
46
47 pub fn from_storage_value(value: &str) -> Option<Self> {
49 match value {
50 "none" => Some(Self::None),
51 "gpu" => Some(Self::Gpu),
52 _ => None,
53 }
54 }
55}
56
57impl Serialize for TargetAccelerator {
58 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
60 where
61 S: Serializer,
62 {
63 match self {
64 Self::None => serializer.serialize_none(),
65 Self::Gpu => serializer.serialize_str("gpu"),
66 }
67 }
68}
69
70impl<'de> Deserialize<'de> for TargetAccelerator {
71 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
73 where
74 D: Deserializer<'de>,
75 {
76 struct TargetAcceleratorVisitor;
77
78 impl<'de> Visitor<'de> for TargetAcceleratorVisitor {
79 type Value = TargetAccelerator;
80
81 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
82 formatter.write_str("null for no accelerator or \"gpu\" for GPU acceleration")
83 }
84
85 fn visit_none<E>(self) -> std::result::Result<Self::Value, E>
86 where
87 E: DeError,
88 {
89 Ok(TargetAccelerator::None)
90 }
91
92 fn visit_unit<E>(self) -> std::result::Result<Self::Value, E>
93 where
94 E: DeError,
95 {
96 Ok(TargetAccelerator::None)
97 }
98
99 fn visit_some<D>(self, deserializer: D) -> std::result::Result<Self::Value, D::Error>
100 where
101 D: Deserializer<'de>,
102 {
103 deserializer.deserialize_any(self)
104 }
105
106 fn visit_str<E>(self, value: &str) -> std::result::Result<Self::Value, E>
107 where
108 E: DeError,
109 {
110 match value {
111 "gpu" => Ok(TargetAccelerator::Gpu),
112 "cpu" => Err(E::custom(
113 "accelerator must be explicit null when no accelerator is required, not \"cpu\"",
114 )),
115 other => Err(E::unknown_variant(other, &["gpu"])),
116 }
117 }
118 }
119
120 deserializer.deserialize_any(TargetAcceleratorVisitor)
121 }
122}
123
124impl schemars::JsonSchema for TargetAccelerator {
125 fn inline_schema() -> bool {
127 true
128 }
129
130 fn schema_name() -> Cow<'static, str> {
132 Cow::Borrowed("TargetAccelerator")
133 }
134
135 fn json_schema(_generator: &mut SchemaGenerator) -> Schema {
137 json_schema!({
138 "x-agentics-preserve-null": true,
139 "oneOf": [
140 { "type": "null" },
141 { "type": "string", "enum": ["gpu"] }
142 ]
143 })
144 }
145}
146
147#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
149#[serde(deny_unknown_fields)]
150pub struct ChallengeTargetSpec {
151 pub name: TargetName,
152 pub docker_platform: DockerPlatform,
153 pub accelerator: TargetAccelerator,
155 pub validation_enabled: bool,
156 pub resource_profile: ResourceProfileSpec,
157}
158
159#[derive(Debug, Clone, Serialize, Deserialize, garde::Validate, schemars::JsonSchema)]
161#[garde(allow_unvalidated)]
162#[serde(deny_unknown_fields)]
163pub struct ResourceProfileSpec {
164 pub name: ResourceProfileName,
165 #[serde(default, skip_serializing_if = "Option::is_none")]
166 #[garde(custom(crate::validation::optional_trimmed_non_empty))]
167 pub resource_description: Option<String>,
168 pub solution_image: ChallengeImageReference,
169 pub evaluator_image: ChallengeImageReference,
170 pub solution: SolutionStageProfiles,
171 pub evaluator: EvaluatorStageProfiles,
172 #[serde(default, skip_serializing_if = "Option::is_none")]
173 pub hardware_metadata: Option<HardwareProfileSpec>,
174}
175
176#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
178#[serde(deny_unknown_fields)]
179pub struct SolutionStageProfiles {
180 pub setup: StageResourceProfile,
181 pub build: StageResourceProfile,
182 #[serde(default, skip_serializing_if = "Option::is_none")]
183 pub run: Option<StageResourceProfile>,
184}
185
186#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
188#[serde(deny_unknown_fields)]
189pub struct EvaluatorStageProfiles {
190 pub setup: StageResourceProfile,
191 pub run: StageResourceProfile,
192}
193
194#[derive(Debug, Clone, Serialize, Deserialize, garde::Validate, schemars::JsonSchema)]
196#[serde(deny_unknown_fields)]
197pub struct StageResourceProfile {
198 #[garde(range(min = 1))]
199 pub timeout_sec: u64,
200 #[garde(range(min = 1))]
201 pub memory_limit_mb: u64,
202 #[garde(range(min = 1))]
203 pub cpu_limit_millis: u32,
204 #[garde(range(min = 1))]
205 pub disk_limit_mb: u64,
206 #[garde(skip)]
207 pub network_access: ZipProjectNetworkAccess,
208}
209
210#[derive(Debug, Clone, Serialize, Deserialize, garde::Validate, schemars::JsonSchema)]
212#[garde(allow_unvalidated)]
213pub struct HardwareProfileSpec {
214 #[garde(custom(crate::validation::trimmed_non_empty))]
215 pub kind: String,
216 #[serde(default, skip_serializing_if = "Option::is_none")]
217 #[garde(custom(crate::validation::optional_trimmed_non_empty))]
218 pub gpu_model: Option<String>,
219 #[serde(default, skip_serializing_if = "Option::is_none")]
220 #[garde(range(min = 1))]
221 pub gpu_count: Option<u32>,
222 #[serde(default, skip_serializing_if = "Option::is_none")]
223 #[garde(range(min = 1))]
224 pub gpu_memory_gb: Option<u64>,
225 #[serde(default, skip_serializing_if = "Option::is_none")]
226 #[garde(custom(crate::validation::optional_trimmed_non_empty))]
227 pub cuda_variant: Option<String>,
228 #[serde(default, skip_serializing_if = "Option::is_none")]
229 #[garde(custom(crate::validation::optional_trimmed_non_empty))]
230 pub cuda_version: Option<String>,
231 #[serde(default, skip_serializing_if = "Option::is_none")]
232 #[garde(custom(crate::validation::optional_trimmed_non_empty))]
233 pub driver_minimum: Option<String>,
234}