Skip to main content

rs_adk/events/
structured.rs

1//! Structured event types — typed wrappers over raw events.
2//!
3//! Converts persisted `Event` records into typed `StructuredEvent` variants
4//! for easier consumption by UI layers and analytics.
5
6use serde_json::Value;
7
8/// Classification of a structured event.
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub enum EventType {
11    /// Internal reasoning or chain-of-thought.
12    Thought,
13    /// User-facing content output.
14    Content,
15    /// A tool/function call from the model.
16    ToolCall,
17    /// The result of a tool/function call.
18    ToolResult,
19    /// A code execution request.
20    CallCode,
21    /// The result of a code execution.
22    CodeResult,
23    /// An error event.
24    Error,
25    /// An activity/status update (e.g., agent transfer).
26    Activity,
27    /// A tool confirmation request or response.
28    ToolConfirmation,
29    /// The agent has finished processing.
30    Finished,
31}
32
33/// A typed, structured representation of an agent event.
34#[derive(Debug, Clone)]
35pub enum StructuredEvent {
36    /// Internal reasoning or chain-of-thought text.
37    Thought {
38        /// The reasoning text.
39        text: String,
40    },
41    /// User-facing content with author attribution.
42    Content {
43        /// The content text.
44        text: String,
45        /// Who authored this content (e.g., "model", "agent").
46        author: String,
47    },
48    /// A tool/function call from the model.
49    ToolCall {
50        /// Tool name.
51        name: String,
52        /// Tool arguments as JSON.
53        args: Value,
54        /// Optional unique call identifier.
55        call_id: Option<String>,
56    },
57    /// The result of a tool/function call.
58    ToolResult {
59        /// Tool name.
60        name: String,
61        /// The result value.
62        result: Value,
63    },
64    /// An error event.
65    Error {
66        /// Error description.
67        message: String,
68    },
69    /// An activity/status update (e.g., agent transfer, escalation).
70    Activity {
71        /// Human-readable description of the activity.
72        description: String,
73    },
74    /// A tool confirmation request or response.
75    ToolConfirmation {
76        /// Optional hint for the confirmation UI.
77        hint: Option<String>,
78        /// Whether the tool call was confirmed.
79        confirmed: bool,
80    },
81    /// The agent has finished processing.
82    Finished,
83}
84
85impl StructuredEvent {
86    /// Returns the classification of this event.
87    pub fn event_type(&self) -> EventType {
88        match self {
89            Self::Thought { .. } => EventType::Thought,
90            Self::Content { .. } => EventType::Content,
91            Self::ToolCall { .. } => EventType::ToolCall,
92            Self::ToolResult { .. } => EventType::ToolResult,
93            Self::Error { .. } => EventType::Error,
94            Self::Activity { .. } => EventType::Activity,
95            Self::ToolConfirmation { .. } => EventType::ToolConfirmation,
96            Self::Finished => EventType::Finished,
97        }
98    }
99}
100
101/// Convert a persisted `Event` into typed `StructuredEvent`s.
102///
103/// A single `Event` may produce multiple structured events (e.g., content + transfer activity).
104pub fn to_structured_events(event: &super::Event) -> Vec<StructuredEvent> {
105    let mut out = Vec::new();
106
107    // Content → Content or Thought
108    if let Some(text) = &event.content {
109        if !text.is_empty() {
110            out.push(StructuredEvent::Content {
111                text: text.clone(),
112                author: event.author.clone(),
113            });
114        }
115    }
116
117    // Transfer → Activity
118    if let Some(target) = &event.actions.transfer_to_agent {
119        out.push(StructuredEvent::Activity {
120            description: format!("Transferring to agent: {target}"),
121        });
122    }
123
124    // Escalate → Activity
125    if event.actions.escalate {
126        out.push(StructuredEvent::Activity {
127            description: "Escalating to human/parent agent".to_string(),
128        });
129    }
130
131    // State delta with tool-related keys → ToolResult hints
132    for (key, value) in &event.actions.state_delta {
133        if key.starts_with("tool:result:") {
134            let tool_name = key.strip_prefix("tool:result:").unwrap_or(key);
135            out.push(StructuredEvent::ToolResult {
136                name: tool_name.to_string(),
137                result: value.clone(),
138            });
139        }
140    }
141
142    out
143}
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148    use crate::events::{Event, EventActions};
149    use std::collections::HashMap;
150
151    #[test]
152    fn event_type_classification() {
153        let thought = StructuredEvent::Thought { text: "hmm".into() };
154        assert_eq!(thought.event_type(), EventType::Thought);
155
156        let content = StructuredEvent::Content {
157            text: "hi".into(),
158            author: "model".into(),
159        };
160        assert_eq!(content.event_type(), EventType::Content);
161
162        assert_eq!(StructuredEvent::Finished.event_type(), EventType::Finished);
163    }
164
165    #[test]
166    fn convert_event_with_content() {
167        let event = Event::new("model", Some("Hello there!".to_string()));
168        let structured = to_structured_events(&event);
169        assert_eq!(structured.len(), 1);
170        match &structured[0] {
171            StructuredEvent::Content { text, author } => {
172                assert_eq!(text, "Hello there!");
173                assert_eq!(author, "model");
174            }
175            other => panic!("expected Content, got: {other:?}"),
176        }
177    }
178
179    #[test]
180    fn convert_event_with_transfer() {
181        let actions = EventActions::transfer("helper");
182        let event = Event::new("agent", None).with_actions(actions);
183        let structured = to_structured_events(&event);
184        assert_eq!(structured.len(), 1);
185        match &structured[0] {
186            StructuredEvent::Activity { description } => {
187                assert!(description.contains("helper"));
188            }
189            other => panic!("expected Activity, got: {other:?}"),
190        }
191    }
192
193    #[test]
194    fn convert_event_with_content_and_transfer() {
195        let actions = EventActions::transfer("target");
196        let event = Event::new("agent", Some("Handing off".to_string())).with_actions(actions);
197        let structured = to_structured_events(&event);
198        assert_eq!(structured.len(), 2);
199        assert_eq!(structured[0].event_type(), EventType::Content);
200        assert_eq!(structured[1].event_type(), EventType::Activity);
201    }
202
203    #[test]
204    fn convert_empty_event() {
205        let event = Event::new("system", None);
206        let structured = to_structured_events(&event);
207        assert!(structured.is_empty());
208    }
209
210    #[test]
211    fn convert_event_with_tool_result_in_state_delta() {
212        let mut delta = HashMap::new();
213        delta.insert(
214            "tool:result:get_weather".to_string(),
215            serde_json::json!({"temp": 22}),
216        );
217        let actions = EventActions::state_delta(delta);
218        let event = Event::new("model", None).with_actions(actions);
219        let structured = to_structured_events(&event);
220        assert_eq!(structured.len(), 1);
221        match &structured[0] {
222            StructuredEvent::ToolResult { name, result } => {
223                assert_eq!(name, "get_weather");
224                assert_eq!(result["temp"], 22);
225            }
226            other => panic!("expected ToolResult, got: {other:?}"),
227        }
228    }
229
230    #[test]
231    fn convert_event_with_escalation() {
232        let actions = EventActions::escalate();
233        let event = Event::new("agent", None).with_actions(actions);
234        let structured = to_structured_events(&event);
235        assert_eq!(structured.len(), 1);
236        assert_eq!(structured[0].event_type(), EventType::Activity);
237    }
238}