strands_agents/agent/
result.rs1use std::fmt;
4
5use serde::{Deserialize, Serialize};
6
7use crate::telemetry::EventLoopMetrics;
8use crate::tools::InvocationState;
9use crate::types::content::Message;
10use crate::types::interrupt::Interrupt;
11use crate::types::streaming::{StopReason, Usage};
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct AgentResult {
16 pub stop_reason: StopReason,
18 pub message: Message,
20 pub usage: Usage,
22 pub metrics: EventLoopMetrics,
24 pub state: InvocationState,
26 #[serde(skip_serializing_if = "Option::is_none")]
28 pub interrupts: Option<Vec<Interrupt>>,
29 #[serde(skip_serializing_if = "Option::is_none")]
31 pub structured_output: Option<serde_json::Value>,
32}
33
34impl AgentResult {
35 pub fn text(&self) -> String {
42 let text = self.message.text_content();
43 if text.is_empty() {
44 if let Some(ref output) = self.structured_output {
45 return serde_json::to_string(output).unwrap_or_default();
46 }
47 }
48 text
49 }
50
51 pub fn is_success(&self) -> bool {
53 matches!(self.stop_reason, StopReason::EndTurn | StopReason::StopSequence)
54 }
55
56 pub fn is_interrupted(&self) -> bool {
58 matches!(self.stop_reason, StopReason::Interrupt)
59 }
60
61 pub fn has_interrupts(&self) -> bool {
63 self.interrupts.as_ref().map(|i| !i.is_empty()).unwrap_or(false)
64 }
65
66 pub fn from_dict(data: serde_json::Value) -> Result<Self, String> {
68 let type_field = data.get("type").and_then(|v| v.as_str());
69 if type_field != Some("agent_result") {
70 return Err(format!(
71 "AgentResult.from_dict: unexpected type {:?}",
72 type_field
73 ));
74 }
75
76 let message: Message = serde_json::from_value(
77 data.get("message").cloned().unwrap_or_default()
78 ).map_err(|e| e.to_string())?;
79
80 let stop_reason: StopReason = serde_json::from_value(
81 data.get("stop_reason").cloned().unwrap_or_default()
82 ).map_err(|e| e.to_string())?;
83
84 Ok(Self {
85 message,
86 stop_reason,
87 usage: Usage::default(),
88 metrics: EventLoopMetrics::default(),
89 state: InvocationState::new(),
90 interrupts: None,
91 structured_output: None,
92 })
93 }
94
95 pub fn to_dict(&self) -> serde_json::Value {
97 serde_json::json!({
98 "type": "agent_result",
99 "message": self.message,
100 "stop_reason": self.stop_reason,
101 })
102 }
103}
104
105impl fmt::Display for AgentResult {
106 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107 write!(f, "{}", self.text())
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114 use crate::types::content::{ContentBlock, Role};
115
116 fn create_test_result(text: &str, stop_reason: StopReason) -> AgentResult {
117 AgentResult {
118 stop_reason,
119 message: Message {
120 role: Role::Assistant,
121 content: vec![ContentBlock::text(text)],
122 },
123 usage: Usage::default(),
124 metrics: EventLoopMetrics::default(),
125 state: InvocationState::new(),
126 interrupts: None,
127 structured_output: None,
128 }
129 }
130
131 #[test]
132 fn test_result_text() {
133 let result = create_test_result("Hello, world!", StopReason::EndTurn);
134 assert_eq!(result.text(), "Hello, world!");
135 assert!(result.is_success());
136 }
137
138 #[test]
139 fn test_result_display() {
140 let result = create_test_result("Test", StopReason::EndTurn);
141 assert_eq!(format!("{}", result), "Test");
142 }
143
144 #[test]
145 fn test_result_with_structured_output() {
146 let mut result = create_test_result("", StopReason::EndTurn);
147 result.structured_output = Some(serde_json::json!({"key": "value"}));
148 assert!(result.text().contains("key"));
149 }
150
151 #[test]
152 fn test_result_interrupts() {
153 let mut result = create_test_result("Test", StopReason::Interrupt);
154 assert!(!result.has_interrupts());
155
156 result.interrupts = Some(vec![Interrupt::new("int-1", "test")]);
157 assert!(result.has_interrupts());
158 assert!(result.is_interrupted());
159 }
160
161 #[test]
162 fn test_result_serialization() {
163 let result = create_test_result("Hello", StopReason::EndTurn);
164 let dict = result.to_dict();
165 assert_eq!(dict["type"], "agent_result");
166
167 let restored = AgentResult::from_dict(dict).unwrap();
168 assert_eq!(restored.text(), "Hello");
169 }
170}