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 children: 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: &[&str] = match kind_name.as_str() {
315 "child" => &["path"],
316 "instruction" => &["evalId", "name"],
317 "argv" => &["evalId", "index", "name", "path"],
318 _ => &[],
319 };
320 let unknown = names.is_empty();
321 let mut kind = Map::from_iter([("kind".into(), Value::String(kind_name.clone()))]);
322 for name in names {
323 if let Some(value) = fields.remove(*name) {
324 kind.insert((*name).into(), value);
325 }
326 }
327 let kind = if unknown {
328 RelationKind::Unknown { kind: kind_name }
329 } else {
330 let parsed =
331 serde_json::from_value(Value::Object(kind)).map_err(serde::de::Error::custom)?;
332 if matches!(parsed, RelationKind::Unknown { .. }) {
333 return Err(serde::de::Error::custom(format!(
334 "Invalid saved {kind_name} relation."
335 )));
336 }
337 parsed
338 };
339 let cyclic = match fields.remove("cyclic") {
340 Some(value) => serde_json::from_value(value).map_err(serde::de::Error::custom)?,
341 None => Field::Missing,
342 };
343 Ok(Self {
344 source,
345 target,
346 kind,
347 cyclic,
348 extra: fields,
349 })
350 }
351}
352#[derive(Debug, Clone, Serialize, Deserialize)]
353#[serde(tag = "kind", rename_all = "lowercase")]
354pub enum RelationKind {
355 Child {
356 #[serde(default, skip_serializing_if = "missing")]
357 path: Field<config::ChildPrefix>,
358 },
359 Instruction {
360 #[serde(rename = "evalId")]
361 eval_id: crate::types::EvalId,
362 name: config::ReferenceName,
363 },
364 #[serde(rename = "argv")]
365 Argument {
366 #[serde(rename = "evalId")]
367 eval_id: crate::types::EvalId,
368 index: usize,
369 name: config::ReferenceName,
370 path: String,
371 },
372 #[serde(untagged)]
373 Unknown { kind: String },
374}
375
376mod duration_field {
377 use crate::config::Field;
378 use serde::{Deserializer, Serializer};
379 use std::time::Duration;
380 pub fn serialize<S: Serializer>(
381 value: &Field<Duration>,
382 serializer: S,
383 ) -> Result<S::Ok, S::Error> {
384 match value {
385 Field::Value(value) => {
386 let milliseconds = value.as_millis();
387 if !(1..=u128::from(crate::config::validation::MAX_TIMEOUT_MS))
388 .contains(&milliseconds)
389 || Duration::from_millis(milliseconds as u64) != *value
390 {
391 return Err(serde::ser::Error::custom(
392 "Saved definition timeoutMs must be whole milliseconds from 1 through 2147483647.",
393 ));
394 }
395 serializer.serialize_u128(milliseconds)
396 }
397 _ => serializer.serialize_none(),
398 }
399 }
400 pub fn deserialize<'de, D: Deserializer<'de>>(
401 deserializer: D,
402 ) -> Result<Field<Duration>, D::Error> {
403 crate::config::validation::milliseconds::deserialize(deserializer)
404 .map(|value| value.map_or(Field::Null, Field::Value))
405 }
406}
407
408pub(super) mod path_field {
409 use crate::config::Field;
410 use serde::{Deserializer, Serializer};
411 use std::path::PathBuf;
412 pub fn serialize<S: Serializer>(
413 value: &Field<PathBuf>,
414 serializer: S,
415 ) -> Result<S::Ok, S::Error> {
416 match value {
417 Field::Value(path) => crate::platform::path_serde::serialize(path, serializer),
418 _ => serializer.serialize_none(),
419 }
420 }
421 pub fn deserialize<'de, D: Deserializer<'de>>(
422 deserializer: D,
423 ) -> Result<Field<PathBuf>, D::Error> {
424 crate::platform::path_serde::option::deserialize(deserializer)
425 .map(|value| value.map_or(Field::Null, Field::Value))
426 }
427}