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