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 basis: Field<bool>,
109 #[serde(default, skip_serializing_if = "missing")]
110 pub fingerprint: Field<Fingerprint>,
111 #[serde(flatten)]
112 pub extra: Map<String, Value>,
113}
114#[derive(Debug, Clone, Default, Serialize, Deserialize)]
115#[serde(rename_all = "camelCase")]
116pub struct Views {
117 #[serde(default, skip_serializing_if = "missing")]
118 pub agent_tools: Field<BTreeMap<config::ToolOperationName, AgentTool>>,
119 #[serde(default, skip_serializing_if = "missing")]
120 pub human_tools: Field<BTreeMap<config::ToolOperationName, HumanTool>>,
121 #[serde(flatten)]
122 pub extra: Map<String, Value>,
123}
124#[derive(Debug, Clone, Serialize, Deserialize)]
125#[serde(untagged)]
126pub enum AgentTool {
127 Command(CommandTool),
128 Builtin(BuiltinTool),
129}
130#[derive(Debug, Clone, Serialize, Deserialize)]
131#[serde(rename_all = "camelCase")]
132pub struct CommandTool {
133 pub description: String,
134 pub input_schema: Value,
135 pub protocol: config::ToolProtocol,
136 pub command: String,
137 pub args: Vec<String>,
138 #[serde(default, skip_serializing_if = "missing", with = "duration_field")]
139 pub timeout_ms: Field<std::time::Duration>,
140 #[serde(default, skip_serializing_if = "missing")]
141 pub execution_paths: Field<Vec<String>>,
142 #[serde(flatten)]
143 pub extra: Map<String, Value>,
144}
145#[derive(Debug, Clone, Serialize, Deserialize)]
146pub struct BuiltinTool {
147 pub builtin: config::Builtin,
148 #[serde(default, skip_serializing_if = "missing")]
149 pub args: Field<Vec<String>>,
150 #[serde(default, skip_serializing_if = "missing")]
151 pub description: Field<String>,
152 #[serde(flatten)]
153 pub extra: Map<String, Value>,
154}
155#[derive(Debug, Clone, Serialize, Deserialize)]
156#[serde(untagged)]
157pub enum HumanTool {
158 Command(HumanCommandTool),
159 Builtin(HumanBuiltinTool),
160}
161#[derive(Debug, Clone, Serialize, Deserialize)]
162#[serde(rename_all = "camelCase")]
163pub struct HumanCommandTool {
164 pub description: String,
165 pub kind: config::HumanToolKind,
166 pub command: String,
167 pub args: Vec<String>,
168 #[serde(flatten)]
169 pub extra: Map<String, Value>,
170}
171#[derive(Debug, Clone, Serialize, Deserialize)]
172pub struct HumanBuiltinTool {
173 pub builtin: config::Builtin,
174 pub description: String,
175 pub kind: config::HumanToolKind,
176 pub args: Vec<String>,
177 #[serde(flatten)]
178 pub extra: Map<String, Value>,
179}
180#[derive(Debug, Clone, Default, Serialize, Deserialize)]
181#[serde(rename_all = "camelCase")]
182pub struct Fingerprint {
183 #[serde(default, skip_serializing_if = "missing")]
184 pub script: Field<Script>,
185 #[serde(default, skip_serializing_if = "missing")]
186 pub files: Field<Vec<String>>,
187 #[serde(default, skip_serializing_if = "missing")]
188 pub ignore: Field<Vec<String>>,
189 #[serde(flatten)]
190 pub extra: Map<String, Value>,
191}
192#[derive(Debug, Clone, Serialize, Deserialize)]
193#[serde(rename_all = "camelCase")]
194pub struct Script {
195 pub command: String,
196 pub args: Vec<String>,
197 #[serde(default, skip_serializing_if = "missing")]
198 pub files: Field<Vec<String>>,
199 #[serde(default, skip_serializing_if = "missing", with = "duration_field")]
200 pub timeout_ms: Field<std::time::Duration>,
201 #[serde(flatten)]
202 pub extra: Map<String, Value>,
203}
204#[derive(Debug, Clone, Serialize, Deserialize)]
205pub struct Eval {
206 pub id: crate::types::EvalId,
207 pub target: crate::types::ArtifactName,
208 #[serde(default, skip_serializing_if = "missing")]
209 pub references: Field<BTreeMap<crate::config::ReferenceName, crate::types::ArtifactName>>,
210 #[serde(default, skip_serializing_if = "missing")]
211 pub deps: Field<Vec<crate::types::ArtifactName>>,
212 #[serde(default, skip_serializing_if = "missing")]
213 pub declaration: Field<Declaration>,
214 #[serde(flatten)]
215 pub extra: Map<String, Value>,
216}
217#[derive(Debug, Clone, Default, Serialize, Deserialize)]
218#[serde(rename_all = "camelCase")]
219pub struct Declaration {
220 #[serde(default, skip_serializing_if = "missing")]
221 pub mutex: Field<Vec<config::MutexTag>>,
222 #[serde(default, skip_serializing_if = "missing")]
223 pub gate: Field<config::Gate>,
224 #[serde(default, skip_serializing_if = "missing")]
225 pub id: Field<crate::config::LocalEvalId>,
226 #[serde(default, skip_serializing_if = "missing")]
227 pub title: Field<String>,
228 #[serde(default, skip_serializing_if = "missing")]
229 pub profile: Field<Profile>,
230 #[serde(default, skip_serializing_if = "missing")]
231 pub profile_variants: Field<BTreeMap<config::ProfileVariantName, Profile>>,
232 #[serde(default, skip_serializing_if = "missing")]
233 pub payload: Field<StoredPayload>,
234 #[serde(default, skip_serializing_if = "missing")]
235 pub pass_schema: Field<Map<String, Value>>,
236 #[serde(default, skip_serializing_if = "missing")]
237 pub fail_schema: Field<Map<String, Value>>,
238 #[serde(flatten)]
239 pub extra: Map<String, Value>,
240}
241#[derive(Debug, Clone, Default, Serialize, Deserialize)]
242pub struct Component {
243 #[serde(default, skip_serializing_if = "missing")]
244 pub id: Field<crate::graph::ComponentId>,
245 #[serde(default, skip_serializing_if = "missing")]
246 pub artifacts: Field<Vec<crate::types::ArtifactName>>,
247 #[serde(default, skip_serializing_if = "missing")]
248 pub dependencies: Field<Vec<crate::graph::ComponentId>>,
249 #[serde(default, skip_serializing_if = "missing")]
250 pub gates: Field<Vec<crate::types::EvalId>>,
251 #[serde(default, skip_serializing_if = "missing")]
252 pub cyclic: Field<bool>,
253 #[serde(flatten)]
254 pub extra: Map<String, Value>,
255}
256#[derive(Debug, Clone, Serialize)]
257pub struct Relation {
258 pub source: crate::types::ArtifactName,
259 pub target: crate::types::ArtifactName,
260 #[serde(flatten)]
261 pub kind: RelationKind,
262 #[serde(default, skip_serializing_if = "missing")]
263 pub cyclic: Field<bool>,
264 #[serde(flatten)]
265 pub extra: Map<String, Value>,
266}
267impl<'de> Deserialize<'de> for Relation {
268 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
269 let mut fields = Map::<String, Value>::deserialize(deserializer)?;
272 let mut take = |name: &str| {
273 fields
274 .remove(name)
275 .ok_or_else(|| serde::de::Error::custom(format!("missing relation {name}")))
276 };
277 let source = serde_json::from_value(take("source")?).map_err(serde::de::Error::custom)?;
278 let target = serde_json::from_value(take("target")?).map_err(serde::de::Error::custom)?;
279 let kind_name: String =
280 serde_json::from_value(take("kind")?).map_err(serde::de::Error::custom)?;
281 let names: &[&str] = match kind_name.as_str() {
282 "child" => &["path"],
283 "instruction" => &["evalId", "name"],
284 "argv" => &["evalId", "index", "name", "path"],
285 _ => &[],
286 };
287 let unknown = names.is_empty();
288 let mut kind = Map::from_iter([("kind".into(), Value::String(kind_name.clone()))]);
289 for name in names {
290 if let Some(value) = fields.remove(*name) {
291 kind.insert((*name).into(), value);
292 }
293 }
294 let kind = if unknown {
295 RelationKind::Unknown { kind: kind_name }
296 } else {
297 let parsed =
298 serde_json::from_value(Value::Object(kind)).map_err(serde::de::Error::custom)?;
299 if matches!(parsed, RelationKind::Unknown { .. }) {
300 return Err(serde::de::Error::custom(format!(
301 "Invalid saved {kind_name} relation."
302 )));
303 }
304 parsed
305 };
306 let cyclic = match fields.remove("cyclic") {
307 Some(value) => serde_json::from_value(value).map_err(serde::de::Error::custom)?,
308 None => Field::Missing,
309 };
310 Ok(Self {
311 source,
312 target,
313 kind,
314 cyclic,
315 extra: fields,
316 })
317 }
318}
319#[derive(Debug, Clone, Serialize, Deserialize)]
320#[serde(tag = "kind", rename_all = "lowercase")]
321pub enum RelationKind {
322 Child {
323 #[serde(default, skip_serializing_if = "missing")]
324 path: Field<config::ChildPrefix>,
325 },
326 Instruction {
327 #[serde(rename = "evalId")]
328 eval_id: crate::types::EvalId,
329 name: config::ReferenceName,
330 },
331 #[serde(rename = "argv")]
332 Argument {
333 #[serde(rename = "evalId")]
334 eval_id: crate::types::EvalId,
335 index: usize,
336 name: config::ReferenceName,
337 path: String,
338 },
339 #[serde(untagged)]
340 Unknown { kind: String },
341}
342
343mod duration_field {
344 use crate::config::Field;
345 use serde::{Deserializer, Serializer};
346 use std::time::Duration;
347 pub fn serialize<S: Serializer>(
348 value: &Field<Duration>,
349 serializer: S,
350 ) -> Result<S::Ok, S::Error> {
351 match value {
352 Field::Value(value) => {
353 let milliseconds = value.as_millis();
354 if !(1..=u128::from(crate::config::validation::MAX_TIMEOUT_MS))
355 .contains(&milliseconds)
356 || Duration::from_millis(milliseconds as u64) != *value
357 {
358 return Err(serde::ser::Error::custom(
359 "Saved definition timeoutMs must be whole milliseconds from 1 through 2147483647.",
360 ));
361 }
362 serializer.serialize_u128(milliseconds)
363 }
364 _ => serializer.serialize_none(),
365 }
366 }
367 pub fn deserialize<'de, D: Deserializer<'de>>(
368 deserializer: D,
369 ) -> Result<Field<Duration>, D::Error> {
370 crate::config::validation::milliseconds::deserialize(deserializer)
371 .map(|value| value.map_or(Field::Null, Field::Value))
372 }
373}
374
375pub(super) mod path_field {
376 use crate::config::Field;
377 use serde::{Deserializer, Serializer};
378 use std::path::PathBuf;
379 pub fn serialize<S: Serializer>(
380 value: &Field<PathBuf>,
381 serializer: S,
382 ) -> Result<S::Ok, S::Error> {
383 match value {
384 Field::Value(path) => crate::platform::path_serde::serialize(path, serializer),
385 _ => serializer.serialize_none(),
386 }
387 }
388 pub fn deserialize<'de, D: Deserializer<'de>>(
389 deserializer: D,
390 ) -> Result<Field<PathBuf>, D::Error> {
391 crate::platform::path_serde::option::deserialize(deserializer)
392 .map(|value| value.map_or(Field::Null, Field::Value))
393 }
394}