Skip to main content

vtcode_exec_events/
items.rs

1//! Timeline item payloads carried by thread item events.
2use 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    /// Stable identifier associated with the item.
13    pub id: String,
14    /// Embedded event details for the item type.
15    #[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    /// Message authored by the agent.
24    AgentMessage(AgentMessageItem),
25    /// Structured plan content authored by the agent in Planning workflow.
26    Plan(PlanItem),
27    /// Free-form reasoning text produced during a turn.
28    Reasoning(Box<ReasoningItem>),
29    /// Public rationale explicitly recorded by the agent; never private reasoning.
30    Decision(Box<DecisionItem>),
31    /// Command execution lifecycle update for an actual shell/PTY process.
32    CommandExecution(Box<CommandExecutionItem>),
33    /// Tool invocation lifecycle update.
34    ToolInvocation(Box<ToolInvocationItem>),
35    /// Tool output lifecycle update tied to a tool invocation.
36    ToolOutput(Box<ToolOutputItem>),
37    /// File change summary associated with the turn.
38    FileChange(Box<FileChangeItem>),
39    /// MCP tool invocation status.
40    McpToolCall(Box<McpToolCallItem>),
41    /// Web search event emitted by a registered search provider.
42    WebSearch(Box<WebSearchItem>),
43    /// Harness-managed continuation or verification lifecycle event.
44    Harness(Box<HarnessEventItem>),
45    /// General error captured for auditing.
46    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    /// Textual content of the agent message.
53    pub text: String,
54}
55
56/// A consequential choice and the agent's reported public rationale.
57#[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    /// Plan markdown content.
72    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    /// Operational diagnostic independent of private model reasoning.
79    #[serde(default, skip_serializing_if = "Option::is_none")]
80    pub diagnostic: Option<Box<Diagnostic>>,
81    /// Free-form reasoning content captured during planning.
82    pub text: String,
83    /// Optional stage of reasoning (e.g., "analysis", "plan", "verification",
84    /// or the bounded evidence-only "diagnosis" stage).
85    #[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    /// Command finished successfully.
94    #[default]
95    Completed,
96    /// Command failed (non-zero exit code or runtime error).
97    Failed,
98    /// Command is still running and may emit additional output.
99    InProgress,
100}
101
102#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
103#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
104pub struct CommandExecutionItem {
105    /// Tool or command identifier executed by the runner.
106    pub command: String,
107    /// Arguments passed to the tool invocation, when available.
108    #[serde(skip_serializing_if = "Option::is_none")]
109    pub arguments: Option<Value>,
110    /// Aggregated output emitted by the command.
111    #[serde(default)]
112    pub aggregated_output: String,
113    /// Exit code reported by the process, when available.
114    #[serde(skip_serializing_if = "Option::is_none")]
115    pub exit_code: Option<i32>,
116    /// Current status of the command execution.
117    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    /// Tool finished successfully.
125    #[default]
126    Completed,
127    /// Tool failed.
128    Failed,
129    /// Tool is still running and may emit additional output.
130    InProgress,
131}
132
133/// Fine-grained outcome of a tool invocation lifecycle.
134///
135/// Mirrors the outcome taxonomy used by the runtime: `status` remains the
136/// coarse lifecycle signal (`Completed` / `Failed` / `InProgress`), while
137/// `outcome` captures *why* the invocation terminated. Consumers that only
138/// need success/failure can continue to read `status`; analytics and the UI
139/// layer use `outcome` for richer classification.
140#[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    /// Tool executed and returned a result.
145    #[default]
146    Success,
147    /// Tool executed but returned an error.
148    Error,
149    /// User rejected the permission prompt.
150    PermissionRejected,
151    /// User cancelled the permission prompt (e.g. Ctrl+C / Esc).
152    PermissionCancelled,
153    /// User provided a followup message instead of approving.
154    Followup,
155    /// A user-configured hook blocked execution.
156    HookDenied,
157    /// Tool not found or arguments couldn't be parsed.
158    InvalidTool,
159    /// Tool was cancelled during execution or closed before dispatch when its turn ended.
160    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/// Map a terminal [`ToolCallStatus`] to its corresponding [`ToolOutcome`].
171///
172/// # Panics
173///
174/// Panics if `status` is [`ToolCallStatus::InProgress`], which is a non-terminal
175/// state and must never be passed to a completion-event emitter.
176#[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    /// Name of the invoked tool.
193    pub tool_name: String,
194    /// Structured arguments passed to the tool.
195    #[serde(skip_serializing_if = "Option::is_none")]
196    pub arguments: Option<Value>,
197    /// Raw model-emitted tool call identifier, when available.
198    #[serde(skip_serializing_if = "Option::is_none")]
199    pub tool_call_id: Option<String>,
200    /// Current lifecycle status of the invocation.
201    pub status: ToolCallStatus,
202    /// Fine-grained outcome of the invocation lifecycle.
203    #[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    /// Identifier of the related harness invocation item.
211    pub call_id: String,
212    /// Raw model-emitted tool call identifier, when available.
213    #[serde(skip_serializing_if = "Option::is_none")]
214    pub tool_call_id: Option<String>,
215    /// Canonical spool file path when the full output was written to disk.
216    #[serde(skip_serializing_if = "Option::is_none")]
217    pub spool_path: Option<String>,
218    /// Aggregated output emitted by the tool.
219    #[serde(default)]
220    pub output: String,
221    /// Exit code reported by the tool, when available.
222    #[serde(skip_serializing_if = "Option::is_none")]
223    pub exit_code: Option<i32>,
224    /// Current lifecycle status of the output item.
225    pub status: ToolCallStatus,
226    /// MCP Apps referenced by this output, when the tool result was enriched
227    /// with app disclosure. Surfaces render preview affordances from these;
228    /// empty for non-app results. Schema 0.21.0; omitted when empty.
229    #[serde(default, skip_serializing_if = "Vec::is_empty")]
230    pub app_previews: Vec<AppPreviewItem>,
231}
232
233/// Disclosure facts about one MCP Apps resource referenced by a tool output.
234///
235/// Mirrors the disclosure attached at execution time; surfaces must not
236/// re-derive these by parsing rendered output text.
237#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
238#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
239pub struct AppPreviewItem {
240    /// Canonical `ui://` resource URI.
241    pub uri: String,
242    /// MIME type of the first content block, when the server provides one.
243    #[serde(default, skip_serializing_if = "Option::is_none")]
244    pub mime_type: Option<String>,
245    /// Total body bytes across content blocks.
246    #[serde(default)]
247    pub bytes: u64,
248    /// Number of content blocks.
249    #[serde(default)]
250    pub blocks: usize,
251    /// Declared UI sandbox posture, when the server declares `_meta.ui`.
252    #[serde(default, skip_serializing_if = "Option::is_none")]
253    pub ui: Option<AppUiPosture>,
254}
255
256/// Declared sandbox posture of an MCP Apps UI resource (SEP-1865 `UIResourceMeta`).
257#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
258#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
259pub struct AppUiPosture {
260    /// Origins for network requests (`csp.connectDomains`).
261    #[serde(default, skip_serializing_if = "Vec::is_empty")]
262    pub connect_domains: Vec<String>,
263    /// Origins for static resources (`csp.resourceDomains`).
264    #[serde(default, skip_serializing_if = "Vec::is_empty")]
265    pub resource_domains: Vec<String>,
266    /// Origins for nested iframes (`csp.frameDomains`).
267    #[serde(default, skip_serializing_if = "Vec::is_empty")]
268    pub frame_domains: Vec<String>,
269    /// Allowed base URIs (`csp.baseUriDomains`).
270    #[serde(default, skip_serializing_if = "Vec::is_empty")]
271    pub base_uri_domains: Vec<String>,
272    /// Requested permission-policy features (`permissions`).
273    #[serde(default, skip_serializing_if = "Vec::is_empty")]
274    pub permissions: Vec<String>,
275    /// Dedicated sandbox origin for the view (`domain`).
276    #[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    /// Captured preview was truncated, suppressed, or unavailable.
284    #[serde(default, skip_serializing_if = "Option::is_none")]
285    pub diff_incomplete: Option<bool>,
286    /// List of individual file updates included in the change set.
287    pub changes: Vec<FileUpdateChange>,
288    /// Whether the patch application succeeded.
289    pub status: PatchApplyStatus,
290    /// Optional precomputed unified diff for the change set.
291    ///
292    /// Populated by the turn diff tracker so consumers can render per-change
293    /// previews without recomputation. Absent in older events.
294    #[serde(default, skip_serializing_if = "Option::is_none")]
295    pub unified_diff: Option<String>,
296    /// Optional added-line count for the change set.
297    #[serde(default, skip_serializing_if = "Option::is_none")]
298    pub additions: Option<u64>,
299    /// Optional deleted-line count for the change set.
300    #[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    /// Path of the file that was updated.
308    pub path: String,
309    /// Type of change applied to the file.
310    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    /// Patch successfully applied.
318    Completed,
319    /// Patch application failed.
320    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    /// File addition.
328    Add,
329    /// File deletion.
330    Delete,
331    /// File update in place.
332    Update,
333}
334
335#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
336#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
337pub struct McpToolCallItem {
338    /// Name of the MCP tool invoked by the agent.
339    pub tool_name: String,
340    /// Arguments passed to the tool invocation, if any.
341    #[serde(skip_serializing_if = "Option::is_none")]
342    pub arguments: Option<Value>,
343    /// Result payload returned by the tool, if captured.
344    #[serde(skip_serializing_if = "Option::is_none")]
345    pub result: Option<String>,
346    /// Lifecycle status for the tool call.
347    #[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    /// Tool invocation has started.
356    Started,
357    /// Tool invocation completed successfully.
358    Completed,
359    /// Tool invocation failed.
360    Failed,
361}
362
363#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
364#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
365pub struct WebSearchItem {
366    /// Query that triggered the search.
367    pub query: String,
368    /// Search provider identifier, when known.
369    #[serde(skip_serializing_if = "Option::is_none")]
370    pub provider: Option<String>,
371    /// Optional raw search results captured for auditing.
372    #[serde(skip_serializing_if = "Option::is_none")]
373    pub results: Option<Vec<String>>,
374}