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