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