1mod extensions;
4#[cfg(test)]
5mod tests;
6use crate::config::{self, Field, StoredPayload};
7pub use extensions::{Profile, SavedSelection};
8use serde::{Deserialize, Deserializer, Serialize, Serializer};
9use serde_json::{Map, Value};
10use std::{collections::BTreeMap, path::PathBuf};
11
12fn missing<T>(field: &Field<T>) -> bool {
13 matches!(field, Field::Missing)
14}
15
16#[derive(Debug, Clone, Default)]
17pub struct Definitions(pub Option<Graph>);
18impl Definitions {
19 pub fn graph(&self) -> Option<&Graph> {
20 self.0.as_ref()
21 }
22 pub fn from_view(view: &crate::query::GraphView<'_>) -> Result<Self, serde_json::Error> {
23 serde_json::from_value(serde_json::to_value(view)?)
24 }
25}
26impl Serialize for Definitions {
27 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
28 self.0.serialize(serializer)
29 }
30}
31impl<'de> Deserialize<'de> for Definitions {
32 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
33 Option::<Graph>::deserialize(deserializer).map(Self)
34 }
35}
36
37#[derive(Debug, Clone, Default, Serialize, Deserialize)]
38#[serde(rename_all = "camelCase")]
39pub struct Graph {
40 #[serde(default, skip_serializing_if = "missing")]
41 pub version: Field<u32>,
42 #[serde(default, skip_serializing_if = "missing", with = "path_field")]
43 pub repo_path: Field<PathBuf>,
44 #[serde(default, skip_serializing_if = "missing")]
45 pub selection: Field<SavedSelection>,
46 #[serde(default, skip_serializing_if = "Field::missing")]
47 pub artifacts: Field<BTreeMap<crate::types::ArtifactName, Artifact>>,
48 #[serde(default, skip_serializing_if = "missing")]
49 pub evals: Field<Vec<Eval>>,
50 #[serde(default, skip_serializing_if = "missing")]
51 pub relations: Field<Vec<Relation>>,
52 #[serde(default, skip_serializing_if = "missing")]
53 pub components: Field<Vec<Component>>,
54 #[serde(flatten)]
55 pub extra: Map<String, Value>,
56}
57impl Graph {
58 pub fn artifacts(&self) -> impl Iterator<Item = (&crate::types::ArtifactName, &Artifact)> {
59 match &self.artifacts {
60 Field::Value(map) => Some(map),
61 _ => None,
62 }
63 .into_iter()
64 .flatten()
65 }
66 pub fn artifact(&self, id: &str) -> Option<&Artifact> {
67 match &self.artifacts {
68 Field::Value(map) => map.get(id),
69 _ => None,
70 }
71 }
72 pub fn evals(&self) -> &[Eval] {
73 match &self.evals {
74 Field::Value(evals) => evals,
75 _ => &[],
76 }
77 }
78 pub fn relations(&self) -> &[Relation] {
79 match &self.relations {
80 Field::Value(relations) => relations,
81 _ => &[],
82 }
83 }
84 pub fn components(&self) -> &[Component] {
85 match &self.components {
86 Field::Value(components) => components,
87 _ => &[],
88 }
89 }
90}
91
92#[derive(Debug, Clone, Default, Serialize, Deserialize)]
93#[serde(rename_all = "camelCase")]
94pub struct Artifact {
95 #[serde(default, skip_serializing_if = "missing")]
96 pub kind: Field<config::ArtifactKind>,
97 #[serde(default, skip_serializing_if = "missing", with = "path_field")]
98 pub path: Field<PathBuf>,
99 #[serde(default, skip_serializing_if = "missing")]
100 pub children: Field<BTreeMap<config::ChildPrefix, crate::types::ArtifactName>>,
101 #[serde(default, skip_serializing_if = "missing")]
102 pub name: Field<crate::types::ArtifactName>,
103 #[serde(default, skip_serializing_if = "missing")]
104 pub tags: Field<Vec<String>>,
105 #[serde(default, skip_serializing_if = "missing")]
106 pub views: Field<Views>,
107 #[serde(default, skip_serializing_if = "missing")]
108 pub mounts: Field<BTreeMap<config::MountAlias, crate::types::ArtifactName>>,
109 #[serde(default, skip_serializing_if = "missing")]
110 pub basis: Field<bool>,
111 #[serde(default, skip_serializing_if = "missing")]
112 pub fingerprint: Field<Fingerprint>,
113 #[serde(default, skip_serializing_if = "missing")]
114 pub review_policy: Field<Policy>,
115 #[serde(flatten)]
116 pub extra: Map<String, Value>,
117}
118#[derive(Debug, Clone, Default, Serialize, Deserialize)]
119#[serde(rename_all = "camelCase")]
120pub struct Views {
121 #[serde(default, skip_serializing_if = "missing")]
122 pub agent_tools: Field<BTreeMap<config::ToolOperationName, AgentTool>>,
123 #[serde(default, skip_serializing_if = "missing")]
124 pub human_tools: Field<BTreeMap<config::ToolOperationName, HumanTool>>,
125 #[serde(flatten)]
126 pub extra: Map<String, Value>,
127}
128#[derive(Debug, Clone, Serialize, Deserialize)]
129#[serde(untagged)]
130pub enum AgentTool {
131 Command(CommandTool),
132 Builtin(BuiltinTool),
133}
134#[derive(Debug, Clone, Serialize, Deserialize)]
135#[serde(rename_all = "camelCase")]
136pub struct CommandTool {
137 pub description: String,
138 pub input_schema: Value,
139 pub protocol: config::ToolProtocol,
140 pub command: String,
141 pub args: Vec<String>,
142 #[serde(default, skip_serializing_if = "missing", with = "duration_field")]
143 pub timeout_ms: Field<std::time::Duration>,
144 #[serde(default, skip_serializing_if = "missing")]
145 pub execution_paths: Field<Vec<String>>,
146 #[serde(flatten)]
147 pub extra: Map<String, Value>,
148}
149#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct BuiltinTool {
151 pub builtin: config::Builtin,
152 #[serde(default, skip_serializing_if = "missing")]
153 pub args: Field<Vec<String>>,
154 #[serde(default, skip_serializing_if = "missing")]
155 pub description: Field<String>,
156 #[serde(flatten)]
157 pub extra: Map<String, Value>,
158}
159#[derive(Debug, Clone, Serialize, Deserialize)]
160#[serde(untagged)]
161pub enum HumanTool {
162 Command(HumanCommandTool),
163 Builtin(HumanBuiltinTool),
164}
165#[derive(Debug, Clone, Serialize, Deserialize)]
166#[serde(rename_all = "camelCase")]
167pub struct HumanCommandTool {
168 pub description: String,
169 pub kind: config::HumanToolKind,
170 pub command: String,
171 pub args: Vec<String>,
172 #[serde(flatten)]
173 pub extra: Map<String, Value>,
174}
175#[derive(Debug, Clone, Serialize, Deserialize)]
176pub struct HumanBuiltinTool {
177 pub builtin: config::Builtin,
178 pub description: String,
179 pub kind: config::HumanToolKind,
180 pub args: Vec<String>,
181 #[serde(flatten)]
182 pub extra: Map<String, Value>,
183}
184#[derive(Debug, Clone, Default, Serialize, Deserialize)]
185#[serde(rename_all = "camelCase")]
186pub struct Fingerprint {
187 #[serde(default, skip_serializing_if = "missing")]
188 pub script: Field<Script>,
189 #[serde(default, skip_serializing_if = "missing")]
190 pub files: Field<Vec<String>>,
191 #[serde(default, skip_serializing_if = "missing")]
192 pub ignore: Field<Vec<String>>,
193 #[serde(flatten)]
194 pub extra: Map<String, Value>,
195}
196#[derive(Debug, Clone, Serialize, Deserialize)]
197#[serde(rename_all = "camelCase")]
198pub struct Script {
199 pub command: String,
200 pub args: Vec<String>,
201 #[serde(default, skip_serializing_if = "missing")]
202 pub files: Field<Vec<String>>,
203 #[serde(default, skip_serializing_if = "missing", with = "duration_field")]
204 pub timeout_ms: Field<std::time::Duration>,
205 #[serde(flatten)]
206 pub extra: Map<String, Value>,
207}
208#[derive(Debug, Clone, Default, Serialize, Deserialize)]
209#[serde(rename_all = "camelCase")]
210pub struct Policy {
211 #[serde(default, skip_serializing_if = "missing")]
212 pub dependency_gates: Field<config::DependencyGates>,
213 #[serde(flatten)]
214 pub extra: Map<String, Value>,
215}
216#[derive(Debug, Clone, Serialize, Deserialize)]
217pub struct Eval {
218 pub id: crate::types::EvalId,
219 pub target: crate::types::ArtifactName,
220 #[serde(default, skip_serializing_if = "missing")]
221 pub references: Field<BTreeMap<crate::config::ReferenceName, crate::types::ArtifactName>>,
222 #[serde(default, skip_serializing_if = "missing")]
223 pub deps: Field<Vec<crate::types::ArtifactName>>,
224 #[serde(default, skip_serializing_if = "missing")]
225 pub declaration: Field<Declaration>,
226 #[serde(flatten)]
227 pub extra: Map<String, Value>,
228}
229#[derive(Debug, Clone, Default, Serialize, Deserialize)]
230#[serde(rename_all = "camelCase")]
231pub struct Declaration {
232 #[serde(default, skip_serializing_if = "missing")]
233 pub id: Field<crate::config::LocalEvalId>,
234 #[serde(default, skip_serializing_if = "missing")]
235 pub title: Field<String>,
236 #[serde(default, skip_serializing_if = "missing")]
237 pub profile: Field<Profile>,
238 #[serde(default, skip_serializing_if = "missing")]
239 pub profile_variants: Field<BTreeMap<config::ProfileVariantName, Profile>>,
240 #[serde(default, skip_serializing_if = "missing")]
241 pub payload: Field<StoredPayload>,
242 #[serde(default, skip_serializing_if = "missing")]
243 pub pass_schema: Field<Map<String, Value>>,
244 #[serde(default, skip_serializing_if = "missing")]
245 pub fail_schema: Field<Map<String, Value>>,
246 #[serde(flatten)]
247 pub extra: Map<String, Value>,
248}
249#[derive(Debug, Clone, Default, Serialize, Deserialize)]
250pub struct Component {
251 #[serde(default, skip_serializing_if = "missing")]
252 pub id: Field<crate::graph::ComponentId>,
253 #[serde(default, skip_serializing_if = "missing")]
254 pub artifacts: Field<Vec<crate::types::ArtifactName>>,
255 #[serde(default, skip_serializing_if = "missing")]
256 pub dependencies: Field<Vec<crate::graph::ComponentId>>,
257 #[serde(default, skip_serializing_if = "missing")]
258 pub gates: Field<Vec<crate::types::EvalId>>,
259 #[serde(default, skip_serializing_if = "missing")]
260 pub cyclic: Field<bool>,
261 #[serde(flatten)]
262 pub extra: Map<String, Value>,
263}
264#[derive(Debug, Clone, Serialize)]
265pub struct Relation {
266 pub source: crate::types::ArtifactName,
267 pub target: crate::types::ArtifactName,
268 #[serde(flatten)]
269 pub kind: RelationKind,
270 #[serde(default, skip_serializing_if = "missing")]
271 pub cyclic: Field<bool>,
272 #[serde(flatten)]
273 pub extra: Map<String, Value>,
274}
275impl<'de> Deserialize<'de> for Relation {
276 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
277 let mut fields = Map::<String, Value>::deserialize(deserializer)?;
280 let mut take = |name: &str| {
281 fields
282 .remove(name)
283 .ok_or_else(|| serde::de::Error::custom(format!("missing relation {name}")))
284 };
285 let source = serde_json::from_value(take("source")?).map_err(serde::de::Error::custom)?;
286 let target = serde_json::from_value(take("target")?).map_err(serde::de::Error::custom)?;
287 let kind_name: String =
288 serde_json::from_value(take("kind")?).map_err(serde::de::Error::custom)?;
289 let names: &[&str] = match kind_name.as_str() {
290 "child" => &["path"],
291 "mount" => &["alias"],
292 "instruction" | "dependency" => &["evalId", "name"],
293 "argv" => &["evalId", "index", "name", "path"],
294 _ => &[],
295 };
296 let unknown = names.is_empty();
297 let mut kind = Map::from_iter([("kind".into(), Value::String(kind_name.clone()))]);
298 for name in names {
299 if let Some(value) = fields.remove(*name) {
300 kind.insert((*name).into(), value);
301 }
302 }
303 let kind = if unknown {
304 RelationKind::Unknown { kind: kind_name }
305 } else {
306 let parsed =
307 serde_json::from_value(Value::Object(kind)).map_err(serde::de::Error::custom)?;
308 if matches!(parsed, RelationKind::Unknown { .. }) {
309 return Err(serde::de::Error::custom(format!(
310 "Invalid saved {kind_name} relation."
311 )));
312 }
313 parsed
314 };
315 let cyclic = match fields.remove("cyclic") {
316 Some(value) => serde_json::from_value(value).map_err(serde::de::Error::custom)?,
317 None => Field::Missing,
318 };
319 Ok(Self {
320 source,
321 target,
322 kind,
323 cyclic,
324 extra: fields,
325 })
326 }
327}
328#[derive(Debug, Clone, Serialize, Deserialize)]
329#[serde(tag = "kind", rename_all = "lowercase")]
330pub enum RelationKind {
331 Child {
332 #[serde(default, skip_serializing_if = "missing")]
333 path: Field<config::ChildPrefix>,
334 },
335 Mount {
336 #[serde(default, skip_serializing_if = "missing")]
337 alias: Field<config::MountAlias>,
338 },
339 Dependency {
340 #[serde(rename = "evalId", default, skip_serializing_if = "missing")]
341 eval_id: Field<crate::types::EvalId>,
342 #[serde(default, skip_serializing_if = "missing")]
343 name: Field<config::ReferenceName>,
344 },
345 Instruction {
346 #[serde(rename = "evalId")]
347 eval_id: crate::types::EvalId,
348 name: config::ReferenceName,
349 },
350 #[serde(rename = "argv")]
351 Argument {
352 #[serde(rename = "evalId")]
353 eval_id: crate::types::EvalId,
354 index: usize,
355 name: config::ReferenceName,
356 path: String,
357 },
358 #[serde(untagged)]
359 Unknown { kind: String },
360}
361
362mod duration_field {
363 use crate::config::Field;
364 use serde::{Deserializer, Serializer};
365 use std::time::Duration;
366 pub fn serialize<S: Serializer>(
367 value: &Field<Duration>,
368 serializer: S,
369 ) -> Result<S::Ok, S::Error> {
370 match value {
371 Field::Value(value) => {
372 let milliseconds = value.as_millis();
373 if !(1..=u128::from(crate::config::validation::MAX_TIMEOUT_MS))
374 .contains(&milliseconds)
375 || Duration::from_millis(milliseconds as u64) != *value
376 {
377 return Err(serde::ser::Error::custom(
378 "Saved definition timeoutMs must be whole milliseconds from 1 through 2147483647.",
379 ));
380 }
381 serializer.serialize_u128(milliseconds)
382 }
383 _ => serializer.serialize_none(),
384 }
385 }
386 pub fn deserialize<'de, D: Deserializer<'de>>(
387 deserializer: D,
388 ) -> Result<Field<Duration>, D::Error> {
389 crate::config::validation::milliseconds::deserialize(deserializer)
390 .map(|value| value.map_or(Field::Null, Field::Value))
391 }
392}
393
394pub(super) mod path_field {
395 use crate::config::Field;
396 use serde::{Deserializer, Serializer};
397 use std::path::PathBuf;
398 pub fn serialize<S: Serializer>(
399 value: &Field<PathBuf>,
400 serializer: S,
401 ) -> Result<S::Ok, S::Error> {
402 match value {
403 Field::Value(path) => crate::platform::path_serde::serialize(path, serializer),
404 _ => serializer.serialize_none(),
405 }
406 }
407 pub fn deserialize<'de, D: Deserializer<'de>>(
408 deserializer: D,
409 ) -> Result<Field<PathBuf>, D::Error> {
410 crate::platform::path_serde::option::deserialize(deserializer)
411 .map(|value| value.map_or(Field::Null, Field::Value))
412 }
413}