use serde::{Deserialize, Serialize};
use crate::{Metadata, Params, Score, Timing, Transcript, Usage};
pub const PROTOCOL_VERSION: &str = "1.1";
pub const MIN_PROTOCOL_VERSION: &str = "1.0";
pub fn version_major(v: &str) -> u32 {
v.split('.')
.next()
.and_then(|s| s.parse().ok())
.unwrap_or(0)
}
pub fn version_compatible(other: &str) -> bool {
version_major(other) == version_major(PROTOCOL_VERSION)
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct Request {
pub id: u64,
pub method: String,
#[serde(default)]
pub params: serde_json::Value,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct Response {
pub id: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<RpcError>,
}
impl Response {
pub fn ok(id: u64, result: serde_json::Value) -> Self {
Self {
id,
result: Some(result),
error: None,
}
}
pub fn err(id: u64, message: impl Into<String>) -> Self {
Self::err_with(id, RpcError::new(message))
}
pub fn err_with(id: u64, error: RpcError) -> Self {
Self {
id,
result: None,
error: Some(error),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct RpcError {
#[serde(default)]
pub code: i32,
pub message: String,
#[serde(default)]
pub retryable: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub data: Option<serde_json::Value>,
}
pub mod codes {
pub const INVALID_PARAMS: i32 = -32602;
pub const METHOD_NOT_FOUND: i32 = -32601;
pub const INTERNAL_ERROR: i32 = -32603;
}
impl RpcError {
pub fn new(message: impl Into<String>) -> Self {
Self {
code: codes::INTERNAL_ERROR,
message: message.into(),
retryable: false,
data: None,
}
}
pub fn with_code(mut self, code: i32) -> Self {
self.code = code;
self
}
pub fn retryable(mut self) -> Self {
self.retryable = true;
self
}
pub fn with_data(mut self, data: serde_json::Value) -> Self {
self.data = Some(data);
self
}
}
impl std::fmt::Display for RpcError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.message)
}
}
impl std::error::Error for RpcError {}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct Notification {
pub method: String,
#[serde(default)]
pub params: serde_json::Value,
}
impl Notification {
pub const EVENT: &'static str = "event";
pub const LOG: &'static str = "log";
pub fn event(params: EventParams) -> Self {
Self {
method: Self::EVENT.into(),
params: serde_json::to_value(params).expect("EventParams serializes"),
}
}
pub fn log(message: impl Into<String>, request_id: u64) -> Self {
Self {
method: Self::LOG.into(),
params: serde_json::to_value(LogParams {
message: message.into(),
request_id,
})
.expect("LogParams serializes"),
}
}
pub fn as_event(&self) -> Option<EventParams> {
(self.method == Self::EVENT)
.then(|| serde_json::from_value(self.params.clone()).ok())
.flatten()
}
pub fn as_log(&self) -> Option<LogParams> {
(self.method == Self::LOG)
.then(|| serde_json::from_value(self.params.clone()).ok())
.flatten()
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct EventParams {
#[serde(default)]
pub request_id: u64,
pub eval: String,
pub sample: String,
pub target: String,
#[serde(default, skip_serializing_if = "Params::is_empty")]
pub params: Params,
pub kind: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub turn: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tool: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub text: Option<String>,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct LogParams {
pub message: String,
#[serde(default)]
pub request_id: u64,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct InitializeResult {
pub protocol_version: String,
pub study: String,
pub evals: usize,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub study_version: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub capabilities: Vec<String>,
#[serde(default, skip_serializing_if = "Metadata::is_empty")]
pub capability_params: Metadata,
}
impl InitializeResult {
pub fn capability_param(&self, capability: &str) -> Option<&serde_json::Value> {
self.capability_params.get(capability)
}
}
pub mod capabilities {
pub const AXES: &str = "axes";
pub const EVENTS: &str = "events";
pub const USAGE: &str = "usage";
pub const EXECUTE: &str = "execute";
pub const SCORE: &str = "score";
pub const TRIALS: &str = "trials";
pub const CANCEL: &str = "cancel";
pub const PAGINATE: &str = "paginate";
pub const TRAJECTORY: &str = "trajectory";
}
pub mod event {
pub const STARTED: &str = "started";
pub const TURN: &str = "turn";
pub const TOOL_CALL: &str = "tool_call";
pub const OUTPUT: &str = "output";
pub const FINISHED: &str = "finished";
pub const ALL: &[&str] = &[STARTED, TURN, TOOL_CALL, OUTPUT, FINISHED];
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct SampleInfo {
pub id: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tags: Vec<String>,
#[serde(default, skip_serializing_if = "Metadata::is_empty")]
pub metadata: Metadata,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct TargetInfo {
pub label: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub provider: String,
pub available: bool,
#[serde(default, skip_serializing_if = "Metadata::is_empty")]
pub metadata: Metadata,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct AxisInfo {
pub name: String,
pub values: Vec<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct EvalInfo {
pub name: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub description: String,
pub samples: Vec<SampleInfo>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub next_cursor: Option<String>,
pub scorers: Vec<String>,
pub targets: Vec<TargetInfo>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub axes: Vec<AxisInfo>,
#[serde(default)]
pub max_turns: usize,
#[serde(default, skip_serializing_if = "is_single_trial")]
pub trials: usize,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub seed: Option<u64>,
#[serde(default, skip_serializing_if = "Metadata::is_empty")]
pub metadata: Metadata,
}
fn is_single_trial(n: &usize) -> bool {
*n <= 1
}
fn is_zero(n: &usize) -> bool {
*n == 0
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct ListResult {
pub evals: Vec<EvalInfo>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct ListSamplesParams {
pub eval: String,
pub cursor: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct ListSamplesResult {
pub samples: Vec<SampleInfo>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub next_cursor: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct RunParams {
pub eval: String,
pub sample: String,
pub target: String,
#[serde(default, skip_serializing_if = "Params::is_empty")]
pub params: Params,
#[serde(default, skip_serializing_if = "is_zero")]
pub trial: usize,
#[serde(default, skip_serializing_if = "is_single_trial")]
pub trials: usize,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub seed: Option<u64>,
}
impl RunParams {
pub fn trial(&self) -> crate::Trial {
crate::Trial {
index: self.trial,
count: self.trials.max(1),
seed: self.seed,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct CancelParams {
pub id: u64,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct CancelResult {
pub cancelled: bool,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct TranscriptSummary {
pub final_response: String,
pub iterations: usize,
pub tool_calls_count: usize,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tool_calls: Vec<String>,
pub usage: Usage,
#[serde(default, skip_serializing_if = "Timing::is_default")]
pub timing: Timing,
#[serde(default, skip_serializing_if = "std::collections::BTreeMap::is_empty")]
pub metrics: std::collections::BTreeMap<String, f64>,
#[serde(default, skip_serializing_if = "Metadata::is_empty")]
pub metadata: Metadata,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
#[serde(default, skip_serializing_if = "crate::ErrorKind::is_subject")]
pub error_kind: crate::ErrorKind,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub output: Vec<crate::Part>,
#[cfg(feature = "protocol-unstable")]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub experimental: Option<String>,
}
impl TranscriptSummary {
pub fn of(t: &Transcript) -> Self {
Self {
final_response: t.final_response.clone(),
iterations: t.iterations,
tool_calls_count: t.tool_calls_count,
tool_calls: t.tool_calls.clone(),
usage: t.usage,
timing: t.timing,
metrics: t.metrics.clone(),
metadata: t.metadata.clone(),
error: t.error.clone(),
error_kind: t.error_kind,
output: t.output.clone(),
#[cfg(feature = "protocol-unstable")]
experimental: None,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct ExecuteResult {
pub eval: String,
pub sample: String,
pub target: String,
#[serde(default, skip_serializing_if = "Params::is_empty")]
pub params: Params,
#[serde(default, skip_serializing_if = "is_zero")]
pub trial: usize,
#[serde(default, skip_serializing_if = "is_single_trial")]
pub trials: usize,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub seed: Option<u64>,
pub transcript: Transcript,
#[serde(default)]
pub skipped: bool,
}
impl ExecuteResult {
pub fn key(&self) -> String {
format!(
"{}{}",
crate::case_key(&self.eval, &self.sample, &self.target, &self.params),
crate::trial_suffix(self.trial, self.trials),
)
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct ScoreParams {
pub eval: String,
pub sample: String,
pub target: String,
#[serde(default, skip_serializing_if = "Params::is_empty")]
pub params: Params,
#[serde(default, skip_serializing_if = "is_zero")]
pub trial: usize,
#[serde(default, skip_serializing_if = "is_single_trial")]
pub trials: usize,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub seed: Option<u64>,
pub transcript: Transcript,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct RunResult {
pub eval: String,
pub sample: String,
pub target: String,
#[serde(default, skip_serializing_if = "Params::is_empty")]
pub params: Params,
#[serde(default, skip_serializing_if = "is_zero")]
pub trial: usize,
#[serde(default, skip_serializing_if = "is_single_trial")]
pub trials: usize,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub seed: Option<u64>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub input: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expected: Option<serde_json::Value>,
pub passed: bool,
pub aggregate: f64,
pub scores: Vec<Score>,
pub transcript: TranscriptSummary,
#[serde(default)]
pub skipped: bool,
}
impl RunResult {
pub fn key(&self) -> String {
format!(
"{}{}",
self.logical_key(),
crate::trial_suffix(self.trial, self.trials)
)
}
pub fn logical_key(&self) -> String {
crate::case_key(&self.eval, &self.sample, &self.target, &self.params)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "protocol-unstable")]
#[test]
fn unstable_field_roundtrips_when_enabled() {
let t = TranscriptSummary {
experimental: Some("staged".into()),
..Default::default()
};
let line = serde_json::to_string(&t).unwrap();
assert!(line.contains("experimental"));
let back: TranscriptSummary = serde_json::from_str(&line).unwrap();
assert_eq!(back.experimental.as_deref(), Some("staged"));
}
#[test]
fn request_response_roundtrip() {
let req = Request {
id: 7,
method: "run".into(),
params: serde_json::json!({"eval": "e"}),
};
let line = serde_json::to_string(&req).unwrap();
let back: Request = serde_json::from_str(&line).unwrap();
assert_eq!(back.id, 7);
assert_eq!(back.method, "run");
}
#[test]
fn cancel_params_and_result_roundtrip() {
let p = CancelParams { id: 42 };
let line = serde_json::to_string(&p).unwrap();
assert_eq!(line, r#"{"id":42}"#);
let back: CancelParams = serde_json::from_value(serde_json::json!({ "id": 42 })).unwrap();
assert_eq!(back.id, 42);
let r = CancelResult { cancelled: true };
let back: CancelResult = serde_json::from_str(&serde_json::to_string(&r).unwrap()).unwrap();
assert!(back.cancelled);
}
#[test]
fn version_compatibility() {
assert!(version_compatible(PROTOCOL_VERSION));
assert!(version_compatible(MIN_PROTOCOL_VERSION));
assert!(version_compatible("1.0"));
assert!(version_compatible("1.5")); assert!(!version_compatible("2.0")); assert!(!version_compatible("0.9")); assert_eq!(version_major("1.4"), 1);
assert_eq!(version_major("garbage"), 0);
}
#[test]
fn unknown_fields_are_ignored_for_forward_compat() {
let line = r#"{"protocol_version":"1.1","study":"x","evals":2,
"capabilities":["axes","future_thing"],"brand_new_field":{"a":1}}"#;
let info: InitializeResult = serde_json::from_str(line).unwrap();
assert_eq!(info.evals, 2);
assert!(info.capabilities.contains(&"axes".to_string()));
}
#[test]
fn eval_info_defaults_missing_optional_fields() {
let line = r#"{"name":"greet","samples":[{"id":"hi"}],
"scorers":["succeeded"],"targets":[{"label":"sim","available":true}]}"#;
let info: EvalInfo = serde_json::from_str(line).unwrap();
assert_eq!(info.max_turns, 0);
assert!(info.axes.is_empty());
assert_eq!(info.samples.len(), 1);
assert!(info.samples[0].metadata.is_empty());
assert!(info.targets[0].metadata.is_empty());
}
#[test]
fn sample_and_model_metadata_omitted_when_empty() {
let sample = serde_json::to_string(&SampleInfo {
id: "hi".into(),
tags: vec![],
metadata: Default::default(),
})
.unwrap();
assert!(!sample.contains("metadata"), "got: {sample}");
let target = serde_json::to_string(&TargetInfo {
label: "sim".into(),
provider: "sim".into(),
available: true,
metadata: Default::default(),
})
.unwrap();
assert!(!target.contains("metadata"), "got: {target}");
}
#[test]
fn sample_and_model_metadata_roundtrip() {
let mut metadata = Metadata::new();
metadata.insert("difficulty".into(), serde_json::json!("hard"));
metadata.insert("retries".into(), serde_json::json!(3));
let info = SampleInfo {
id: "hi".into(),
tags: vec!["smoke".into()],
metadata: metadata.clone(),
};
let back: SampleInfo =
serde_json::from_str(&serde_json::to_string(&info).unwrap()).unwrap();
assert_eq!(back.metadata.get("difficulty").unwrap(), "hard");
assert_eq!(back.metadata.get("retries").unwrap(), &serde_json::json!(3));
}
#[test]
fn event_notification_roundtrips_and_correlates() {
let n = Notification::event(EventParams {
request_id: 42,
eval: "greet".into(),
sample: "hi".into(),
target: "sim".into(),
kind: event::TOOL_CALL.into(),
tool: Some("search".into()),
..Default::default()
});
let line = serde_json::to_string(&n).unwrap();
assert!(!line.contains("\"id\""));
assert!(serde_json::from_str::<Response>(&line).is_err());
let back: Notification = serde_json::from_str(&line).unwrap();
let ev = back.as_event().expect("parses as event");
assert_eq!(ev.request_id, 42);
assert_eq!(ev.kind, event::TOOL_CALL);
assert_eq!(ev.tool.as_deref(), Some("search"));
assert!(back.as_log().is_none());
}
#[test]
fn untyped_event_still_parses() {
let line = r#"{"method":"event","params":
{"eval":"greet","sample":"hi","target":"sim","kind":"started"}}"#;
let n: Notification = serde_json::from_str(line).unwrap();
let ev = n.as_event().expect("legacy event parses");
assert_eq!(ev.request_id, 0);
assert_eq!(ev.kind, "started");
}
#[test]
fn log_notification_roundtrips() {
let n = Notification::log("warming up", 7);
let back: Notification = serde_json::from_str(&serde_json::to_string(&n).unwrap()).unwrap();
let log = back.as_log().expect("parses as log");
assert_eq!(log.message, "warming up");
assert_eq!(log.request_id, 7);
let legacy: Notification =
serde_json::from_str(r#"{"method":"log","params":{"message":"hi"}}"#).unwrap();
let log = legacy.as_log().unwrap();
assert_eq!(log.message, "hi");
assert_eq!(log.request_id, 0);
}
#[test]
fn eval_info_without_cursor_omits_it_and_defaults_on_read() {
let info = EvalInfo {
name: "greet".into(),
description: String::new(),
samples: vec![SampleInfo {
id: "hi".into(),
tags: vec![],
metadata: Metadata::default(),
}],
next_cursor: None,
scorers: vec![],
targets: vec![],
axes: vec![],
max_turns: 0,
trials: 0,
seed: None,
metadata: Metadata::default(),
};
let line = serde_json::to_string(&info).unwrap();
assert!(!line.contains("next_cursor"));
let minimal = r#"{"name":"greet","samples":[{"id":"hi"}],
"scorers":[],"targets":[]}"#;
let back: EvalInfo = serde_json::from_str(minimal).unwrap();
assert!(back.next_cursor.is_none());
}
#[test]
fn list_samples_roundtrips_with_and_without_next() {
let params = ListSamplesParams {
eval: "swe".into(),
cursor: "500".into(),
};
let back: ListSamplesParams =
serde_json::from_str(&serde_json::to_string(¶ms).unwrap()).unwrap();
assert_eq!(back.eval, "swe");
assert_eq!(back.cursor, "500");
let last = ListSamplesResult {
samples: vec![SampleInfo {
id: "case-999".into(),
tags: vec![],
metadata: Metadata::default(),
}],
next_cursor: None,
};
let line = serde_json::to_string(&last).unwrap();
assert!(!line.contains("next_cursor")); let more = ListSamplesResult {
samples: vec![],
next_cursor: Some("1000".into()),
};
let back: ListSamplesResult =
serde_json::from_str(&serde_json::to_string(&more).unwrap()).unwrap();
assert_eq!(back.next_cursor.as_deref(), Some("1000"));
}
#[test]
fn notification_has_no_id() {
let n = Notification {
method: "event".into(),
params: serde_json::json!({"kind": "started"}),
};
let line = serde_json::to_string(&n).unwrap();
assert!(!line.contains("\"id\""));
assert!(serde_json::from_str::<Response>(&line).is_err());
}
#[test]
fn rpc_error_is_classifiable_and_roundtrips() {
let err = RpcError::new("provider 503")
.with_code(codes::INTERNAL_ERROR)
.retryable()
.with_data(serde_json::json!({ "provider": "anthropic" }));
let line = serde_json::to_string(&err).unwrap();
let back: RpcError = serde_json::from_str(&line).unwrap();
assert!(back.retryable);
assert_eq!(back.code, codes::INTERNAL_ERROR);
assert_eq!(
back.data,
Some(serde_json::json!({ "provider": "anthropic" }))
);
let plain = RpcError::new("nope");
assert!(!plain.retryable);
assert!(!serde_json::to_string(&plain).unwrap().contains("data"));
}
#[test]
fn rpc_error_backward_compatible_with_bare_message() {
let back: RpcError = serde_json::from_str(r#"{"message":"no such eval"}"#).unwrap();
assert_eq!(back.message, "no such eval");
assert_eq!(back.code, 0);
assert!(!back.retryable);
assert!(back.data.is_none());
}
#[test]
fn run_result_key() {
let r = RunResult {
eval: "greet".into(),
sample: "hi".into(),
target: "sim".into(),
params: Default::default(),
trial: 0,
trials: 0,
seed: None,
input: Vec::new(),
expected: None,
passed: true,
aggregate: 1.0,
scores: vec![],
transcript: TranscriptSummary::default(),
skipped: false,
};
assert_eq!(r.key(), "greet/hi@sim");
let mut params = Params::new();
params.insert("effort".into(), "high".into());
let r2 = RunResult {
params,
..r.clone()
};
assert_eq!(r2.key(), "greet/hi@sim[effort=high]");
}
#[test]
fn run_result_trial_key_and_logical_key() {
let r = RunResult {
eval: "greet".into(),
sample: "hi".into(),
target: "sim".into(),
params: Default::default(),
trial: 2,
trials: 5,
seed: Some(42),
input: Vec::new(),
expected: None,
passed: true,
aggregate: 1.0,
scores: vec![],
transcript: TranscriptSummary::default(),
skipped: false,
};
assert_eq!(r.key(), "greet/hi@sim#2");
assert_eq!(r.logical_key(), "greet/hi@sim");
let single = RunResult {
trial: 0,
trials: 1,
..r.clone()
};
assert_eq!(single.key(), "greet/hi@sim");
}
#[test]
fn pre_trials_payloads_parse_as_single_trial() {
let line = r#"{"eval":"greet","sample":"hi","target":"sim","passed":true,
"aggregate":1.0,"scores":[],
"transcript":{"final_response":"hi","iterations":1,"tool_calls_count":0,
"usage":{"input_tokens":1,"output_tokens":1,"cost_usd":0.0}}}"#;
let r: RunResult = serde_json::from_str(line).unwrap();
assert_eq!(r.trial, 0);
assert_eq!(r.trials, 0);
assert_eq!(r.seed, None);
assert_eq!(r.key(), "greet/hi@sim"); assert_eq!(r.logical_key(), "greet/hi@sim");
let line = r#"{"name":"greet","samples":[{"id":"hi"}],"scorers":["s"],
"targets":[{"label":"sim","available":true}]}"#;
let e: EvalInfo = serde_json::from_str(line).unwrap();
assert_eq!(e.trials, 0); assert_eq!(e.seed, None);
}
#[test]
fn trial_fields_omitted_on_wire_for_single_run() {
let p = RunParams {
eval: "e".into(),
sample: "s".into(),
target: "m".into(),
params: Default::default(),
trial: 0,
trials: 1,
seed: None,
};
let line = serde_json::to_string(&p).unwrap();
assert!(!line.contains("trial"));
assert!(!line.contains("seed"));
let p2 = RunParams {
trial: 3,
trials: 8,
seed: Some(7),
..p
};
let line2 = serde_json::to_string(&p2).unwrap();
let back: RunParams = serde_json::from_str(&line2).unwrap();
assert_eq!(back.trial, 3);
assert_eq!(back.trials, 8);
assert_eq!(back.seed, Some(7));
assert_eq!(back.trial().count, 8);
}
}