use std::fmt;
use serde::{Deserialize, Serialize};
use crate::telemetry::EventLoopMetrics;
use crate::tools::InvocationState;
use crate::types::content::Message;
use crate::types::interrupt::Interrupt;
use crate::types::streaming::{StopReason, Usage};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentResult {
pub stop_reason: StopReason,
pub message: Message,
pub usage: Usage,
pub metrics: EventLoopMetrics,
pub state: InvocationState,
#[serde(skip_serializing_if = "Option::is_none")]
pub interrupts: Option<Vec<Interrupt>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub structured_output: Option<serde_json::Value>,
}
impl AgentResult {
pub fn text(&self) -> String {
let text = self.message.text_content();
if text.is_empty() {
if let Some(ref output) = self.structured_output {
return serde_json::to_string(output).unwrap_or_default();
}
}
text
}
pub fn is_success(&self) -> bool {
matches!(self.stop_reason, StopReason::EndTurn | StopReason::StopSequence)
}
pub fn is_interrupted(&self) -> bool {
matches!(self.stop_reason, StopReason::Interrupt)
}
pub fn has_interrupts(&self) -> bool {
self.interrupts.as_ref().map(|i| !i.is_empty()).unwrap_or(false)
}
pub fn from_dict(data: serde_json::Value) -> Result<Self, String> {
let type_field = data.get("type").and_then(|v| v.as_str());
if type_field != Some("agent_result") {
return Err(format!(
"AgentResult.from_dict: unexpected type {:?}",
type_field
));
}
let message: Message = serde_json::from_value(
data.get("message").cloned().unwrap_or_default()
).map_err(|e| e.to_string())?;
let stop_reason: StopReason = serde_json::from_value(
data.get("stop_reason").cloned().unwrap_or_default()
).map_err(|e| e.to_string())?;
Ok(Self {
message,
stop_reason,
usage: Usage::default(),
metrics: EventLoopMetrics::default(),
state: InvocationState::new(),
interrupts: None,
structured_output: None,
})
}
pub fn to_dict(&self) -> serde_json::Value {
serde_json::json!({
"type": "agent_result",
"message": self.message,
"stop_reason": self.stop_reason,
})
}
}
impl fmt::Display for AgentResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.text())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::content::{ContentBlock, Role};
fn create_test_result(text: &str, stop_reason: StopReason) -> AgentResult {
AgentResult {
stop_reason,
message: Message {
role: Role::Assistant,
content: vec![ContentBlock::text(text)],
},
usage: Usage::default(),
metrics: EventLoopMetrics::default(),
state: InvocationState::new(),
interrupts: None,
structured_output: None,
}
}
#[test]
fn test_result_text() {
let result = create_test_result("Hello, world!", StopReason::EndTurn);
assert_eq!(result.text(), "Hello, world!");
assert!(result.is_success());
}
#[test]
fn test_result_display() {
let result = create_test_result("Test", StopReason::EndTurn);
assert_eq!(format!("{}", result), "Test");
}
#[test]
fn test_result_with_structured_output() {
let mut result = create_test_result("", StopReason::EndTurn);
result.structured_output = Some(serde_json::json!({"key": "value"}));
assert!(result.text().contains("key"));
}
#[test]
fn test_result_interrupts() {
let mut result = create_test_result("Test", StopReason::Interrupt);
assert!(!result.has_interrupts());
result.interrupts = Some(vec![Interrupt::new("int-1", "test")]);
assert!(result.has_interrupts());
assert!(result.is_interrupted());
}
#[test]
fn test_result_serialization() {
let result = create_test_result("Hello", StopReason::EndTurn);
let dict = result.to_dict();
assert_eq!(dict["type"], "agent_result");
let restored = AgentResult::from_dict(dict).unwrap();
assert_eq!(restored.text(), "Hello");
}
}