use serde::{Deserialize, Serialize};
use serde_json::Value;
use super::{Diagnostic, ErrorItem, HarnessEventItem, ItemContext};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct ThreadItem {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub context: Option<Box<ItemContext>>,
pub id: String,
#[serde(flatten)]
pub details: ThreadItemDetails,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ThreadItemDetails {
AgentMessage(AgentMessageItem),
Plan(PlanItem),
Reasoning(Box<ReasoningItem>),
Decision(Box<DecisionItem>),
CommandExecution(Box<CommandExecutionItem>),
ToolInvocation(Box<ToolInvocationItem>),
ToolOutput(Box<ToolOutputItem>),
FileChange(Box<FileChangeItem>),
McpToolCall(Box<McpToolCallItem>),
WebSearch(Box<WebSearchItem>),
Harness(Box<HarnessEventItem>),
Error(ErrorItem),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct AgentMessageItem {
pub text: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct DecisionItem {
pub summary: String,
pub rationale: String,
#[serde(default)]
pub alternatives: Vec<String>,
#[serde(default)]
pub evidence_ids: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct PlanItem {
pub text: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct ReasoningItem {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub diagnostic: Option<Box<Diagnostic>>,
pub text: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub stage: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum CommandExecutionStatus {
#[default]
Completed,
Failed,
InProgress,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct CommandExecutionItem {
pub command: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub arguments: Option<Value>,
#[serde(default)]
pub aggregated_output: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub exit_code: Option<i32>,
pub status: CommandExecutionStatus,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum ToolCallStatus {
#[default]
Completed,
Failed,
InProgress,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum ToolOutcome {
#[default]
Success,
Error,
PermissionRejected,
PermissionCancelled,
Followup,
HookDenied,
InvalidTool,
Cancelled,
}
impl ToolOutcome {
#[must_use]
pub const fn is_terminal(self) -> bool {
!matches!(self, Self::Followup)
}
}
#[must_use]
#[allow(
clippy::unreachable,
reason = "Intentional compatibility, platform, or test-only suppression."
)]
pub fn tool_outcome_from_status(status: &ToolCallStatus) -> ToolOutcome {
match status {
ToolCallStatus::Completed => ToolOutcome::Success,
ToolCallStatus::Failed => ToolOutcome::Error,
ToolCallStatus::InProgress => unreachable!("InProgress status passed to completion event"),
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct ToolInvocationItem {
pub tool_name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub arguments: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_call_id: Option<String>,
pub status: ToolCallStatus,
#[serde(skip_serializing_if = "Option::is_none")]
pub outcome: Option<ToolOutcome>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct ToolOutputItem {
pub call_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_call_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub spool_path: Option<String>,
#[serde(default)]
pub output: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub exit_code: Option<i32>,
pub status: ToolCallStatus,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub app_previews: Vec<AppPreviewItem>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct AppPreviewItem {
pub uri: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>,
#[serde(default)]
pub bytes: u64,
#[serde(default)]
pub blocks: usize,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ui: Option<AppUiPosture>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct AppUiPosture {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub connect_domains: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub resource_domains: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub frame_domains: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub base_uri_domains: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub permissions: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub domain: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct FileChangeItem {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub diff_incomplete: Option<bool>,
pub changes: Vec<FileUpdateChange>,
pub status: PatchApplyStatus,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub unified_diff: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub additions: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub deletions: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct FileUpdateChange {
pub path: String,
pub kind: PatchChangeKind,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum PatchApplyStatus {
Completed,
Failed,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum PatchChangeKind {
Add,
Delete,
Update,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct McpToolCallItem {
pub tool_name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub arguments: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<McpToolCallStatus>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum McpToolCallStatus {
Started,
Completed,
Failed,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema-export", derive(schemars::JsonSchema))]
pub struct WebSearchItem {
pub query: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub provider: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub results: Option<Vec<String>>,
}