1use serde::{Deserialize, Serialize};
3use serde_json::Value;
4
5use super::{Diagnostic, ErrorItem, HarnessEventItem, ItemContext};
6
7#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
8#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
9pub struct ThreadItem {
10 #[serde(default, skip_serializing_if = "Option::is_none")]
11 pub context: Option<Box<ItemContext>>,
12 pub id: String,
14 #[serde(flatten)]
16 pub details: ThreadItemDetails,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
20#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
21#[serde(tag = "type", rename_all = "snake_case")]
22pub enum ThreadItemDetails {
23 AgentMessage(AgentMessageItem),
25 Plan(PlanItem),
27 Reasoning(Box<ReasoningItem>),
29 Decision(Box<DecisionItem>),
31 CommandExecution(Box<CommandExecutionItem>),
33 ToolInvocation(Box<ToolInvocationItem>),
35 ToolOutput(Box<ToolOutputItem>),
37 FileChange(Box<FileChangeItem>),
39 McpToolCall(Box<McpToolCallItem>),
41 WebSearch(Box<WebSearchItem>),
43 Harness(Box<HarnessEventItem>),
45 Error(ErrorItem),
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
50#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
51pub struct AgentMessageItem {
52 pub text: String,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
58#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
59pub struct DecisionItem {
60 pub summary: String,
61 pub rationale: String,
62 #[serde(default)]
63 pub alternatives: Vec<String>,
64 #[serde(default)]
65 pub evidence_ids: Vec<String>,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
69#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
70pub struct PlanItem {
71 pub text: String,
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
76#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
77pub struct ReasoningItem {
78 #[serde(default, skip_serializing_if = "Option::is_none")]
80 pub diagnostic: Option<Box<Diagnostic>>,
81 pub text: String,
83 #[serde(skip_serializing_if = "Option::is_none")]
86 pub stage: Option<String>,
87}
88
89#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
90#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
91#[serde(rename_all = "snake_case")]
92pub enum CommandExecutionStatus {
93 #[default]
95 Completed,
96 Failed,
98 InProgress,
100}
101
102#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
103#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
104pub struct CommandExecutionItem {
105 pub command: String,
107 #[serde(skip_serializing_if = "Option::is_none")]
109 pub arguments: Option<Value>,
110 #[serde(default)]
112 pub aggregated_output: String,
113 #[serde(skip_serializing_if = "Option::is_none")]
115 pub exit_code: Option<i32>,
116 pub status: CommandExecutionStatus,
118}
119
120#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
121#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
122#[serde(rename_all = "snake_case")]
123pub enum ToolCallStatus {
124 #[default]
126 Completed,
127 Failed,
129 InProgress,
131}
132
133#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
141#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
142#[serde(rename_all = "snake_case")]
143pub enum ToolOutcome {
144 #[default]
146 Success,
147 Error,
149 PermissionRejected,
151 PermissionCancelled,
153 Followup,
155 HookDenied,
157 InvalidTool,
159 Cancelled,
161}
162
163impl ToolOutcome {
164 #[must_use]
165 pub const fn is_terminal(self) -> bool {
166 !matches!(self, Self::Followup)
167 }
168}
169
170#[must_use]
177#[allow(
178 clippy::unreachable,
179 reason = "Intentional compatibility, platform, or test-only suppression."
180)]
181pub fn tool_outcome_from_status(status: &ToolCallStatus) -> ToolOutcome {
182 match status {
183 ToolCallStatus::Completed => ToolOutcome::Success,
184 ToolCallStatus::Failed => ToolOutcome::Error,
185 ToolCallStatus::InProgress => unreachable!("InProgress status passed to completion event"),
186 }
187}
188
189#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
190#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
191pub struct ToolInvocationItem {
192 pub tool_name: String,
194 #[serde(skip_serializing_if = "Option::is_none")]
196 pub arguments: Option<Value>,
197 #[serde(skip_serializing_if = "Option::is_none")]
199 pub tool_call_id: Option<String>,
200 pub status: ToolCallStatus,
202 #[serde(skip_serializing_if = "Option::is_none")]
204 pub outcome: Option<ToolOutcome>,
205}
206
207#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
208#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
209pub struct ToolOutputItem {
210 pub call_id: String,
212 #[serde(skip_serializing_if = "Option::is_none")]
214 pub tool_call_id: Option<String>,
215 #[serde(skip_serializing_if = "Option::is_none")]
217 pub spool_path: Option<String>,
218 #[serde(default)]
220 pub output: String,
221 #[serde(default, skip_serializing_if = "Option::is_none")]
224 pub is_delta: Option<bool>,
225 #[serde(skip_serializing_if = "Option::is_none")]
227 pub exit_code: Option<i32>,
228 pub status: ToolCallStatus,
230 #[serde(default, skip_serializing_if = "Vec::is_empty")]
234 pub app_previews: Vec<AppPreviewItem>,
235}
236
237#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
242#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
243pub struct AppPreviewItem {
244 pub uri: String,
246 #[serde(default, skip_serializing_if = "Option::is_none")]
248 pub mime_type: Option<String>,
249 #[serde(default)]
251 pub bytes: u64,
252 #[serde(default)]
254 pub blocks: usize,
255 #[serde(default, skip_serializing_if = "Option::is_none")]
257 pub ui: Option<AppUiPosture>,
258}
259
260#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
262#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
263pub struct AppUiPosture {
264 #[serde(default, skip_serializing_if = "Vec::is_empty")]
266 pub connect_domains: Vec<String>,
267 #[serde(default, skip_serializing_if = "Vec::is_empty")]
269 pub resource_domains: Vec<String>,
270 #[serde(default, skip_serializing_if = "Vec::is_empty")]
272 pub frame_domains: Vec<String>,
273 #[serde(default, skip_serializing_if = "Vec::is_empty")]
275 pub base_uri_domains: Vec<String>,
276 #[serde(default, skip_serializing_if = "Vec::is_empty")]
278 pub permissions: Vec<String>,
279 #[serde(default, skip_serializing_if = "Option::is_none")]
281 pub domain: Option<String>,
282}
283
284#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
285#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
286pub struct FileChangeItem {
287 #[serde(default, skip_serializing_if = "Option::is_none")]
289 pub diff_incomplete: Option<bool>,
290 pub changes: Vec<FileUpdateChange>,
292 pub status: PatchApplyStatus,
294 #[serde(default, skip_serializing_if = "Option::is_none")]
299 pub unified_diff: Option<String>,
300 #[serde(default, skip_serializing_if = "Option::is_none")]
302 pub additions: Option<u64>,
303 #[serde(default, skip_serializing_if = "Option::is_none")]
305 pub deletions: Option<u64>,
306}
307
308#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
309#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
310pub struct FileUpdateChange {
311 pub path: String,
313 pub kind: PatchChangeKind,
315 #[serde(default, skip_serializing_if = "Option::is_none")]
319 pub terminators_normalized: Option<bool>,
320}
321
322#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
323#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
324#[serde(rename_all = "snake_case")]
325pub enum PatchApplyStatus {
326 Completed,
328 Failed,
330}
331
332#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
333#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
334#[serde(rename_all = "snake_case")]
335pub enum PatchChangeKind {
336 Add,
338 Delete,
340 Update,
342}
343
344#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
345#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
346pub struct McpToolCallItem {
347 pub tool_name: String,
349 #[serde(skip_serializing_if = "Option::is_none")]
351 pub arguments: Option<Value>,
352 #[serde(skip_serializing_if = "Option::is_none")]
354 pub result: Option<String>,
355 #[serde(skip_serializing_if = "Option::is_none")]
357 pub status: Option<McpToolCallStatus>,
358}
359
360#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
361#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
362#[serde(rename_all = "snake_case")]
363pub enum McpToolCallStatus {
364 Started,
366 Completed,
368 Failed,
370}
371
372#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
373#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
374pub struct WebSearchItem {
375 pub query: String,
377 #[serde(skip_serializing_if = "Option::is_none")]
379 pub provider: Option<String>,
380 #[serde(skip_serializing_if = "Option::is_none")]
382 pub results: Option<Vec<String>>,
383}