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        /// Browser tab id that produced this progress (if the tool is
91        /// tab-aware). `None` for tools that don't have a tab concept,
92        /// or for older tool implementations that don't propagate tab ids.
93        #[serde(default, skip_serializing_if = "Option::is_none")]
94        tab_id: Option<uuid::Uuid>,
95    },
96
97    /// A tool execution has finished.
98    ToolExecutionEnd {
99        /// Identifier of the completed tool call.
100        tool_call_id: String,
101        /// Name of the tool.
102        tool_name: String,
103        /// The tool result payload.
104        result: oxi_ai::ToolResult,
105        /// Whether the tool execution resulted in an error.
106        is_error: bool,
107    },
108
109    // ── Legacy events (kept for backward compatibility) ──────────
110    /// Legacy: agent started processing a prompt.
111    #[serde(rename = "start")]
112    Start {
113        /// The user prompt that triggered the run.
114        prompt: String,
115    },
116
117    /// Agent is waiting for the first response token.
118    Thinking,
119
120    /// Incremental thinking / reasoning text from the model.
121    ThinkingDelta {
122        /// The reasoning text delta.
123        text: String,
124    },
125
126    /// A chunk of generated text from the model.
127    TextChunk {
128        /// The text delta to append.
129        text: String,
130    },
131
132    /// The model requested a tool call.
133    ToolCall {
134        /// The tool call descriptor from the provider.
135        tool_call: oxi_ai::ToolCall,
136    },
137
138    /// A tool execution has started.
139    ToolStart {
140        /// Identifier of the tool call.
141        tool_call_id: String,
142        /// Name of the tool being invoked.
143        tool_name: String,
144        /// JSON arguments for the tool call.
145        #[serde(default)]
146        arguments: serde_json::Value,
147    },
148
149    /// Progress update from a running tool.
150    ToolProgress {
151        /// Identifier of the tool call.
152        tool_call_id: String,
153        /// Human-readable progress message.
154        message: String,
155    },
156
157    /// A tool execution has completed.
158    ToolComplete {
159        /// The tool result payload.
160        result: oxi_ai::ToolResult,
161    },
162
163    /// A tool execution failed.
164    ToolError {
165        /// Identifier of the failed tool call.
166        tool_call_id: String,
167        /// Error description.
168        error: String,
169    },
170
171    /// The agent produced a final response.
172    Complete {
173        /// Full response text.
174        content: String,
175        /// Stop reason string (e.g. `"EndTurn"`).
176        stop_reason: String,
177    },
178
179    /// An error occurred during agent execution.
180    Error {
181        /// Human-readable error message.
182        message: String,
183        /// Optional session identifier.
184        session_id: Option<String>,
185    },
186
187    /// Agent loop iteration counter update.
188    Iteration {
189        /// Current iteration number.
190        number: usize,
191    },
192
193    /// Token usage report for a completed turn.
194    Usage {
195        /// Number of prompt / input tokens consumed.
196        input_tokens: usize,
197        /// Number of completion / output tokens produced.
198        output_tokens: usize,
199    },
200
201    /// Context compaction lifecycle event.
202    Compaction {
203        /// The underlying compaction event detail.
204        event: CompactionEvent,
205    },
206
207    /// The agent is retrying after a transient error.
208    Retry {
209        /// Current retry attempt (1-based).
210        attempt: usize,
211        /// Maximum number of retries allowed.
212        max_retries: usize,
213        /// Seconds until the next attempt.
214        retry_after_secs: u64,
215        /// Why the previous attempt failed.
216        reason: String,
217        /// Optional session identifier.
218        session_id: Option<String>,
219    },
220
221    /// The agent switched to a fallback model.
222    Fallback {
223        /// Model that was being used before the failure.
224        from_model: String,
225        /// Fallback model that will be used instead.
226        to_model: String,
227    },
228
229    /// The agent run was cancelled by the caller.
230    Cancelled,
231
232    /// A partial response delivered mid-stream (useful for UI rendering).
233    PartialResponse {
234        /// Accumulated response content so far.
235        content: String,
236    },
237
238    // ── Auto-retry events ─────────────────────────────────────────
239    /// An automatic retry attempt is starting.
240    AutoRetryStart {
241        /// Current retry attempt (1-based).
242        attempt: usize,
243        /// Total retry attempts that will be made.
244        max_attempts: usize,
245        /// Milliseconds before this attempt is sent.
246        delay_ms: u64,
247        /// The error that triggered the retry.
248        error_message: String,
249    },
250
251    /// An automatic retry attempt has concluded.
252    AutoRetryEnd {
253        /// Whether the retry succeeded.
254        success: bool,
255        /// Which attempt this was (1-based).
256        attempt: usize,
257        /// Final error if the retry failed, `None` on success.
258        final_error: Option<String>,
259    },
260
261    // ── Loop-specific steering events ─────────────────────────────
262    /// A system-level steering message injected into the conversation.
263    SteeringMessage {
264        /// The steering message to add to the context.
265        message: oxi_ai::Message,
266    },
267
268    /// A follow-up message appended to continue the conversation.
269    FollowUpMessage {
270        /// The follow-up message.
271        message: oxi_ai::Message,
272    },
273}
274
275impl AgentEvent {
276    /// Returns `true` if this event represents the end of the agent lifecycle.
277    pub fn is_terminal(&self) -> bool {
278        matches!(self, AgentEvent::AgentEnd { .. })
279    }
280
281    /// Returns the snake_case variant name of this event (useful for logging / serialization).
282    pub fn type_name(&self) -> &'static str {
283        match self {
284            AgentEvent::AgentStart { .. } => "agent_start",
285            AgentEvent::AgentEnd { .. } => "agent_end",
286            AgentEvent::TurnStart { .. } => "turn_start",
287            AgentEvent::TurnEnd { .. } => "turn_end",
288            AgentEvent::MessageStart { .. } => "message_start",
289            AgentEvent::MessageUpdate { .. } => "message_update",
290            AgentEvent::MessageEnd { .. } => "message_end",
291            AgentEvent::ToolExecutionStart { .. } => "tool_execution_start",
292            AgentEvent::ToolExecutionUpdate { .. } => "tool_execution_update",
293            AgentEvent::ToolExecutionEnd { .. } => "tool_execution_end",
294            AgentEvent::Start { .. } => "start",
295            AgentEvent::Thinking => "thinking",
296            AgentEvent::ThinkingDelta { .. } => "thinking_delta",
297            AgentEvent::TextChunk { .. } => "text_chunk",
298            AgentEvent::ToolCall { .. } => "tool_call",
299            AgentEvent::ToolStart { .. } => "tool_start",
300            AgentEvent::ToolProgress { .. } => "tool_progress",
301            AgentEvent::ToolComplete { .. } => "tool_complete",
302            AgentEvent::ToolError { .. } => "tool_error",
303            AgentEvent::Complete { .. } => "complete",
304            AgentEvent::Error { .. } => "error",
305            AgentEvent::Iteration { .. } => "iteration",
306            AgentEvent::Usage { .. } => "usage",
307            AgentEvent::Compaction { .. } => "compaction",
308            AgentEvent::Retry { .. } => "retry",
309            AgentEvent::Fallback { .. } => "fallback",
310            AgentEvent::Cancelled => "cancelled",
311            AgentEvent::PartialResponse { .. } => "partial_response",
312            AgentEvent::AutoRetryStart { .. } => "auto_retry_start",
313            AgentEvent::AutoRetryEnd { .. } => "auto_retry_end",
314            AgentEvent::SteeringMessage { .. } => "steering_message",
315            AgentEvent::FollowUpMessage { .. } => "follow_up_message",
316        }
317    }
318}