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