use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(default)]
pub struct StatusSurfaceState {
pub kind: String,
pub header: String,
pub details: Option<String>,
pub inline_message: Option<String>,
pub interrupt_visible: bool,
pub revision: i64,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(default)]
pub struct RenderPlanStep {
pub step: String,
pub status: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(default)]
pub struct ExecCommandLine {
pub label: String,
pub text: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(default)]
pub struct FileChangeLine {
pub path: String,
pub summary: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(default)]
pub struct ThreadRenderSnapshot {
pub runtime_id: String,
pub thread_id: String,
pub revision: i64,
pub status_surface: Option<StatusSurfaceState>,
#[serde(default)]
pub nodes: Vec<ThreadRenderNode>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(default)]
pub struct ThreadRenderPatch {
pub runtime_id: String,
pub thread_id: String,
pub snapshot: ThreadRenderSnapshot,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ThreadRenderNode {
UserMessage {
id: String,
turn_id: Option<String>,
item_id: Option<String>,
text: String,
},
AssistantMarkdown {
id: String,
turn_id: Option<String>,
item_id: Option<String>,
text: String,
phase: Option<String>,
streaming: bool,
},
FinalSeparator {
id: String,
turn_id: Option<String>,
},
ReasoningSummary {
id: String,
turn_id: Option<String>,
item_id: Option<String>,
title: String,
text: String,
},
PlanUpdate {
id: String,
turn_id: Option<String>,
item_id: Option<String>,
title: String,
explanation: Option<String>,
#[serde(default)]
steps: Vec<RenderPlanStep>,
streaming: bool,
},
ProposedPlan {
id: String,
turn_id: Option<String>,
item_id: Option<String>,
title: String,
text: String,
},
ExecGroup {
id: String,
turn_id: Option<String>,
item_id: Option<String>,
title: String,
state: String,
#[serde(default)]
commands: Vec<ExecCommandLine>,
output_text: Option<String>,
exit_code: Option<i64>,
},
FileChange {
id: String,
turn_id: Option<String>,
item_id: Option<String>,
title: String,
state: String,
#[serde(default)]
changes: Vec<FileChangeLine>,
},
McpToolCall {
id: String,
turn_id: Option<String>,
item_id: Option<String>,
title: String,
state: String,
subtitle: String,
detail: Option<String>,
},
DynamicToolCall {
id: String,
turn_id: Option<String>,
item_id: Option<String>,
title: String,
state: String,
subtitle: String,
detail: Option<String>,
},
WebSearch {
id: String,
turn_id: Option<String>,
item_id: Option<String>,
title: String,
state: String,
detail: Option<String>,
},
CollabEvent {
id: String,
turn_id: Option<String>,
item_id: Option<String>,
title: String,
#[serde(default)]
detail_lines: Vec<String>,
},
InfoNotice {
id: String,
turn_id: Option<String>,
item_id: Option<String>,
title: String,
detail: Option<String>,
},
ViewImage {
id: String,
turn_id: Option<String>,
item_id: Option<String>,
title: String,
path: String,
},
ImageGeneration {
id: String,
turn_id: Option<String>,
item_id: Option<String>,
title: String,
state: String,
prompt: Option<String>,
result: String,
saved_path: Option<String>,
},
}
impl Default for ThreadRenderNode {
fn default() -> Self {
Self::InfoNotice {
id: String::new(),
turn_id: None,
item_id: None,
title: String::new(),
detail: None,
}
}
}