1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Serialize, Deserialize)]
5pub enum AgentAction {
6 ReadFile { paths: Vec<String> },
8 WriteFile { path: String, content: String },
10 EditFile {
12 path: String,
13 old_string: String,
14 new_string: String,
15 },
16 DeleteFile { path: String },
18 CreateDirectory { path: String },
20 ExecuteCommand {
22 command: String,
23 working_dir: Option<String>,
24 timeout: Option<u64>,
25 },
26 WebSearch { queries: Vec<(String, usize)> },
28 WebFetch { url: String },
30 SpawnAgent { prompt: String, description: String },
32 Screenshot {
34 mode: String, monitor: Option<String>, region: Option<String>, window: Option<String>, },
39 Click {
41 x: i32,
42 y: i32,
43 button: String,
44 #[serde(default)]
50 screenshot_id: Option<u64>,
51 },
52 TypeText { text: String },
54 PressKey { key: String },
56 Scroll { direction: String, amount: i32 },
58 MouseMove {
60 x: i32,
61 y: i32,
62 #[serde(default)]
64 screenshot_id: Option<u64>,
65 },
66 ListWindows,
68 McpToolCall {
70 server_name: String,
71 tool_name: String,
72 arguments: serde_json::Value,
73 },
74 ParseError { message: String },
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize)]
80#[must_use]
81pub enum ActionResult {
82 Success {
83 output: String,
84 #[serde(default)]
85 images: Option<Vec<String>>,
86 },
87 Error {
88 error: String,
89 },
90}
91
92impl AgentAction {
93 pub fn display_info(&self) -> (&str, String) {
95 match self {
96 AgentAction::ReadFile { paths } => {
97 if paths.len() == 1 {
98 ("Read", paths[0].clone())
99 } else {
100 ("Read", format!("{} files", paths.len()))
101 }
102 },
103 AgentAction::WriteFile { path, .. } => ("Write", path.clone()),
104 AgentAction::EditFile { path, .. } => ("Edit", path.clone()),
105 AgentAction::DeleteFile { path } => ("Delete", path.clone()),
106 AgentAction::CreateDirectory { path } => ("Bash", format!("mkdir -p {}", path)),
107 AgentAction::ExecuteCommand { command, .. } => ("Bash", command.clone()),
108 AgentAction::WebSearch { queries } => {
109 if queries.len() == 1 {
110 ("Web Search", queries[0].0.clone())
111 } else {
112 ("Web Search", format!("{} queries", queries.len()))
113 }
114 },
115 AgentAction::WebFetch { url } => ("Web Fetch", url.clone()),
116 AgentAction::SpawnAgent { description, .. } => ("Agent", description.clone()),
117 AgentAction::Screenshot { mode, window, .. } => {
118 let target = match mode.as_str() {
119 "focused" => "focused window".to_string(),
120 "monitor" => "monitor".to_string(),
121 "region" => "region".to_string(),
122 "window" => {
123 format!("window \"{}\"", window.as_deref().unwrap_or("?"))
124 },
125 _ => "screen capture".to_string(),
126 };
127 ("Screenshot", target)
128 },
129 AgentAction::Click { x, y, button, .. } => {
130 ("Click", format!("({}, {}) {}", x, y, button))
131 },
132 AgentAction::TypeText { text } => ("Type", text.chars().take(30).collect()),
133 AgentAction::PressKey { key } => ("Key", key.clone()),
134 AgentAction::Scroll { direction, amount } => {
135 ("Scroll", format!("{} {}", direction, amount))
136 },
137 AgentAction::MouseMove { x, y, .. } => ("Move", format!("({}, {})", x, y)),
138 AgentAction::ListWindows => ("ListWindows", "visible windows".to_string()),
139 AgentAction::McpToolCall {
140 server_name,
141 tool_name,
142 ..
143 } => ("MCP", format!("{}:{}", server_name, tool_name)),
144 AgentAction::ParseError { message } => ("Error", message.clone()),
145 }
146 }
147}
148
149#[derive(Debug, Clone, Serialize, Deserialize)]
151pub struct ActionDisplay {
152 pub action_type: String,
154 pub target: String,
156 pub result: ActionResult,
158 #[serde(default)]
160 pub details: ActionDetails,
161 pub duration_seconds: Option<f64>,
163}
164
165#[derive(Debug, Clone, Default, Serialize, Deserialize)]
167pub enum ActionDetails {
168 #[default]
170 Simple,
171 Preview {
173 text: String,
174 line_count: Option<usize>,
175 },
176 FileContent { line_count: usize, content: String },
178 Diff { summary: String, diff: String },
180 Agent { summary: String, tool_uses: usize },
182}