orion_core/events.rs
1use serde::{Deserialize, Serialize};
2
3use crate::messages::{Message, ToolResult};
4
5/// Events emitted by the agent loop.
6/// Mirrors pi-agent-core's event system for UI reactivity.
7///
8/// This enum is `#[non_exhaustive]`: match it with a wildcard arm, as new
9/// event variants may be added in a minor release.
10#[derive(Debug, Clone, Serialize, Deserialize)]
11#[serde(tag = "type", rename_all = "snake_case")]
12#[non_exhaustive]
13pub enum AgentEvent {
14 /// Agent begins processing a prompt.
15 AgentStart,
16
17 /// Agent finished all processing.
18 AgentEnd {
19 /// All messages produced during this `prompt()` call.
20 messages: Vec<Message>,
21 },
22
23 /// A new turn begins (one LLM call + any tool executions).
24 TurnStart,
25
26 /// A turn completed.
27 TurnEnd {
28 /// The assistant message produced by the turn.
29 message: Message,
30 /// Results of any tools the turn executed.
31 tool_results: Vec<ToolResult>,
32 },
33
34 /// A message was added (user, assistant, or tool_result).
35 MessageStart {
36 /// The message that was added.
37 message: Message,
38 },
39
40 /// Streaming delta for the current assistant message.
41 MessageDelta {
42 /// The new token/chunk of text.
43 delta: String,
44 /// Tokens generated so far in this response.
45 tokens_generated: u32,
46 /// Current generation speed.
47 tokens_per_sec: f64,
48 },
49
50 /// A message is complete.
51 MessageEnd {
52 /// The completed message.
53 message: Message,
54 },
55
56 /// Timing and token statistics for one completed LLM generation.
57 ///
58 /// **Emission guarantee.** Exactly one `GenerationStats` is emitted for each
59 /// LLM iteration that runs to completion within a single `prompt()` call -
60 /// no more, no less - and always before that turn's `MessageEnd`/`TurnEnd`
61 /// and before the run's closing `AgentEnd`. When tools fire, a `prompt()`
62 /// spans several iterations; summing the `tokens_generated` / `prompt_tokens`
63 /// of every `GenerationStats` in the run therefore yields the exact per-run
64 /// totals, with no gaps and no double counting. A generation that is aborted
65 /// or errors before completing produces no result and so emits no
66 /// `GenerationStats` (the internal summarization pass likewise does not emit
67 /// one). Consumers metering usage can rely on this contract; it is pinned by
68 /// tests.
69 GenerationStats {
70 /// Tokens generated in the response.
71 tokens_generated: u32,
72 /// Tokens in the formatted prompt.
73 prompt_tokens: u32,
74 /// Average generation speed in tokens per second.
75 tokens_per_sec: f64,
76 /// Time to the first emitted token, in milliseconds.
77 time_to_first_token_ms: f64,
78 /// Total generation time, in milliseconds.
79 generation_time_ms: f64,
80 },
81
82 /// A tool execution started.
83 ToolExecStart {
84 /// Id of the tool call being executed.
85 tool_call_id: String,
86 /// Name of the tool being executed.
87 tool_name: String,
88 /// Arguments passed to the tool.
89 args: serde_json::Value,
90 },
91
92 /// Streaming progress from a tool execution.
93 ToolExecUpdate {
94 /// Id of the tool call reporting progress.
95 tool_call_id: String,
96 /// Name of the tool reporting progress.
97 tool_name: String,
98 /// Partial output emitted so far.
99 partial: String,
100 },
101
102 /// A tool execution completed.
103 ToolExecEnd {
104 /// Id of the completed tool call.
105 tool_call_id: String,
106 /// Name of the completed tool.
107 tool_name: String,
108 /// The tool's result.
109 result: ToolResult,
110 },
111
112 /// A tool call was refused by the approval hook and never executed.
113 ///
114 /// Distinct from a `ToolExecEnd` carrying an error result: that signals a
115 /// tool that ran and failed, whereas this signals a call that was blocked
116 /// before execution. The same `reason` is also appended to the conversation
117 /// as an error tool result so the model can adapt.
118 ToolDenied {
119 /// Id of the denied tool call.
120 tool_call_id: String,
121 /// Name of the denied tool.
122 tool_name: String,
123 /// Human-readable reason the call was refused.
124 reason: String,
125 },
126
127 /// Context budget info after formatting.
128 ContextBudget {
129 /// Tokens used by the prepared prompt.
130 used_tokens: u32,
131 /// Maximum context tokens available.
132 max_tokens: u32,
133 /// Number of messages kept in the prompt.
134 messages_in_context: u32,
135 /// Number of messages pruned to fit.
136 messages_pruned: u32,
137 },
138
139 /// Non-fatal warning during processing.
140 Warning {
141 /// Human-readable warning text.
142 message: String,
143 },
144
145 /// Fatal error that stopped processing.
146 Error {
147 /// Human-readable error text.
148 message: String,
149 },
150}