Skip to main content

agent_sdk/
events.rs

1//! Agent events for real-time streaming.
2//!
3//! The [`AgentEvent`] enum represents all events that can occur during agent
4//! execution. These events are streamed via an async channel for real-time
5//! UI updates and logging.
6//!
7//! # Event Flow
8//!
9//! A typical event sequence looks like:
10//! 1. `Start` - Agent begins processing
11//! 2. `Text` / `ToolCallStart` / `ToolCallEnd` - Processing events
12//! 3. `TurnComplete` - One LLM round-trip finished
13//! 4. `Done` - Agent completed successfully, or `Error` if failed
14
15use crate::types::{ThreadId, TokenUsage, ToolResult, ToolTier};
16use serde::{Deserialize, Serialize};
17use std::time::Duration;
18
19/// Events emitted by the agent loop during execution.
20/// These are streamed to the client for real-time UI updates.
21#[derive(Clone, Debug, Serialize, Deserialize)]
22#[serde(tag = "type", rename_all = "snake_case")]
23pub enum AgentEvent {
24    /// Agent loop has started
25    Start { thread_id: ThreadId, turn: usize },
26
27    /// Agent is "thinking" - streaming text that may be shown as typing indicator
28    Thinking { text: String },
29
30    /// A text delta for streaming responses
31    TextDelta { delta: String },
32
33    /// Complete text block from the agent
34    Text { text: String },
35
36    /// Agent is about to call a tool
37    ToolCallStart {
38        id: String,
39        name: String,
40        display_name: String,
41        input: serde_json::Value,
42        tier: ToolTier,
43    },
44
45    /// Tool execution completed
46    ToolCallEnd {
47        id: String,
48        name: String,
49        display_name: String,
50        result: ToolResult,
51    },
52
53    /// Progress update from an async tool operation
54    ToolProgress {
55        /// Tool call ID
56        id: String,
57        /// Tool name
58        name: String,
59        /// Human-readable display name
60        display_name: String,
61        /// Progress stage
62        stage: String,
63        /// Human-readable progress message
64        message: String,
65        /// Optional tool-specific data
66        data: Option<serde_json::Value>,
67    },
68
69    /// Tool requires confirmation before execution.
70    /// The application determines the confirmation type (normal, PIN, biometric).
71    ToolRequiresConfirmation {
72        id: String,
73        name: String,
74        input: serde_json::Value,
75        description: String,
76    },
77
78    /// Agent turn completed (one LLM round-trip)
79    TurnComplete { turn: usize, usage: TokenUsage },
80
81    /// Agent loop completed successfully
82    Done {
83        thread_id: ThreadId,
84        total_turns: usize,
85        total_usage: TokenUsage,
86        duration: Duration,
87    },
88
89    /// An error occurred during execution
90    Error { message: String, recoverable: bool },
91
92    /// Context was compacted to reduce size
93    ContextCompacted {
94        /// Number of messages before compaction
95        original_count: usize,
96        /// Number of messages after compaction
97        new_count: usize,
98        /// Estimated tokens before compaction
99        original_tokens: usize,
100        /// Estimated tokens after compaction
101        new_tokens: usize,
102    },
103
104    /// Progress update from a running subagent
105    SubagentProgress {
106        /// ID of the parent tool call that spawned this subagent
107        subagent_id: String,
108        /// Name of the subagent (e.g., "explore", "plan")
109        subagent_name: String,
110        /// Tool name that just started or completed
111        tool_name: String,
112        /// Brief context for the tool (e.g., file path, pattern)
113        tool_context: String,
114        /// Whether the tool completed (false = started, true = ended)
115        completed: bool,
116        /// Whether the tool succeeded (only meaningful if completed)
117        success: bool,
118        /// Current total tool count for this subagent
119        tool_count: u32,
120        /// Current total tokens used by this subagent
121        total_tokens: u64,
122    },
123}
124
125impl AgentEvent {
126    #[must_use]
127    pub const fn start(thread_id: ThreadId, turn: usize) -> Self {
128        Self::Start { thread_id, turn }
129    }
130
131    #[must_use]
132    pub fn thinking(text: impl Into<String>) -> Self {
133        Self::Thinking { text: text.into() }
134    }
135
136    #[must_use]
137    pub fn text_delta(delta: impl Into<String>) -> Self {
138        Self::TextDelta {
139            delta: delta.into(),
140        }
141    }
142
143    #[must_use]
144    pub fn text(text: impl Into<String>) -> Self {
145        Self::Text { text: text.into() }
146    }
147
148    #[must_use]
149    pub fn tool_call_start(
150        id: impl Into<String>,
151        name: impl Into<String>,
152        display_name: impl Into<String>,
153        input: serde_json::Value,
154        tier: ToolTier,
155    ) -> Self {
156        Self::ToolCallStart {
157            id: id.into(),
158            name: name.into(),
159            display_name: display_name.into(),
160            input,
161            tier,
162        }
163    }
164
165    #[must_use]
166    pub fn tool_call_end(
167        id: impl Into<String>,
168        name: impl Into<String>,
169        display_name: impl Into<String>,
170        result: ToolResult,
171    ) -> Self {
172        Self::ToolCallEnd {
173            id: id.into(),
174            name: name.into(),
175            display_name: display_name.into(),
176            result,
177        }
178    }
179
180    #[must_use]
181    pub fn tool_progress(
182        id: impl Into<String>,
183        name: impl Into<String>,
184        display_name: impl Into<String>,
185        stage: impl Into<String>,
186        message: impl Into<String>,
187        data: Option<serde_json::Value>,
188    ) -> Self {
189        Self::ToolProgress {
190            id: id.into(),
191            name: name.into(),
192            display_name: display_name.into(),
193            stage: stage.into(),
194            message: message.into(),
195            data,
196        }
197    }
198
199    #[must_use]
200    pub const fn done(
201        thread_id: ThreadId,
202        total_turns: usize,
203        total_usage: TokenUsage,
204        duration: Duration,
205    ) -> Self {
206        Self::Done {
207            thread_id,
208            total_turns,
209            total_usage,
210            duration,
211        }
212    }
213
214    #[must_use]
215    pub fn error(message: impl Into<String>, recoverable: bool) -> Self {
216        Self::Error {
217            message: message.into(),
218            recoverable,
219        }
220    }
221
222    #[must_use]
223    pub const fn context_compacted(
224        original_count: usize,
225        new_count: usize,
226        original_tokens: usize,
227        new_tokens: usize,
228    ) -> Self {
229        Self::ContextCompacted {
230            original_count,
231            new_count,
232            original_tokens,
233            new_tokens,
234        }
235    }
236}