Skip to main content

oxi_agent/
events.rs

1/// Agent event system
2/// Defines all events emitted during an agent run, including lifecycle,
3/// streaming, tool execution, compaction, retry, and steering events.
4use crate::compaction::CompactionEvent;
5use serde::{Deserialize, Serialize};
6
7/// Events emitted during agent execution.
8///
9/// Events are tagged with `type` and serialized as camelCase for JSON consumers.
10/// This enum is `#[non_exhaustive]` — new variants may be added in future releases.
11#[derive(Debug, Clone, Serialize, Deserialize)]
12#[serde(tag = "type", rename_all = "camelCase")]
13#[non_exhaustive]
14pub enum AgentEvent {
15    // ── Lifecycle events ──────────────────────────────────────────────
16    /// Emitted when the agent begins processing a batch of prompts.
17    AgentStart {
18        /// The initial prompt messages sent to the agent.
19        prompts: Vec<oxi_ai::Message>,
20        /// Optional session identifier for correlation.
21        session_id: Option<String>,
22    },
23
24    /// Emitted when the agent finishes all processing.
25    AgentEnd {
26        /// Final conversation messages.
27        messages: Vec<oxi_ai::Message>,
28        /// Why the agent stopped (e.g. `"end_turn"`, `"tool_use"`).
29        stop_reason: Option<String>,
30        /// Optional session identifier for correlation.
31        session_id: Option<String>,
32    },
33
34    /// Emitted at the start of each agent loop turn.
35    TurnStart {
36        /// Zero-based turn index.
37        turn_number: u32,
38    },
39
40    /// Emitted when a turn completes, including the assistant reply and tool results.
41    TurnEnd {
42        /// Turn index that just completed.
43        turn_number: u32,
44        /// The assistant message produced this turn.
45        assistant_message: oxi_ai::Message,
46        /// Tool results collected during this turn.
47        tool_results: Vec<oxi_ai::ToolResultMessage>,
48    },
49
50    // ── Message events ────────────────────────────────────────────────
51    /// A new message has been created in the conversation.
52    MessageStart {
53        /// The message that started.
54        message: oxi_ai::Message,
55    },
56
57    /// A message has been updated with new content.
58    MessageUpdate {
59        /// The message in its current state.
60        message: oxi_ai::Message,
61        /// Incremental text delta since the last update, if available.
62        delta: Option<String>,
63    },
64
65    /// A message has been finalized.
66    MessageEnd {
67        /// The completed message.
68        message: oxi_ai::Message,
69    },
70
71    // ── Tool execution events ────────────────────────────────────────
72    /// A tool is about to be executed.
73    ToolExecutionStart {
74        /// Unique identifier for this tool call.
75        tool_call_id: String,
76        /// Name of the tool being invoked.
77        tool_name: String,
78        /// JSON arguments passed to the tool.
79        args: serde_json::Value,
80    },
81
82    /// Partial progress from a running tool execution.
83    ToolExecutionUpdate {
84        /// Identifier of the tool call producing the update.
85        tool_call_id: String,
86        /// Name of the tool.
87        tool_name: String,
88        /// Partial result text so far.
89        partial_result: String,
90    },
91
92    /// A tool execution has finished.
93    ToolExecutionEnd {
94        /// Identifier of the completed tool call.
95        tool_call_id: String,
96        /// Name of the tool.
97        tool_name: String,
98        /// The tool result payload.
99        result: oxi_ai::ToolResult,
100        /// Whether the tool execution resulted in an error.
101        is_error: bool,
102    },
103
104    // ── Legacy events (kept for backward compatibility) ──────────
105    /// Legacy: agent started processing a prompt.
106    #[serde(rename = "start")]
107    Start {
108        /// The user prompt that triggered the run.
109        prompt: String,
110    },
111
112    /// Agent is waiting for the first response token.
113    Thinking,
114
115    /// Incremental thinking / reasoning text from the model.
116    ThinkingDelta {
117        /// The reasoning text delta.
118        text: String,
119    },
120
121    /// A chunk of generated text from the model.
122    TextChunk {
123        /// The text delta to append.
124        text: String,
125    },
126
127    /// The model requested a tool call.
128    ToolCall {
129        /// The tool call descriptor from the provider.
130        tool_call: oxi_ai::ToolCall,
131    },
132
133    /// A tool execution has started.
134    ToolStart {
135        /// Identifier of the tool call.
136        tool_call_id: String,
137        /// Name of the tool being invoked.
138        tool_name: String,
139        /// JSON arguments for the tool call.
140        #[serde(default)]
141        arguments: serde_json::Value,
142    },
143
144    /// Progress update from a running tool.
145    ToolProgress {
146        /// Identifier of the tool call.
147        tool_call_id: String,
148        /// Human-readable progress message.
149        message: String,
150    },
151
152    /// A tool execution has completed.
153    ToolComplete {
154        /// The tool result payload.
155        result: oxi_ai::ToolResult,
156    },
157
158    /// A tool execution failed.
159    ToolError {
160        /// Identifier of the failed tool call.
161        tool_call_id: String,
162        /// Error description.
163        error: String,
164    },
165
166    /// The agent produced a final response.
167    Complete {
168        /// Full response text.
169        content: String,
170        /// Stop reason string (e.g. `"EndTurn"`).
171        stop_reason: String,
172    },
173
174    /// An error occurred during agent execution.
175    Error {
176        /// Human-readable error message.
177        message: String,
178        /// Optional session identifier.
179        session_id: Option<String>,
180    },
181
182    /// Agent loop iteration counter update.
183    Iteration {
184        /// Current iteration number.
185        number: usize,
186    },
187
188    /// Token usage report for a completed turn.
189    Usage {
190        /// Number of prompt / input tokens consumed.
191        input_tokens: usize,
192        /// Number of completion / output tokens produced.
193        output_tokens: usize,
194    },
195
196    /// Context compaction lifecycle event.
197    Compaction {
198        /// The underlying compaction event detail.
199        event: CompactionEvent,
200    },
201
202    /// The agent is retrying after a transient error.
203    Retry {
204        /// Current retry attempt (1-based).
205        attempt: usize,
206        /// Maximum number of retries allowed.
207        max_retries: usize,
208        /// Seconds until the next attempt.
209        retry_after_secs: u64,
210        /// Why the previous attempt failed.
211        reason: String,
212        /// Optional session identifier.
213        session_id: Option<String>,
214    },
215
216    /// The agent switched to a fallback model.
217    Fallback {
218        /// Model that was being used before the failure.
219        from_model: String,
220        /// Fallback model that will be used instead.
221        to_model: String,
222    },
223
224    /// The agent run was cancelled by the caller.
225    Cancelled,
226
227    /// A partial response delivered mid-stream (useful for UI rendering).
228    PartialResponse {
229        /// Accumulated response content so far.
230        content: String,
231    },
232
233    // ── Auto-retry events ─────────────────────────────────────────
234    /// An automatic retry attempt is starting.
235    AutoRetryStart {
236        /// Current retry attempt (1-based).
237        attempt: usize,
238        /// Total retry attempts that will be made.
239        max_attempts: usize,
240        /// Milliseconds before this attempt is sent.
241        delay_ms: u64,
242        /// The error that triggered the retry.
243        error_message: String,
244    },
245
246    /// An automatic retry attempt has concluded.
247    AutoRetryEnd {
248        /// Whether the retry succeeded.
249        success: bool,
250        /// Which attempt this was (1-based).
251        attempt: usize,
252        /// Final error if the retry failed, `None` on success.
253        final_error: Option<String>,
254    },
255
256    // ── Loop-specific steering events ─────────────────────────────
257    /// A system-level steering message injected into the conversation.
258    SteeringMessage {
259        /// The steering message to add to the context.
260        message: oxi_ai::Message,
261    },
262
263    /// A follow-up message appended to continue the conversation.
264    FollowUpMessage {
265        /// The follow-up message.
266        message: oxi_ai::Message,
267    },
268}
269
270impl AgentEvent {
271    /// Returns `true` if this event represents the end of the agent lifecycle.
272    pub fn is_terminal(&self) -> bool {
273        matches!(self, AgentEvent::AgentEnd { .. })
274    }
275
276    /// Returns the snake_case variant name of this event (useful for logging / serialization).
277    pub fn type_name(&self) -> &'static str {
278        match self {
279            AgentEvent::AgentStart { .. } => "agent_start",
280            AgentEvent::AgentEnd { .. } => "agent_end",
281            AgentEvent::TurnStart { .. } => "turn_start",
282            AgentEvent::TurnEnd { .. } => "turn_end",
283            AgentEvent::MessageStart { .. } => "message_start",
284            AgentEvent::MessageUpdate { .. } => "message_update",
285            AgentEvent::MessageEnd { .. } => "message_end",
286            AgentEvent::ToolExecutionStart { .. } => "tool_execution_start",
287            AgentEvent::ToolExecutionUpdate { .. } => "tool_execution_update",
288            AgentEvent::ToolExecutionEnd { .. } => "tool_execution_end",
289            AgentEvent::Start { .. } => "start",
290            AgentEvent::Thinking => "thinking",
291            AgentEvent::ThinkingDelta { .. } => "thinking_delta",
292            AgentEvent::TextChunk { .. } => "text_chunk",
293            AgentEvent::ToolCall { .. } => "tool_call",
294            AgentEvent::ToolStart { .. } => "tool_start",
295            AgentEvent::ToolProgress { .. } => "tool_progress",
296            AgentEvent::ToolComplete { .. } => "tool_complete",
297            AgentEvent::ToolError { .. } => "tool_error",
298            AgentEvent::Complete { .. } => "complete",
299            AgentEvent::Error { .. } => "error",
300            AgentEvent::Iteration { .. } => "iteration",
301            AgentEvent::Usage { .. } => "usage",
302            AgentEvent::Compaction { .. } => "compaction",
303            AgentEvent::Retry { .. } => "retry",
304            AgentEvent::Fallback { .. } => "fallback",
305            AgentEvent::Cancelled => "cancelled",
306            AgentEvent::PartialResponse { .. } => "partial_response",
307            AgentEvent::AutoRetryStart { .. } => "auto_retry_start",
308            AgentEvent::AutoRetryEnd { .. } => "auto_retry_end",
309            AgentEvent::SteeringMessage { .. } => "steering_message",
310            AgentEvent::FollowUpMessage { .. } => "follow_up_message",
311        }
312    }
313}