Skip to main content

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}