use serde::{Deserialize, Serialize};
use crate::activity_event::StopKind;
use crate::payload::{ContentType, Payload};
impl StopKind {
#[must_use]
pub fn canonical_stop_reason(&self) -> &'static str {
match self {
Self::EndTurn => "end_turn",
Self::ToolUse => "tool_use",
Self::LimitReached => "limit_reached",
Self::Cancelled => "cancelled",
Self::Error { .. } => "error",
Self::Other { .. } => "other",
}
}
}
#[derive(Serialize, Deserialize, ts_rs::TS, Clone, Debug, PartialEq, Eq)]
pub struct AgentOutcome {
pub text: String,
pub stop_reason: String,
}
impl AgentOutcome {
#[must_use]
pub fn new(text: impl Into<String>, stop: &StopKind) -> Self {
Self {
text: text.into(),
stop_reason: stop.canonical_stop_reason().to_owned(),
}
}
pub fn into_payload(self) -> Result<Payload, serde_json::Error> {
let bytes = serde_json::to_vec(&self)?;
Ok(Payload::new(ContentType::Json, bytes))
}
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::{AgentOutcome, StopKind};
type TestResult = Result<(), Box<dyn std::error::Error>>;
#[test]
fn every_stop_kind_has_its_canonical_snake_case_spelling() {
let cases = [
(StopKind::EndTurn, "end_turn"),
(StopKind::ToolUse, "tool_use"),
(StopKind::LimitReached, "limit_reached"),
(StopKind::Cancelled, "cancelled"),
(
StopKind::Error {
message: "boom".to_owned(),
},
"error",
),
(
StopKind::Other {
reason: "teleported".to_owned(),
},
"other",
),
];
for (kind, expected) in cases {
assert_eq!(kind.canonical_stop_reason(), expected);
}
}
#[test]
fn the_record_serializes_as_the_two_snake_case_fields() -> TestResult {
let outcome = AgentOutcome::new("the answer", &StopKind::EndTurn);
assert_eq!(
serde_json::to_value(&outcome)?,
json!({ "text": "the answer", "stop_reason": "end_turn" })
);
Ok(())
}
#[test]
fn the_record_round_trips_through_its_payload() -> TestResult {
let outcome = AgentOutcome::new("", &StopKind::EndTurn);
let payload = outcome.clone().into_payload()?;
assert_eq!(payload.content_type(), &super::ContentType::Json);
let decoded: AgentOutcome = serde_json::from_slice(payload.bytes())?;
assert_eq!(decoded, outcome);
assert_eq!(
decoded.text, "",
"an empty text on a completed run is honest and survives the wire"
);
Ok(())
}
}