use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
mod diff;
pub use diff::{
Attribution, AttributionMode, Counterfactual, DiffResult, FieldDiff, FieldDiffKind, Fork, Meta,
Move, MoveKind, Recovery, RunPair, RunRef, Source, Warning, WarningCode,
};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(transparent)]
pub struct SchemaVersion(pub String);
impl SchemaVersion {
pub const CURRENT: &'static str = "0.1";
#[must_use]
pub fn current() -> Self {
Self(Self::CURRENT.to_string())
}
#[must_use]
pub fn is_current(&self) -> bool {
self.0 == Self::CURRENT
}
}
impl Default for SchemaVersion {
fn default() -> Self {
Self::current()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Outcome {
Pass,
Fail,
Unknown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum StepKind {
Llm,
Tool,
Agent,
Other,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Payload {
Text(String),
Object(Map<String, Value>),
Other(Value),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Edge(pub usize, pub usize);
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Step {
pub idx: usize,
pub kind: StepKind,
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub inputs: Option<Payload>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub outputs: Option<Payload>,
#[serde(default, skip_serializing_if = "Map::is_empty")]
pub attrs: Map<String, Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub t_start: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub t_end: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub parent_idx: Option<usize>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Run {
pub schema_version: SchemaVersion,
pub id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub task: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub outcome: Option<Outcome>,
pub steps: Vec<Step>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub edges: Option<Vec<Edge>>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn schema_version_current_is_native() {
let v = SchemaVersion::current();
assert!(v.is_current());
assert_eq!(v.0, SchemaVersion::CURRENT);
assert_eq!(SchemaVersion::default(), v);
}
#[test]
fn schema_version_serializes_transparently() {
let json = serde_json::to_string(&SchemaVersion::current()).unwrap();
assert_eq!(json, "\"0.1\"");
let back: SchemaVersion = serde_json::from_str("\"0.1\"").unwrap();
assert_eq!(back, SchemaVersion::current());
}
}