strands_agents/types/
events.rs

1//! Typed event system for the Strands Agents framework.
2//!
3//! This module defines the event types that are emitted during agent execution,
4//! providing a structured way to observe different events of the event loop and
5//! agent lifecycle.
6
7use serde::{Deserialize, Serialize};
8
9use crate::types::content::Message;
10use crate::types::interrupt::Interrupt;
11use crate::types::streaming::{ContentBlockDelta, Metrics, StopReason, StreamEvent, Usage};
12use crate::types::tools::{ToolResult, ToolUse};
13use crate::types::citations::Citation;
14
15/// Base trait for all typed events in the agent system.
16pub trait TypedEvent: Send + Sync {
17    /// True if this event should trigger the callback_handler to fire.
18    fn is_callback_event(&self) -> bool { true }
19
20    /// Convert this event to a JSON-serializable value.
21    fn as_dict(&self) -> serde_json::Value;
22}
23
24/// Event emitted at the very beginning of agent execution.
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct InitEventLoopEvent {
27    pub init_event_loop: bool,
28}
29
30impl InitEventLoopEvent {
31    pub fn new() -> Self {
32        Self { init_event_loop: true }
33    }
34}
35
36impl Default for InitEventLoopEvent {
37    fn default() -> Self { Self::new() }
38}
39
40impl TypedEvent for InitEventLoopEvent {
41    fn as_dict(&self) -> serde_json::Value {
42        serde_json::json!({ "init_event_loop": self.init_event_loop })
43    }
44}
45
46/// Event emitted at the start of each event loop cycle.
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct StartEvent {
49    pub start: bool,
50}
51
52impl StartEvent {
53    pub fn new() -> Self {
54        Self { start: true }
55    }
56}
57
58impl Default for StartEvent {
59    fn default() -> Self { Self::new() }
60}
61
62impl TypedEvent for StartEvent {
63    fn as_dict(&self) -> serde_json::Value {
64        serde_json::json!({ "start": self.start })
65    }
66}
67
68/// Event emitted when the event loop cycle begins processing.
69#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct StartEventLoopEvent {
71    pub start_event_loop: bool,
72}
73
74impl StartEventLoopEvent {
75    pub fn new() -> Self {
76        Self { start_event_loop: true }
77    }
78}
79
80impl Default for StartEventLoopEvent {
81    fn default() -> Self { Self::new() }
82}
83
84impl TypedEvent for StartEventLoopEvent {
85    fn as_dict(&self) -> serde_json::Value {
86        serde_json::json!({ "start_event_loop": self.start_event_loop })
87    }
88}
89
90/// Event emitted during model response streaming for each raw chunk.
91#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct ModelStreamChunkEvent {
93    pub event: StreamEvent,
94}
95
96impl ModelStreamChunkEvent {
97    pub fn new(chunk: StreamEvent) -> Self {
98        Self { event: chunk }
99    }
100
101    pub fn chunk(&self) -> &StreamEvent {
102        &self.event
103    }
104}
105
106impl TypedEvent for ModelStreamChunkEvent {
107    fn as_dict(&self) -> serde_json::Value {
108        serde_json::json!({ "event": self.event })
109    }
110}
111
112/// Event emitted during model response streaming.
113#[derive(Debug, Clone, Serialize, Deserialize)]
114pub struct ModelStreamEvent {
115    #[serde(skip_serializing_if = "Option::is_none")]
116    pub data: Option<String>,
117    #[serde(skip_serializing_if = "Option::is_none")]
118    pub delta: Option<ContentBlockDelta>,
119    #[serde(skip_serializing_if = "Option::is_none")]
120    pub reasoning_text: Option<String>,
121    #[serde(skip_serializing_if = "Option::is_none")]
122    pub current_tool_use: Option<serde_json::Value>,
123}
124
125impl ModelStreamEvent {
126    pub fn text(delta: ContentBlockDelta, text: String) -> Self {
127        Self {
128            data: Some(text),
129            delta: Some(delta),
130            reasoning_text: None,
131            current_tool_use: None,
132        }
133    }
134
135    pub fn tool_use(delta: ContentBlockDelta, current_tool_use: serde_json::Value) -> Self {
136        Self {
137            data: None,
138            delta: Some(delta),
139            reasoning_text: None,
140            current_tool_use: Some(current_tool_use),
141        }
142    }
143
144    pub fn reasoning(delta: ContentBlockDelta, reasoning_text: String) -> Self {
145        Self {
146            data: None,
147            delta: Some(delta),
148            reasoning_text: Some(reasoning_text),
149            current_tool_use: None,
150        }
151    }
152}
153
154impl Default for ModelStreamEvent {
155    fn default() -> Self {
156        Self {
157            data: None,
158            delta: None,
159            reasoning_text: None,
160            current_tool_use: None,
161        }
162    }
163}
164
165impl TypedEvent for ModelStreamEvent {
166    fn is_callback_event(&self) -> bool {
167        self.data.is_some() || self.reasoning_text.is_some() || self.current_tool_use.is_some()
168    }
169
170    fn as_dict(&self) -> serde_json::Value {
171        serde_json::to_value(self).unwrap_or_default()
172    }
173}
174
175/// Event emitted during text content streaming.
176#[derive(Debug, Clone, Serialize, Deserialize)]
177pub struct TextStreamEvent {
178    pub data: String,
179    pub delta: ContentBlockDelta,
180}
181
182impl TextStreamEvent {
183    pub fn new(delta: ContentBlockDelta, text: String) -> Self {
184        Self { data: text, delta }
185    }
186}
187
188impl TypedEvent for TextStreamEvent {
189    fn as_dict(&self) -> serde_json::Value {
190        serde_json::json!({ "data": self.data, "delta": self.delta })
191    }
192}
193
194/// Event emitted during citation streaming.
195#[derive(Debug, Clone, Serialize, Deserialize)]
196pub struct CitationStreamEvent {
197    pub citation: Citation,
198    pub delta: ContentBlockDelta,
199}
200
201impl CitationStreamEvent {
202    pub fn new(delta: ContentBlockDelta, citation: Citation) -> Self {
203        Self { citation, delta }
204    }
205}
206
207impl TypedEvent for CitationStreamEvent {
208    fn as_dict(&self) -> serde_json::Value {
209        serde_json::json!({ "citation": self.citation, "delta": self.delta })
210    }
211}
212
213/// Event emitted during reasoning text streaming.
214#[derive(Debug, Clone, Serialize, Deserialize)]
215pub struct ReasoningTextStreamEvent {
216    pub reasoning_text: Option<String>,
217    pub delta: ContentBlockDelta,
218    pub reasoning: bool,
219}
220
221impl ReasoningTextStreamEvent {
222    pub fn new(delta: ContentBlockDelta, reasoning_text: Option<String>) -> Self {
223        Self { reasoning_text, delta, reasoning: true }
224    }
225}
226
227impl TypedEvent for ReasoningTextStreamEvent {
228    fn as_dict(&self) -> serde_json::Value {
229        serde_json::json!({ "reasoningText": self.reasoning_text, "delta": self.delta, "reasoning": self.reasoning })
230    }
231}
232
233/// Event emitted when model invocation stops.
234#[derive(Debug, Clone, Serialize, Deserialize)]
235pub struct ModelStopReason {
236    pub stop_reason: StopReason,
237    pub message: Message,
238    pub usage: Usage,
239    pub metrics: Metrics,
240}
241
242impl ModelStopReason {
243    pub fn new(stop_reason: StopReason, message: Message, usage: Usage, metrics: Metrics) -> Self {
244        Self { stop_reason, message, usage, metrics }
245    }
246}
247
248impl TypedEvent for ModelStopReason {
249    fn is_callback_event(&self) -> bool { false }
250
251    fn as_dict(&self) -> serde_json::Value {
252        serde_json::json!({ "stop": [self.stop_reason, self.message, self.usage, self.metrics] })
253    }
254}
255
256/// Event emitted when the agent execution completes normally.
257#[derive(Debug, Clone, Serialize, Deserialize)]
258pub struct EventLoopStopEvent {
259    pub stop_reason: StopReason,
260    pub message: Message,
261    pub request_state: serde_json::Value,
262    #[serde(skip_serializing_if = "Option::is_none")]
263    pub interrupts: Option<Vec<Interrupt>>,
264    #[serde(skip_serializing_if = "Option::is_none")]
265    pub structured_output: Option<serde_json::Value>,
266}
267
268impl EventLoopStopEvent {
269    pub fn new(
270        stop_reason: StopReason,
271        message: Message,
272        request_state: serde_json::Value,
273    ) -> Self {
274        Self {
275            stop_reason,
276            message,
277            request_state,
278            interrupts: None,
279            structured_output: None,
280        }
281    }
282
283    pub fn with_interrupts(mut self, interrupts: Vec<Interrupt>) -> Self {
284        self.interrupts = Some(interrupts);
285        self
286    }
287
288    pub fn with_structured_output(mut self, output: serde_json::Value) -> Self {
289        self.structured_output = Some(output);
290        self
291    }
292}
293
294impl TypedEvent for EventLoopStopEvent {
295    fn is_callback_event(&self) -> bool { false }
296
297    fn as_dict(&self) -> serde_json::Value {
298        serde_json::to_value(self).unwrap_or_default()
299    }
300}
301
302/// Event emitted when the event loop is throttled due to rate limiting.
303#[derive(Debug, Clone, Serialize, Deserialize)]
304pub struct EventLoopThrottleEvent {
305    pub event_loop_throttled_delay: u64,
306}
307
308impl EventLoopThrottleEvent {
309    pub fn new(delay: u64) -> Self {
310        Self { event_loop_throttled_delay: delay }
311    }
312}
313
314impl TypedEvent for EventLoopThrottleEvent {
315    fn as_dict(&self) -> serde_json::Value {
316        serde_json::json!({ "event_loop_throttled_delay": self.event_loop_throttled_delay })
317    }
318}
319
320/// Event emitted when a tool execution completes.
321#[derive(Debug, Clone, Serialize, Deserialize)]
322pub struct ToolResultEvent {
323    pub tool_result: ToolResult,
324}
325
326impl ToolResultEvent {
327    pub fn new(tool_result: ToolResult) -> Self {
328        Self { tool_result }
329    }
330
331    pub fn tool_use_id(&self) -> &str {
332        &self.tool_result.tool_use_id
333    }
334}
335
336impl TypedEvent for ToolResultEvent {
337    fn is_callback_event(&self) -> bool { false }
338
339    fn as_dict(&self) -> serde_json::Value {
340        serde_json::json!({ "type": "tool_result", "tool_result": self.tool_result })
341    }
342}
343
344/// Event emitted when a tool yields sub-events as part of tool execution.
345#[derive(Debug, Clone, Serialize, Deserialize)]
346pub struct ToolStreamEvent {
347    pub tool_use: ToolUse,
348    pub data: serde_json::Value,
349}
350
351impl ToolStreamEvent {
352    pub fn new(tool_use: ToolUse, data: serde_json::Value) -> Self {
353        Self { tool_use, data }
354    }
355
356    pub fn tool_use_id(&self) -> &str {
357        &self.tool_use.tool_use_id
358    }
359}
360
361impl TypedEvent for ToolStreamEvent {
362    fn as_dict(&self) -> serde_json::Value {
363        serde_json::json!({ "type": "tool_stream", "tool_stream_event": { "tool_use": self.tool_use, "data": self.data } })
364    }
365}
366
367/// Event emitted when a user cancels a tool call.
368#[derive(Debug, Clone, Serialize, Deserialize)]
369pub struct ToolCancelEvent {
370    pub tool_use: ToolUse,
371    pub message: String,
372}
373
374impl ToolCancelEvent {
375    pub fn new(tool_use: ToolUse, message: String) -> Self {
376        Self { tool_use, message }
377    }
378
379    pub fn tool_use_id(&self) -> &str {
380        &self.tool_use.tool_use_id
381    }
382}
383
384impl TypedEvent for ToolCancelEvent {
385    fn as_dict(&self) -> serde_json::Value {
386        serde_json::json!({ "tool_cancel_event": { "tool_use": self.tool_use, "message": self.message } })
387    }
388}
389
390/// Event emitted when a tool is interrupted.
391#[derive(Debug, Clone, Serialize, Deserialize)]
392pub struct ToolInterruptEvent {
393    pub tool_use: ToolUse,
394    pub interrupts: Vec<Interrupt>,
395}
396
397impl ToolInterruptEvent {
398    pub fn new(tool_use: ToolUse, interrupts: Vec<Interrupt>) -> Self {
399        Self { tool_use, interrupts }
400    }
401
402    pub fn tool_use_id(&self) -> &str {
403        &self.tool_use.tool_use_id
404    }
405}
406
407impl TypedEvent for ToolInterruptEvent {
408    fn as_dict(&self) -> serde_json::Value {
409        serde_json::json!({ "tool_interrupt_event": { "tool_use": self.tool_use, "interrupts": self.interrupts } })
410    }
411}
412
413/// Event emitted when the model invocation has completed.
414#[derive(Debug, Clone, Serialize, Deserialize)]
415pub struct ModelMessageEvent {
416    pub message: Message,
417}
418
419impl ModelMessageEvent {
420    pub fn new(message: Message) -> Self {
421        Self { message }
422    }
423}
424
425impl TypedEvent for ModelMessageEvent {
426    fn as_dict(&self) -> serde_json::Value {
427        serde_json::json!({ "message": self.message })
428    }
429}
430
431/// Event emitted when tool results are formatted as a message.
432#[derive(Debug, Clone, Serialize, Deserialize)]
433pub struct ToolResultMessageEvent {
434    pub message: Message,
435}
436
437impl ToolResultMessageEvent {
438    pub fn new(message: Message) -> Self {
439        Self { message }
440    }
441}
442
443impl TypedEvent for ToolResultMessageEvent {
444    fn as_dict(&self) -> serde_json::Value {
445        serde_json::json!({ "message": self.message })
446    }
447}
448
449/// Event emitted when the agent execution is forcibly stopped.
450#[derive(Debug, Clone, Serialize, Deserialize)]
451pub struct ForceStopEvent {
452    pub force_stop: bool,
453    pub force_stop_reason: String,
454}
455
456impl ForceStopEvent {
457    pub fn new(reason: impl Into<String>) -> Self {
458        Self {
459            force_stop: true,
460            force_stop_reason: reason.into(),
461        }
462    }
463}
464
465impl TypedEvent for ForceStopEvent {
466    fn as_dict(&self) -> serde_json::Value {
467        serde_json::json!({ "force_stop": self.force_stop, "force_stop_reason": self.force_stop_reason })
468    }
469}
470
471/// Multi-agent event emitted when node execution starts.
472#[derive(Debug, Clone, Serialize, Deserialize)]
473pub struct MultiAgentNodeStartEvent {
474    pub node_id: String,
475    pub node_type: String,
476}
477
478impl MultiAgentNodeStartEvent {
479    pub fn new(node_id: impl Into<String>, node_type: impl Into<String>) -> Self {
480        Self {
481            node_id: node_id.into(),
482            node_type: node_type.into(),
483        }
484    }
485}
486
487impl TypedEvent for MultiAgentNodeStartEvent {
488    fn as_dict(&self) -> serde_json::Value {
489        serde_json::json!({ "type": "multiagent_node_start", "node_id": self.node_id, "node_type": self.node_type })
490    }
491}
492
493/// Multi-agent event emitted when node execution stops.
494#[derive(Debug, Clone, Serialize, Deserialize)]
495pub struct MultiAgentNodeStopEvent {
496    pub node_id: String,
497    pub node_result: serde_json::Value,
498}
499
500impl MultiAgentNodeStopEvent {
501    pub fn new(node_id: impl Into<String>, node_result: serde_json::Value) -> Self {
502        Self {
503            node_id: node_id.into(),
504            node_result,
505        }
506    }
507}
508
509impl TypedEvent for MultiAgentNodeStopEvent {
510    fn as_dict(&self) -> serde_json::Value {
511        serde_json::json!({ "type": "multiagent_node_stop", "node_id": self.node_id, "node_result": self.node_result })
512    }
513}
514
515/// Multi-agent handoff event for node transitions.
516#[derive(Debug, Clone, Serialize, Deserialize)]
517pub struct MultiAgentHandoffEvent {
518    pub from_node_ids: Vec<String>,
519    pub to_node_ids: Vec<String>,
520    #[serde(skip_serializing_if = "Option::is_none")]
521    pub message: Option<String>,
522}
523
524impl MultiAgentHandoffEvent {
525    pub fn new(from_node_ids: Vec<String>, to_node_ids: Vec<String>) -> Self {
526        Self {
527            from_node_ids,
528            to_node_ids,
529            message: None,
530        }
531    }
532
533    pub fn with_message(mut self, message: impl Into<String>) -> Self {
534        self.message = Some(message.into());
535        self
536    }
537}
538
539impl TypedEvent for MultiAgentHandoffEvent {
540    fn as_dict(&self) -> serde_json::Value {
541        let mut value = serde_json::json!({
542            "type": "multiagent_handoff",
543            "from_node_ids": self.from_node_ids,
544            "to_node_ids": self.to_node_ids,
545        });
546        if let Some(ref msg) = self.message {
547            value["message"] = serde_json::json!(msg);
548        }
549        value
550    }
551}
552
553/// Multi-agent node stream event.
554#[derive(Debug, Clone, Serialize, Deserialize)]
555pub struct MultiAgentNodeStreamEvent {
556    pub node_id: String,
557    pub event: serde_json::Value,
558}
559
560impl MultiAgentNodeStreamEvent {
561    pub fn new(node_id: impl Into<String>, event: serde_json::Value) -> Self {
562        Self {
563            node_id: node_id.into(),
564            event,
565        }
566    }
567}
568
569impl TypedEvent for MultiAgentNodeStreamEvent {
570    fn as_dict(&self) -> serde_json::Value {
571        serde_json::json!({ "type": "multiagent_node_stream", "node_id": self.node_id, "event": self.event })
572    }
573}
574
575/// Multi-agent node cancel event.
576#[derive(Debug, Clone, Serialize, Deserialize)]
577pub struct MultiAgentNodeCancelEvent {
578    pub node_id: String,
579    pub message: String,
580}
581
582impl MultiAgentNodeCancelEvent {
583    pub fn new(node_id: impl Into<String>, message: impl Into<String>) -> Self {
584        Self {
585            node_id: node_id.into(),
586            message: message.into(),
587        }
588    }
589}
590
591impl TypedEvent for MultiAgentNodeCancelEvent {
592    fn as_dict(&self) -> serde_json::Value {
593        serde_json::json!({ "type": "multiagent_node_cancel", "node_id": self.node_id, "message": self.message })
594    }
595}
596
597/// Multi-agent node interrupt event.
598#[derive(Debug, Clone, Serialize, Deserialize)]
599pub struct MultiAgentNodeInterruptEvent {
600    pub node_id: String,
601    pub interrupts: Vec<Interrupt>,
602}
603
604impl MultiAgentNodeInterruptEvent {
605    pub fn new(node_id: impl Into<String>, interrupts: Vec<Interrupt>) -> Self {
606        Self {
607            node_id: node_id.into(),
608            interrupts,
609        }
610    }
611}
612
613impl TypedEvent for MultiAgentNodeInterruptEvent {
614    fn as_dict(&self) -> serde_json::Value {
615        serde_json::json!({ "type": "multiagent_node_interrupt", "node_id": self.node_id, "interrupts": self.interrupts })
616    }
617}
618
619/// Enum wrapper for all event types.
620#[derive(Debug, Clone, Serialize, Deserialize)]
621#[serde(tag = "type")]
622pub enum AgentEvent {
623    InitEventLoop(InitEventLoopEvent),
624    Start(StartEvent),
625    StartEventLoop(StartEventLoopEvent),
626    ModelStreamChunk(ModelStreamChunkEvent),
627    ModelStream(ModelStreamEvent),
628    TextStream(TextStreamEvent),
629    CitationStream(CitationStreamEvent),
630    ReasoningTextStream(ReasoningTextStreamEvent),
631    ModelStopReason(ModelStopReason),
632    EventLoopStop(EventLoopStopEvent),
633    EventLoopThrottle(EventLoopThrottleEvent),
634    ToolResult(ToolResultEvent),
635    ToolStream(ToolStreamEvent),
636    ToolCancel(ToolCancelEvent),
637    ToolInterrupt(ToolInterruptEvent),
638    ModelMessage(ModelMessageEvent),
639    ToolResultMessage(ToolResultMessageEvent),
640    ForceStop(ForceStopEvent),
641    MultiAgentNodeStart(MultiAgentNodeStartEvent),
642    MultiAgentNodeStop(MultiAgentNodeStopEvent),
643    MultiAgentHandoff(MultiAgentHandoffEvent),
644    MultiAgentNodeStream(MultiAgentNodeStreamEvent),
645    MultiAgentNodeCancel(MultiAgentNodeCancelEvent),
646    MultiAgentNodeInterrupt(MultiAgentNodeInterruptEvent),
647}
648
649impl AgentEvent {
650    pub fn is_callback_event(&self) -> bool {
651        match self {
652            AgentEvent::ModelStopReason(_) => false,
653            AgentEvent::EventLoopStop(_) => false,
654            AgentEvent::ToolResult(_) => false,
655            AgentEvent::ModelStream(e) => e.is_callback_event(),
656            _ => true,
657        }
658    }
659
660    pub fn as_dict(&self) -> serde_json::Value {
661        match self {
662            AgentEvent::InitEventLoop(e) => e.as_dict(),
663            AgentEvent::Start(e) => e.as_dict(),
664            AgentEvent::StartEventLoop(e) => e.as_dict(),
665            AgentEvent::ModelStreamChunk(e) => e.as_dict(),
666            AgentEvent::ModelStream(e) => e.as_dict(),
667            AgentEvent::TextStream(e) => e.as_dict(),
668            AgentEvent::CitationStream(e) => e.as_dict(),
669            AgentEvent::ReasoningTextStream(e) => e.as_dict(),
670            AgentEvent::ModelStopReason(e) => e.as_dict(),
671            AgentEvent::EventLoopStop(e) => e.as_dict(),
672            AgentEvent::EventLoopThrottle(e) => e.as_dict(),
673            AgentEvent::ToolResult(e) => e.as_dict(),
674            AgentEvent::ToolStream(e) => e.as_dict(),
675            AgentEvent::ToolCancel(e) => e.as_dict(),
676            AgentEvent::ToolInterrupt(e) => e.as_dict(),
677            AgentEvent::ModelMessage(e) => e.as_dict(),
678            AgentEvent::ToolResultMessage(e) => e.as_dict(),
679            AgentEvent::ForceStop(e) => e.as_dict(),
680            AgentEvent::MultiAgentNodeStart(e) => e.as_dict(),
681            AgentEvent::MultiAgentNodeStop(e) => e.as_dict(),
682            AgentEvent::MultiAgentHandoff(e) => e.as_dict(),
683            AgentEvent::MultiAgentNodeStream(e) => e.as_dict(),
684            AgentEvent::MultiAgentNodeCancel(e) => e.as_dict(),
685            AgentEvent::MultiAgentNodeInterrupt(e) => e.as_dict(),
686        }
687    }
688}
689
690#[cfg(test)]
691mod tests {
692    use super::*;
693
694    #[test]
695    fn test_init_event_loop_event() {
696        let event = InitEventLoopEvent::new();
697        assert!(event.is_callback_event());
698        let dict = event.as_dict();
699        assert_eq!(dict["init_event_loop"], true);
700    }
701
702    #[test]
703    fn test_model_stream_event() {
704        let delta = ContentBlockDelta::default();
705        let event = ModelStreamEvent::text(delta, "Hello".to_string());
706        assert!(event.is_callback_event());
707    }
708
709    #[test]
710    fn test_empty_model_stream_event() {
711        let event = ModelStreamEvent::default();
712        assert!(!event.is_callback_event());
713    }
714
715    #[test]
716    fn test_force_stop_event() {
717        let event = ForceStopEvent::new("Test reason");
718        assert!(event.force_stop);
719        assert_eq!(event.force_stop_reason, "Test reason");
720    }
721}
722