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