1use crate::a2a::cli::A2aCommands;
10
11pub async fn execute_a2a_command(command: A2aCommands) -> anyhow::Result<()> {
13 match command {
14 A2aCommands::Serve {
15 host,
16 port,
17 base_url,
18 enable_push,
19 } => serve_a2a_agent(host, port, base_url, enable_push).await,
20 A2aCommands::Discover { agent_url } => discover_agent(agent_url).await,
21 A2aCommands::SendTask {
22 agent_url,
23 message,
24 stream,
25 context_id,
26 } => send_task_to_agent(agent_url, message, stream, context_id).await,
27 A2aCommands::ListTasks {
28 agent_url,
29 context_id,
30 limit,
31 } => list_agent_tasks(agent_url, context_id, limit).await,
32 A2aCommands::GetTask { agent_url, task_id } => get_agent_task(agent_url, task_id).await,
33 A2aCommands::CancelTask { agent_url, task_id } => {
34 cancel_agent_task(agent_url, task_id).await
35 }
36 }
37}
38
39#[cfg(feature = "a2a-server")]
41async fn serve_a2a_agent(
42 host: String,
43 port: u16,
44 base_url: Option<String>,
45 _enable_push: bool,
46) -> anyhow::Result<()> {
47 use crate::a2a::server::{A2aServerState, create_router};
48 use crate::a2a::{AgentCard, TaskManager};
49 use std::net::SocketAddr;
50
51 println!("Starting VT Code A2A Agent Server...");
52 println!("Feature: a2a-server enabled ✓");
53
54 let base_url = base_url.unwrap_or_else(|| format!("http://{}:{}", host, port));
55 let agent_card = AgentCard::vtcode_default(&base_url);
56 let task_manager = TaskManager::new();
57
58 let server_state = A2aServerState::new(task_manager, agent_card);
59 let router = create_router(server_state);
60
61 let addr = format!("{}:{}", host, port).parse::<SocketAddr>()?;
62 println!("Listening on http://{}", addr);
63 println!("Agent Card: http://{}/.well-known/agent-card.json", addr);
64 println!("JSON-RPC API: http://{}/a2a", addr);
65 println!("Streaming API: http://{}/a2a/stream", addr);
66
67 let listener = tokio::net::TcpListener::bind(addr).await?;
68 axum::serve(listener, router)
69 .with_graceful_shutdown(crate::shutdown::shutdown_signal_logged("A2A"))
70 .await?;
71
72 Ok(())
73}
74
75#[cfg(not(feature = "a2a-server"))]
76async fn serve_a2a_agent(
77 _host: String,
78 _port: u16,
79 _base_url: Option<String>,
80 _enable_push: bool,
81) -> anyhow::Result<()> {
82 anyhow::bail!(
83 "A2A server is not enabled. Build with '--features a2a-server' to enable this feature.\n\
84 Example: cargo build --release --features a2a-server"
85 )
86}
87
88async fn discover_agent(agent_url: String) -> anyhow::Result<()> {
90 use crate::a2a::A2aClient;
91
92 println!("Discovering A2A agent at: {}", agent_url);
93
94 let client = A2aClient::new(&agent_url)?;
95 let agent_card = client.agent_card().await?;
96
97 println!("\n═══════════════════════════════════════════════════════════════");
98 println!("A2A Agent Discovery");
99 println!("═══════════════════════════════════════════════════════════════\n");
100
101 println!("Name: {}", agent_card.name);
102 println!("Description: {}", agent_card.description);
103 println!("Version: {}", agent_card.version);
104 println!("Protocol Version: {}", agent_card.protocol_version);
105 println!("URL: {}", agent_card.url);
106
107 if let Some(provider) = &agent_card.provider {
108 println!("\nProvider:");
109 println!(" Organization: {}", provider.organization);
110 if let Some(url) = &provider.url {
111 println!(" URL: {}", url);
112 }
113 }
114
115 if let Some(capabilities) = &agent_card.capabilities {
116 println!("\nCapabilities:");
117 println!(" Streaming: {}", capabilities.streaming);
118 println!(" Push Notifications: {}", capabilities.push_notifications);
119 println!(
120 " State Transition History: {}",
121 capabilities.state_transition_history
122 );
123 if !capabilities.extensions.is_empty() {
124 println!(" Extensions: {:?}", capabilities.extensions);
125 }
126 }
127
128 if !agent_card.skills.is_empty() {
129 println!("\nSkills:");
130 for skill in &agent_card.skills {
131 println!(" - {}", skill.name);
132 if let Some(desc) = &skill.description {
133 println!(" Description: {}", desc);
134 }
135 if !skill.tags.is_empty() {
136 println!(" Tags: {:?}", skill.tags);
137 }
138 }
139 }
140
141 println!("\nInput Modes: {:?}", agent_card.default_input_modes);
142 println!("Output Modes: {:?}", agent_card.default_output_modes);
143
144 Ok(())
145}
146
147async fn send_task_to_agent(
149 agent_url: String,
150 message: String,
151 stream: bool,
152 context_id: Option<String>,
153) -> anyhow::Result<()> {
154 use crate::a2a::{A2aClient, Message, rpc::MessageSendParams};
155 use futures::StreamExt;
156
157 println!("Connecting to A2A agent: {}", agent_url);
158
159 let client = A2aClient::new(&agent_url)?;
160 let msg = Message::user_text(message);
161
162 let mut params = MessageSendParams::new(msg);
163 if let Some(ctx_id) = context_id {
164 params = params.with_context_id(ctx_id);
165 }
166
167 if stream {
168 println!("Streaming task execution...\n");
169 let stream = client.stream_message(params).await?;
170 futures::pin_mut!(stream);
171
172 while let Some(event) = stream.next().await {
173 match event {
174 Ok(event) => {
175 match event {
177 crate::a2a::rpc::StreamingEvent::Message { message, .. } => {
178 if let Some(text) = message.parts.iter().find_map(|p| p.as_text()) {
179 println!("Agent: {}", text);
180 }
181 }
182 crate::a2a::rpc::StreamingEvent::TaskStatus { status, .. } => {
183 println!("Status: {:?}", status.state);
184 if let Some(msg) = status.message
185 && let Some(text) = msg.parts.iter().find_map(|p| p.as_text())
186 {
187 println!(" Message: {}", text);
188 }
189 }
190 crate::a2a::rpc::StreamingEvent::TaskArtifact { artifact, .. } => {
191 println!("Artifact: {}", artifact.id);
192 }
193 }
194 }
195 Err(e) => {
196 eprintln!("Stream error: {}", e);
197 break;
198 }
199 }
200 }
201 } else {
202 println!("Sending task...\n");
203 let task = client.send_message(params).await?;
204 println!("Task created: {}", task.id);
205 println!("Status: {:?}\n", task.status.state);
206
207 if let Some(msg) = &task.status.message
208 && let Some(text) = msg.parts.iter().find_map(|p| p.as_text())
209 {
210 println!("Response: {}", text);
211 }
212
213 if !task.artifacts.is_empty() {
214 println!("\nArtifacts:");
215 for artifact in &task.artifacts {
216 println!(" - {} ({} parts)", artifact.id, artifact.parts.len());
217 }
218 }
219 }
220
221 Ok(())
222}
223
224async fn list_agent_tasks(
226 agent_url: String,
227 context_id: Option<String>,
228 limit: u32,
229) -> anyhow::Result<()> {
230 use crate::a2a::{A2aClient, rpc::ListTasksParams};
231 use serde_json::Value;
232
233 println!("Fetching tasks from: {}", agent_url);
234
235 let client = A2aClient::new(&agent_url)?;
236 let mut params = ListTasksParams::default();
237
238 if let Some(ctx_id) = context_id {
239 params.context_id = Some(ctx_id);
240 }
241 params.page_size = Some(limit);
242
243 let result_value: Value = client.list_tasks(Some(params)).await?;
244
245 let tasks_array = result_value
247 .get("tasks")
248 .and_then(|v| v.as_array())
249 .ok_or_else(|| anyhow::anyhow!("Invalid response format"))?;
250
251 let total_size = result_value
252 .get("totalSize")
253 .and_then(|v| v.as_u64())
254 .unwrap_or(tasks_array.len() as u64);
255
256 println!(
257 "\nTasks ({} total, showing {}):",
258 total_size,
259 tasks_array.len()
260 );
261 println!("═══════════════════════════════════════════════════════════════\n");
262
263 for task_value in tasks_array {
264 if let Some(task_id) = task_value.get("id").and_then(|v| v.as_str()) {
265 println!("Task: {}", task_id);
266 }
267 if let Some(status) = task_value.get("status")
268 && let Some(state) = status.get("state").and_then(|v| v.as_str())
269 {
270 println!(" Status: {}", state);
271 }
272 if let Some(ctx_id) = task_value.get("contextId").and_then(|v| v.as_str()) {
273 println!(" Context: {}", ctx_id);
274 }
275 if let Some(artifacts) = task_value.get("artifacts").and_then(|v| v.as_array()) {
276 println!(" Artifacts: {}", artifacts.len());
277 }
278 println!();
279 }
280
281 Ok(())
282}
283
284async fn get_agent_task(agent_url: String, task_id: String) -> anyhow::Result<()> {
286 use crate::a2a::A2aClient;
287
288 println!("Fetching task {} from: {}", task_id, agent_url);
289
290 let client = A2aClient::new(&agent_url)?;
291 let task = client.get_task(task_id.clone()).await?;
292
293 println!("\n═══════════════════════════════════════════════════════════════");
294 println!("Task: {}", task.id);
295 println!("═══════════════════════════════════════════════════════════════\n");
296
297 println!("Status: {:?}", task.status.state);
298 if let Some(ctx_id) = &task.context_id {
299 println!("Context: {}", ctx_id);
300 }
301
302 if let Some(msg) = &task.status.message {
303 println!("\nLatest Message:");
304 println!(" Role: {:?}", msg.role);
305 for part in &msg.parts {
306 match part {
307 crate::a2a::types::Part::Text { text } => println!(" Text: {}", text),
308 crate::a2a::types::Part::File { file } => println!(" File: {:?}", file),
309 crate::a2a::types::Part::Data { data } => println!(" Data: {}", data),
310 }
311 }
312 }
313
314 if !task.artifacts.is_empty() {
315 println!("\nArtifacts:");
316 for artifact in &task.artifacts {
317 println!(" - {}:", artifact.id);
318 for part in &artifact.parts {
319 match part {
320 crate::a2a::types::Part::Text { text } => {
321 let preview =
322 vtcode_commons::formatting::truncate_byte_budget(text, 60, "...");
323 println!(" Text: {}", preview);
324 }
325 crate::a2a::types::Part::File { file } => println!(" File: {:?}", file),
326 crate::a2a::types::Part::Data { data } => {
327 let s = data.to_string();
328 let preview =
329 vtcode_commons::formatting::truncate_byte_budget(&s, 60, "...");
330 println!(" Data: {}", preview);
331 }
332 }
333 }
334 }
335 }
336
337 if !task.history.is_empty() {
338 println!("\nHistory ({} messages):", task.history.len());
339 for (i, msg) in task.history.iter().enumerate() {
340 println!(" {}. {:?}: {} parts", i + 1, msg.role, msg.parts.len());
341 }
342 }
343
344 Ok(())
345}
346
347async fn cancel_agent_task(agent_url: String, task_id: String) -> anyhow::Result<()> {
349 use crate::a2a::A2aClient;
350
351 println!("Canceling task {} at: {}", task_id, agent_url);
352
353 let client = A2aClient::new(&agent_url)?;
354 client.cancel_task(task_id).await?;
355
356 println!("Task cancellation requested successfully.");
357
358 Ok(())
359}
360
361#[cfg(test)]
362mod tests {
363 #[tokio::test]
366 async fn test_discover_agent_display() {
367 }
369}