1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3use std::collections::BTreeMap;
4use std::path::PathBuf;
5
6#[derive(Clone, Debug, Deserialize, Serialize)]
18#[serde(tag = "type")]
19pub enum ThreadEvent {
20 #[serde(rename = "thread.started", alias = "thread.resumed")]
21 ThreadStarted(ThreadStarted),
22 #[serde(rename = "turn.started")]
23 TurnStarted(TurnStarted),
24 #[serde(rename = "turn.completed")]
25 TurnCompleted(TurnCompleted),
26 #[serde(rename = "turn.failed")]
27 TurnFailed(TurnFailed),
28 #[serde(rename = "item.started", alias = "item.created")]
29 ItemStarted(ItemEnvelope<ItemSnapshot>),
30 #[serde(rename = "item.delta", alias = "item.updated")]
31 ItemDelta(ItemDelta),
32 #[serde(rename = "item.completed")]
33 ItemCompleted(ItemEnvelope<ItemSnapshot>),
34 #[serde(rename = "item.failed")]
35 ItemFailed(ItemEnvelope<ItemFailure>),
36 #[serde(rename = "error")]
37 Error(EventError),
38}
39
40impl ThreadEvent {
41 pub fn thread_id(&self) -> Option<&str> {
42 match self {
43 ThreadEvent::ThreadStarted(event) => Some(event.thread_id.as_str()),
44 ThreadEvent::TurnStarted(event) => Some(event.thread_id.as_str()),
45 ThreadEvent::TurnCompleted(event) => Some(event.thread_id.as_str()),
46 ThreadEvent::TurnFailed(event) => Some(event.thread_id.as_str()),
47 ThreadEvent::ItemStarted(event) => Some(event.thread_id.as_str()),
48 ThreadEvent::ItemDelta(event) => Some(event.thread_id.as_str()),
49 ThreadEvent::ItemCompleted(event) => Some(event.thread_id.as_str()),
50 ThreadEvent::ItemFailed(event) => Some(event.thread_id.as_str()),
51 ThreadEvent::Error(_) => None,
52 }
53 }
54}
55
56#[derive(Clone, Debug, Deserialize, Serialize)]
58pub struct ThreadStarted {
59 pub thread_id: String,
60 #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
61 pub extra: BTreeMap<String, Value>,
62}
63
64#[derive(Clone, Debug, Deserialize, Serialize)]
66pub struct TurnStarted {
67 pub thread_id: String,
68 pub turn_id: String,
69 #[serde(default, skip_serializing_if = "Option::is_none")]
71 pub input_text: Option<String>,
72 #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
73 pub extra: BTreeMap<String, Value>,
74}
75
76#[derive(Clone, Debug, Deserialize, Serialize)]
78pub struct TurnCompleted {
79 pub thread_id: String,
80 pub turn_id: String,
81 #[serde(default, skip_serializing_if = "Option::is_none")]
83 pub last_item_id: Option<String>,
84 #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
85 pub extra: BTreeMap<String, Value>,
86}
87
88#[derive(Clone, Debug, Deserialize, Serialize)]
90pub struct TurnFailed {
91 pub thread_id: String,
92 pub turn_id: String,
93 pub error: EventError,
94 #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
95 pub extra: BTreeMap<String, Value>,
96}
97
98#[derive(Clone, Debug, Deserialize, Serialize)]
100pub struct ItemEnvelope<T> {
101 pub thread_id: String,
102 pub turn_id: String,
103 #[serde(flatten)]
104 pub item: T,
105}
106
107#[derive(Clone, Debug, Deserialize, Serialize)]
109pub struct ItemSnapshot {
110 #[serde(rename = "item_id", alias = "id")]
111 pub item_id: String,
112 #[serde(default, skip_serializing_if = "Option::is_none")]
113 pub index: Option<u32>,
114 #[serde(default)]
115 pub status: ItemStatus,
116 #[serde(flatten)]
117 pub payload: ItemPayload,
118 #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
119 pub extra: BTreeMap<String, Value>,
120}
121
122#[derive(Clone, Debug, Deserialize, Serialize)]
124pub struct ItemDelta {
125 pub thread_id: String,
126 pub turn_id: String,
127 #[serde(rename = "item_id", alias = "id")]
128 pub item_id: String,
129 #[serde(default, skip_serializing_if = "Option::is_none")]
130 pub index: Option<u32>,
131 #[serde(flatten)]
132 pub delta: ItemDeltaPayload,
133 #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
134 pub extra: BTreeMap<String, Value>,
135}
136
137#[derive(Clone, Debug, Deserialize, Serialize)]
139pub struct ItemFailure {
140 #[serde(rename = "item_id", alias = "id")]
141 pub item_id: String,
142 #[serde(default, skip_serializing_if = "Option::is_none")]
143 pub index: Option<u32>,
144 pub error: EventError,
145 #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
146 pub extra: BTreeMap<String, Value>,
147}
148
149#[derive(Clone, Debug, Deserialize, Serialize)]
151#[serde(tag = "item_type", content = "content", rename_all = "snake_case")]
152pub enum ItemPayload {
153 AgentMessage(TextContent),
154 Reasoning(TextContent),
155 CommandExecution(CommandExecutionState),
156 FileChange(FileChangeState),
157 McpToolCall(McpToolCallState),
158 WebSearch(WebSearchState),
159 TodoList(TodoListState),
160 Error(EventError),
161}
162
163#[derive(Clone, Debug, Deserialize, Serialize)]
165#[serde(tag = "item_type", content = "delta", rename_all = "snake_case")]
166pub enum ItemDeltaPayload {
167 AgentMessage(TextDelta),
168 Reasoning(TextDelta),
169 CommandExecution(CommandExecutionDelta),
170 FileChange(FileChangeDelta),
171 McpToolCall(McpToolCallDelta),
172 WebSearch(WebSearchDelta),
173 TodoList(TodoListDelta),
174 Error(EventError),
175}
176
177#[derive(Clone, Debug, Default, Deserialize, Serialize, Eq, PartialEq)]
179#[serde(rename_all = "snake_case")]
180pub enum ItemStatus {
181 #[default]
182 InProgress,
183 Completed,
184 Failed,
185 #[serde(other)]
186 Unknown,
187}
188
189#[derive(Clone, Debug, Deserialize, Serialize)]
191pub struct TextContent {
192 pub text: String,
193 #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
194 pub extra: BTreeMap<String, Value>,
195}
196
197#[derive(Clone, Debug, Deserialize, Serialize)]
199pub struct TextDelta {
200 #[serde(rename = "text_delta", alias = "text")]
201 pub text_delta: String,
202 #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
203 pub extra: BTreeMap<String, Value>,
204}
205
206#[derive(Clone, Debug, Deserialize, Serialize)]
208pub struct CommandExecutionState {
209 pub command: String,
210 #[serde(default, skip_serializing_if = "Option::is_none")]
211 pub exit_code: Option<i32>,
212 #[serde(
213 default,
214 skip_serializing_if = "String::is_empty",
215 alias = "aggregated_output",
216 alias = "output"
217 )]
218 pub stdout: String,
219 #[serde(
220 default,
221 skip_serializing_if = "String::is_empty",
222 alias = "error_output",
223 alias = "err"
224 )]
225 pub stderr: String,
226 #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
227 pub extra: BTreeMap<String, Value>,
228}
229
230#[derive(Clone, Debug, Deserialize, Serialize)]
232pub struct CommandExecutionDelta {
233 #[serde(
234 default,
235 skip_serializing_if = "String::is_empty",
236 alias = "aggregated_output",
237 alias = "output"
238 )]
239 pub stdout: String,
240 #[serde(
241 default,
242 skip_serializing_if = "String::is_empty",
243 alias = "error_output",
244 alias = "err"
245 )]
246 pub stderr: String,
247 #[serde(default, skip_serializing_if = "Option::is_none")]
248 pub exit_code: Option<i32>,
249 #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
250 pub extra: BTreeMap<String, Value>,
251}
252
253#[derive(Clone, Debug, Deserialize, Serialize)]
255pub struct FileChangeState {
256 #[serde(alias = "file_path")]
257 pub path: PathBuf,
258 #[serde(default, skip_serializing_if = "Option::is_none")]
259 pub change: Option<FileChangeKind>,
260 #[serde(default, skip_serializing_if = "Option::is_none", alias = "patch")]
261 pub diff: Option<String>,
262 #[serde(default, skip_serializing_if = "Option::is_none")]
263 pub exit_code: Option<i32>,
264 #[serde(
265 default,
266 skip_serializing_if = "String::is_empty",
267 alias = "aggregated_output",
268 alias = "output"
269 )]
270 pub stdout: String,
271 #[serde(
272 default,
273 skip_serializing_if = "String::is_empty",
274 alias = "error_output",
275 alias = "err"
276 )]
277 pub stderr: String,
278 #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
279 pub extra: BTreeMap<String, Value>,
280}
281
282#[derive(Clone, Debug, Deserialize, Serialize)]
284pub struct FileChangeDelta {
285 #[serde(default, skip_serializing_if = "Option::is_none", alias = "patch")]
286 pub diff: Option<String>,
287 #[serde(
288 default,
289 skip_serializing_if = "String::is_empty",
290 alias = "aggregated_output",
291 alias = "output"
292 )]
293 pub stdout: String,
294 #[serde(
295 default,
296 skip_serializing_if = "String::is_empty",
297 alias = "error_output",
298 alias = "err"
299 )]
300 pub stderr: String,
301 #[serde(default, skip_serializing_if = "Option::is_none")]
302 pub exit_code: Option<i32>,
303 #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
304 pub extra: BTreeMap<String, Value>,
305}
306
307#[derive(Clone, Copy, Debug, Deserialize, Serialize, Eq, PartialEq)]
309#[serde(rename_all = "snake_case")]
310pub enum FileChangeKind {
311 Apply,
312 Diff,
313 #[serde(other)]
314 Unknown,
315}
316
317#[derive(Clone, Debug, Deserialize, Serialize)]
319pub struct McpToolCallState {
320 #[serde(alias = "server")]
321 pub server_name: String,
322 #[serde(alias = "tool")]
323 pub tool_name: String,
324 #[serde(default, skip_serializing_if = "Option::is_none")]
325 pub arguments: Option<Value>,
326 #[serde(default, skip_serializing_if = "Option::is_none")]
327 pub result: Option<Value>,
328 #[serde(default)]
329 pub status: ToolCallStatus,
330 #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
331 pub extra: BTreeMap<String, Value>,
332}
333
334#[derive(Clone, Debug, Deserialize, Serialize)]
336pub struct McpToolCallDelta {
337 #[serde(default, skip_serializing_if = "Option::is_none")]
338 pub result: Option<Value>,
339 #[serde(default)]
340 pub status: ToolCallStatus,
341 #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
342 pub extra: BTreeMap<String, Value>,
343}
344
345#[derive(Clone, Debug, Default, Deserialize, Serialize, Eq, PartialEq)]
347#[serde(rename_all = "snake_case")]
348pub enum ToolCallStatus {
349 #[default]
350 Pending,
351 Running,
352 Completed,
353 Failed,
354 #[serde(other)]
355 Unknown,
356}
357
358#[derive(Clone, Debug, Deserialize, Serialize)]
360pub struct WebSearchState {
361 pub query: String,
362 #[serde(default, skip_serializing_if = "Option::is_none")]
363 pub results: Option<Value>,
364 #[serde(default)]
365 pub status: WebSearchStatus,
366 #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
367 pub extra: BTreeMap<String, Value>,
368}
369
370#[derive(Clone, Debug, Deserialize, Serialize)]
372pub struct WebSearchDelta {
373 #[serde(default, skip_serializing_if = "Option::is_none")]
374 pub results: Option<Value>,
375 #[serde(default)]
376 pub status: WebSearchStatus,
377 #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
378 pub extra: BTreeMap<String, Value>,
379}
380
381#[derive(Clone, Debug, Default, Deserialize, Serialize, Eq, PartialEq)]
383#[serde(rename_all = "snake_case")]
384pub enum WebSearchStatus {
385 #[default]
386 Pending,
387 Running,
388 Completed,
389 Failed,
390 #[serde(other)]
391 Unknown,
392}
393
394#[derive(Clone, Debug, Deserialize, Serialize)]
396pub struct TodoListState {
397 #[serde(default, skip_serializing_if = "Vec::is_empty")]
398 pub items: Vec<TodoItem>,
399 #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
400 pub extra: BTreeMap<String, Value>,
401}
402
403#[derive(Clone, Debug, Deserialize, Serialize)]
405pub struct TodoListDelta {
406 #[serde(default, skip_serializing_if = "Vec::is_empty")]
407 pub items: Vec<TodoItem>,
408 #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
409 pub extra: BTreeMap<String, Value>,
410}
411
412#[derive(Clone, Debug, Deserialize, Serialize)]
414pub struct TodoItem {
415 pub title: String,
416 #[serde(default)]
417 pub completed: bool,
418 #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
419 pub extra: BTreeMap<String, Value>,
420}
421
422#[derive(Clone, Debug, Deserialize, Serialize)]
424pub struct EventError {
425 pub message: String,
426 #[serde(default, skip_serializing_if = "Option::is_none")]
427 pub code: Option<String>,
428 #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
429 pub extra: BTreeMap<String, Value>,
430}