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(skip_serializing_if = "Option::is_none")]
223 pub exit_code: Option<i32>,
224 pub status: ToolCallStatus,
226 #[serde(default, skip_serializing_if = "Vec::is_empty")]
230 pub app_previews: Vec<AppPreviewItem>,
231}
232
233#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
238#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
239pub struct AppPreviewItem {
240 pub uri: String,
242 #[serde(default, skip_serializing_if = "Option::is_none")]
244 pub mime_type: Option<String>,
245 #[serde(default)]
247 pub bytes: u64,
248 #[serde(default)]
250 pub blocks: usize,
251 #[serde(default, skip_serializing_if = "Option::is_none")]
253 pub ui: Option<AppUiPosture>,
254}
255
256#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
258#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
259pub struct AppUiPosture {
260 #[serde(default, skip_serializing_if = "Vec::is_empty")]
262 pub connect_domains: Vec<String>,
263 #[serde(default, skip_serializing_if = "Vec::is_empty")]
265 pub resource_domains: Vec<String>,
266 #[serde(default, skip_serializing_if = "Vec::is_empty")]
268 pub frame_domains: Vec<String>,
269 #[serde(default, skip_serializing_if = "Vec::is_empty")]
271 pub base_uri_domains: Vec<String>,
272 #[serde(default, skip_serializing_if = "Vec::is_empty")]
274 pub permissions: Vec<String>,
275 #[serde(default, skip_serializing_if = "Option::is_none")]
277 pub domain: Option<String>,
278}
279
280#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
281#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
282pub struct FileChangeItem {
283 #[serde(default, skip_serializing_if = "Option::is_none")]
285 pub diff_incomplete: Option<bool>,
286 pub changes: Vec<FileUpdateChange>,
288 pub status: PatchApplyStatus,
290 #[serde(default, skip_serializing_if = "Option::is_none")]
295 pub unified_diff: Option<String>,
296 #[serde(default, skip_serializing_if = "Option::is_none")]
298 pub additions: Option<u64>,
299 #[serde(default, skip_serializing_if = "Option::is_none")]
301 pub deletions: Option<u64>,
302}
303
304#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
305#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
306pub struct FileUpdateChange {
307 pub path: String,
309 pub kind: PatchChangeKind,
311}
312
313#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
314#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
315#[serde(rename_all = "snake_case")]
316pub enum PatchApplyStatus {
317 Completed,
319 Failed,
321}
322
323#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
324#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
325#[serde(rename_all = "snake_case")]
326pub enum PatchChangeKind {
327 Add,
329 Delete,
331 Update,
333}
334
335#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
336#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
337pub struct McpToolCallItem {
338 pub tool_name: String,
340 #[serde(skip_serializing_if = "Option::is_none")]
342 pub arguments: Option<Value>,
343 #[serde(skip_serializing_if = "Option::is_none")]
345 pub result: Option<String>,
346 #[serde(skip_serializing_if = "Option::is_none")]
348 pub status: Option<McpToolCallStatus>,
349}
350
351#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
352#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
353#[serde(rename_all = "snake_case")]
354pub enum McpToolCallStatus {
355 Started,
357 Completed,
359 Failed,
361}
362
363#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
364#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
365pub struct WebSearchItem {
366 pub query: String,
368 #[serde(skip_serializing_if = "Option::is_none")]
370 pub provider: Option<String>,
371 #[serde(skip_serializing_if = "Option::is_none")]
373 pub results: Option<Vec<String>>,
374}