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}