Skip to main content

oxicode_ai/providers/
event.rs

1//! Provider streaming events
2
3use std::sync::Arc;
4
5use crate::{AssistantMessage, StopReason, ToolCall};
6
7/// Streaming events emitted by providers
8///
9/// Note: We use crate::AssistantMessage directly to avoid type alias conflicts
10#[derive(Debug, Clone)]
11#[non_exhaustive]
12pub enum ProviderEvent {
13    /// Stream started with partial assistant message.
14    Start {
15        /// Partial assistant message state.
16        partial: Arc<AssistantMessage>,
17    },
18
19    /// Text content block started.
20    TextStart {
21        /// Index of the content block in the message.
22        content_index: usize,
23        /// Partial assistant message state.
24        partial: Arc<AssistantMessage>,
25    },
26
27    /// Incremental text delta received.
28    TextDelta {
29        /// Index of the content block in the message.
30        content_index: usize,
31        /// The text delta to append.
32        delta: String,
33        /// Partial assistant message state.
34        partial: Arc<AssistantMessage>,
35    },
36
37    /// Text content block finished.
38    TextEnd {
39        /// Index of the content block in the message.
40        content_index: usize,
41        /// The complete text content.
42        content: String,
43        /// Partial assistant message state.
44        partial: Arc<AssistantMessage>,
45    },
46
47    /// Thinking content block started.
48    ThinkingStart {
49        /// Index of the content block in the message.
50        content_index: usize,
51        /// Partial assistant message state.
52        partial: Arc<AssistantMessage>,
53    },
54
55    /// Incremental thinking delta received.
56    ThinkingDelta {
57        /// Index of the content block in the message.
58        content_index: usize,
59        /// The thinking text delta to append.
60        delta: String,
61        /// Partial assistant message state.
62        partial: Arc<AssistantMessage>,
63    },
64
65    /// Thinking content block finished.
66    ThinkingEnd {
67        /// Index of the content block in the message.
68        content_index: usize,
69        /// The complete thinking content.
70        content: String,
71        /// Partial assistant message state.
72        partial: Arc<AssistantMessage>,
73    },
74
75    /// Tool call block started.
76    ToolCallStart {
77        /// Index of the content block in the message.
78        content_index: usize,
79        /// The tool call ID from the provider, if available at start time.
80        /// Providers that only surface the ID later (in deltas/end) leave this `None`.
81        tool_call_id: Option<String>,
82        /// The tool name, if available at start time.
83        tool_name: Option<String>,
84        /// Partial assistant message state.
85        partial: Arc<AssistantMessage>,
86    },
87
88    /// Tool call delta received (partial JSON arguments).
89    ToolCallDelta {
90        /// Index of the content block in the message.
91        content_index: usize,
92        /// The delta string to append to tool arguments.
93        delta: String,
94        /// Partial assistant message state.
95        partial: Arc<AssistantMessage>,
96    },
97
98    /// Tool call block finished.
99    ToolCallEnd {
100        /// Index of the content block in the message.
101        content_index: usize,
102        /// The complete tool call with resolved arguments.
103        tool_call: ToolCall,
104        /// Partial assistant message state.
105        partial: Arc<AssistantMessage>,
106    },
107
108    /// Image content block started (incremental image streaming, e.g. Gemini).
109    ///
110    /// Mirrors omp's `AssistantMessageEvent::image_start`. Without this event
111    /// family, providers that stream images incrementally (Gemini) cannot
112    /// represent image output — see P0-D in the omp-realignment design.
113    ImageStart {
114        /// Index of the content block in the message.
115        content_index: usize,
116        /// MIME type of the incoming image (e.g. `image/png`).
117        mime_type: String,
118        /// Partial assistant message state.
119        partial: Arc<AssistantMessage>,
120    },
121
122    /// Incremental image delta received (base64-encoded chunk).
123    ImageDelta {
124        /// Index of the content block in the message.
125        content_index: usize,
126        /// Base64-encoded image bytes delta to append.
127        delta: String,
128        /// Partial assistant message state.
129        partial: Arc<AssistantMessage>,
130    },
131
132    /// Image content block finished.
133    ImageEnd {
134        /// Index of the content block in the message.
135        content_index: usize,
136        /// The complete base64-encoded image data.
137        data: String,
138        /// MIME type of the image.
139        mime_type: String,
140        /// Partial assistant message state.
141        partial: Arc<AssistantMessage>,
142    },
143
144    /// Stream completed successfully.
145    Done {
146        /// Why the model stopped generating.
147        reason: StopReason,
148        /// The final assistant message.
149        message: AssistantMessage,
150    },
151
152    /// Stream ended with an error.
153    Error {
154        /// The stop reason at time of error.
155        reason: StopReason,
156        /// Error details in assistant message form.
157        error: AssistantMessage,
158    },
159}
160
161impl ProviderEvent {
162    /// Extract the partial assistant message if present
163    pub fn partial(&self) -> Option<&AssistantMessage> {
164        match self {
165            ProviderEvent::Start { partial }
166            | ProviderEvent::TextStart { partial, .. }
167            | ProviderEvent::TextDelta { partial, .. }
168            | ProviderEvent::TextEnd { partial, .. }
169            | ProviderEvent::ThinkingStart { partial, .. }
170            | ProviderEvent::ThinkingDelta { partial, .. }
171            | ProviderEvent::ThinkingEnd { partial, .. }
172            | ProviderEvent::ToolCallStart { partial, .. }
173            | ProviderEvent::ToolCallDelta { partial, .. }
174            | ProviderEvent::ToolCallEnd { partial, .. }
175            | ProviderEvent::ImageStart { partial, .. }
176            | ProviderEvent::ImageDelta { partial, .. }
177            | ProviderEvent::ImageEnd { partial, .. } => Some(partial),
178            ProviderEvent::Done { message, .. } => Some(message),
179            ProviderEvent::Error { error, .. } => Some(error),
180        }
181    }
182
183    /// Check if this is a done event
184    pub fn is_done(&self) -> bool {
185        matches!(self, ProviderEvent::Done { .. })
186    }
187
188    /// Check if this is an error event
189    pub fn is_error(&self) -> bool {
190        matches!(self, ProviderEvent::Error { .. })
191    }
192}