mod extensions;
#[cfg(test)]
mod tests;
use crate::config::{self, Field, StoredPayload};
pub use extensions::{Profile, SavedSelection};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_json::{Map, Value};
use std::{collections::BTreeMap, path::PathBuf};
fn missing<T>(field: &Field<T>) -> bool {
matches!(field, Field::Missing)
}
#[derive(Debug, Clone, Default)]
pub struct Definitions(pub Option<Graph>);
impl Definitions {
pub fn graph(&self) -> Option<&Graph> {
self.0.as_ref()
}
pub fn from_view(view: &crate::query::GraphView<'_>) -> Result<Self, serde_json::Error> {
serde_json::from_value(serde_json::to_value(view)?)
}
}
impl Serialize for Definitions {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.0.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for Definitions {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
Option::<Graph>::deserialize(deserializer).map(Self)
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Graph {
#[serde(default, skip_serializing_if = "missing")]
pub version: Field<u32>,
#[serde(default, skip_serializing_if = "missing", with = "path_field")]
pub repo_path: Field<PathBuf>,
#[serde(default, skip_serializing_if = "missing")]
pub selection: Field<SavedSelection>,
#[serde(default, skip_serializing_if = "Field::missing")]
pub artifacts: Field<BTreeMap<crate::types::ArtifactName, Artifact>>,
#[serde(default, skip_serializing_if = "missing")]
pub evals: Field<Vec<Eval>>,
#[serde(default, skip_serializing_if = "missing")]
pub relations: Field<Vec<Relation>>,
#[serde(default, skip_serializing_if = "missing")]
pub components: Field<Vec<Component>>,
#[serde(flatten)]
pub extra: Map<String, Value>,
}
impl Graph {
pub fn artifacts(&self) -> impl Iterator<Item = (&crate::types::ArtifactName, &Artifact)> {
match &self.artifacts {
Field::Value(map) => Some(map),
_ => None,
}
.into_iter()
.flatten()
}
pub fn artifact(&self, id: &str) -> Option<&Artifact> {
match &self.artifacts {
Field::Value(map) => map.get(id),
_ => None,
}
}
pub fn evals(&self) -> &[Eval] {
match &self.evals {
Field::Value(evals) => evals,
_ => &[],
}
}
pub fn relations(&self) -> &[Relation] {
match &self.relations {
Field::Value(relations) => relations,
_ => &[],
}
}
pub fn components(&self) -> &[Component] {
match &self.components {
Field::Value(components) => components,
_ => &[],
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Artifact {
#[serde(default, skip_serializing_if = "missing")]
pub kind: Field<config::ArtifactKind>,
#[serde(default, skip_serializing_if = "missing", with = "path_field")]
pub path: Field<PathBuf>,
#[serde(default, skip_serializing_if = "missing")]
pub children: Field<BTreeMap<config::ChildPrefix, crate::types::ArtifactName>>,
#[serde(default, skip_serializing_if = "missing")]
pub name: Field<crate::types::ArtifactName>,
#[serde(default, skip_serializing_if = "missing")]
pub tags: Field<Vec<String>>,
#[serde(default, skip_serializing_if = "missing")]
pub views: Field<Views>,
#[serde(default, skip_serializing_if = "missing")]
pub basis: Field<bool>,
#[serde(default, skip_serializing_if = "missing")]
pub fingerprint: Field<Fingerprint>,
#[serde(flatten)]
pub extra: Map<String, Value>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Views {
#[serde(default, skip_serializing_if = "missing")]
pub agent_tools: Field<BTreeMap<config::ToolOperationName, AgentTool>>,
#[serde(default, skip_serializing_if = "missing")]
pub human_tools: Field<BTreeMap<config::ToolOperationName, HumanTool>>,
#[serde(flatten)]
pub extra: Map<String, Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum AgentTool {
Command(CommandTool),
Builtin(BuiltinTool),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CommandTool {
pub description: String,
pub input_schema: Value,
pub protocol: config::ToolProtocol,
pub command: String,
pub args: Vec<String>,
#[serde(default, skip_serializing_if = "missing", with = "duration_field")]
pub timeout_ms: Field<std::time::Duration>,
#[serde(default, skip_serializing_if = "missing")]
pub execution_paths: Field<Vec<String>>,
#[serde(flatten)]
pub extra: Map<String, Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BuiltinTool {
pub builtin: config::Builtin,
#[serde(default, skip_serializing_if = "missing")]
pub args: Field<Vec<String>>,
#[serde(default, skip_serializing_if = "missing")]
pub description: Field<String>,
#[serde(flatten)]
pub extra: Map<String, Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum HumanTool {
Command(HumanCommandTool),
Builtin(HumanBuiltinTool),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct HumanCommandTool {
pub description: String,
pub kind: config::HumanToolKind,
pub command: String,
pub args: Vec<String>,
#[serde(flatten)]
pub extra: Map<String, Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HumanBuiltinTool {
pub builtin: config::Builtin,
pub description: String,
pub kind: config::HumanToolKind,
pub args: Vec<String>,
#[serde(flatten)]
pub extra: Map<String, Value>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Fingerprint {
#[serde(default, skip_serializing_if = "missing")]
pub script: Field<Script>,
#[serde(default, skip_serializing_if = "missing")]
pub files: Field<Vec<String>>,
#[serde(default, skip_serializing_if = "missing")]
pub ignore: Field<Vec<String>>,
#[serde(flatten)]
pub extra: Map<String, Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Script {
pub command: String,
pub args: Vec<String>,
#[serde(default, skip_serializing_if = "missing")]
pub files: Field<Vec<String>>,
#[serde(default, skip_serializing_if = "missing", with = "duration_field")]
pub timeout_ms: Field<std::time::Duration>,
#[serde(flatten)]
pub extra: Map<String, Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Eval {
pub id: crate::types::EvalId,
pub target: crate::types::ArtifactName,
#[serde(default, skip_serializing_if = "missing")]
pub references: Field<BTreeMap<crate::config::ReferenceName, crate::types::ArtifactName>>,
#[serde(default, skip_serializing_if = "missing")]
pub deps: Field<Vec<crate::types::ArtifactName>>,
#[serde(default, skip_serializing_if = "missing")]
pub declaration: Field<Declaration>,
#[serde(flatten)]
pub extra: Map<String, Value>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Declaration {
#[serde(default, skip_serializing_if = "missing")]
pub mutex: Field<Vec<config::MutexTag>>,
#[serde(default, skip_serializing_if = "missing")]
pub gate: Field<config::Gate>,
#[serde(default, skip_serializing_if = "missing")]
pub id: Field<crate::config::LocalEvalId>,
#[serde(default, skip_serializing_if = "missing")]
pub title: Field<String>,
#[serde(default, skip_serializing_if = "missing")]
pub profile: Field<Profile>,
#[serde(default, skip_serializing_if = "missing")]
pub profile_variants: Field<BTreeMap<config::ProfileVariantName, Profile>>,
#[serde(default, skip_serializing_if = "missing")]
pub payload: Field<StoredPayload>,
#[serde(default, skip_serializing_if = "missing")]
pub pass_schema: Field<Map<String, Value>>,
#[serde(default, skip_serializing_if = "missing")]
pub fail_schema: Field<Map<String, Value>>,
#[serde(flatten)]
pub extra: Map<String, Value>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Component {
#[serde(default, skip_serializing_if = "missing")]
pub id: Field<crate::graph::ComponentId>,
#[serde(default, skip_serializing_if = "missing")]
pub artifacts: Field<Vec<crate::types::ArtifactName>>,
#[serde(default, skip_serializing_if = "missing")]
pub dependencies: Field<Vec<crate::graph::ComponentId>>,
#[serde(default, skip_serializing_if = "missing")]
pub gates: Field<Vec<crate::types::EvalId>>,
#[serde(default, skip_serializing_if = "missing")]
pub cyclic: Field<bool>,
#[serde(flatten)]
pub extra: Map<String, Value>,
}
#[derive(Debug, Clone, Serialize)]
pub struct Relation {
pub source: crate::types::ArtifactName,
pub target: crate::types::ArtifactName,
#[serde(flatten)]
pub kind: RelationKind,
#[serde(default, skip_serializing_if = "missing")]
pub cyclic: Field<bool>,
#[serde(flatten)]
pub extra: Map<String, Value>,
}
impl<'de> Deserialize<'de> for Relation {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let mut fields = Map::<String, Value>::deserialize(deserializer)?;
let mut take = |name: &str| {
fields
.remove(name)
.ok_or_else(|| serde::de::Error::custom(format!("missing relation {name}")))
};
let source = serde_json::from_value(take("source")?).map_err(serde::de::Error::custom)?;
let target = serde_json::from_value(take("target")?).map_err(serde::de::Error::custom)?;
let kind_name: String =
serde_json::from_value(take("kind")?).map_err(serde::de::Error::custom)?;
let names: &[&str] = match kind_name.as_str() {
"child" => &["path"],
"instruction" => &["evalId", "name"],
"argv" => &["evalId", "index", "name", "path"],
_ => &[],
};
let unknown = names.is_empty();
let mut kind = Map::from_iter([("kind".into(), Value::String(kind_name.clone()))]);
for name in names {
if let Some(value) = fields.remove(*name) {
kind.insert((*name).into(), value);
}
}
let kind = if unknown {
RelationKind::Unknown { kind: kind_name }
} else {
let parsed =
serde_json::from_value(Value::Object(kind)).map_err(serde::de::Error::custom)?;
if matches!(parsed, RelationKind::Unknown { .. }) {
return Err(serde::de::Error::custom(format!(
"Invalid saved {kind_name} relation."
)));
}
parsed
};
let cyclic = match fields.remove("cyclic") {
Some(value) => serde_json::from_value(value).map_err(serde::de::Error::custom)?,
None => Field::Missing,
};
Ok(Self {
source,
target,
kind,
cyclic,
extra: fields,
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "lowercase")]
pub enum RelationKind {
Child {
#[serde(default, skip_serializing_if = "missing")]
path: Field<config::ChildPrefix>,
},
Instruction {
#[serde(rename = "evalId")]
eval_id: crate::types::EvalId,
name: config::ReferenceName,
},
#[serde(rename = "argv")]
Argument {
#[serde(rename = "evalId")]
eval_id: crate::types::EvalId,
index: usize,
name: config::ReferenceName,
path: String,
},
#[serde(untagged)]
Unknown { kind: String },
}
mod duration_field {
use crate::config::Field;
use serde::{Deserializer, Serializer};
use std::time::Duration;
pub fn serialize<S: Serializer>(
value: &Field<Duration>,
serializer: S,
) -> Result<S::Ok, S::Error> {
match value {
Field::Value(value) => {
let milliseconds = value.as_millis();
if !(1..=u128::from(crate::config::validation::MAX_TIMEOUT_MS))
.contains(&milliseconds)
|| Duration::from_millis(milliseconds as u64) != *value
{
return Err(serde::ser::Error::custom(
"Saved definition timeoutMs must be whole milliseconds from 1 through 2147483647.",
));
}
serializer.serialize_u128(milliseconds)
}
_ => serializer.serialize_none(),
}
}
pub fn deserialize<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<Field<Duration>, D::Error> {
crate::config::validation::milliseconds::deserialize(deserializer)
.map(|value| value.map_or(Field::Null, Field::Value))
}
}
pub(super) mod path_field {
use crate::config::Field;
use serde::{Deserializer, Serializer};
use std::path::PathBuf;
pub fn serialize<S: Serializer>(
value: &Field<PathBuf>,
serializer: S,
) -> Result<S::Ok, S::Error> {
match value {
Field::Value(path) => crate::platform::path_serde::serialize(path, serializer),
_ => serializer.serialize_none(),
}
}
pub fn deserialize<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<Field<PathBuf>, D::Error> {
crate::platform::path_serde::option::deserialize(deserializer)
.map(|value| value.map_or(Field::Null, Field::Value))
}
}