grok-cli-acp 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/.env.example +42 -0
  2. package/.github/workflows/ci.yml +30 -0
  3. package/.github/workflows/rust.yml +22 -0
  4. package/.grok/.env.example +85 -0
  5. package/.grok/COMPLETE_FIX_SUMMARY.md +466 -0
  6. package/.grok/ENV_CONFIG_GUIDE.md +173 -0
  7. package/.grok/QUICK_REFERENCE.md +180 -0
  8. package/.grok/README.md +104 -0
  9. package/.grok/TESTING_GUIDE.md +393 -0
  10. package/CHANGELOG.md +465 -0
  11. package/CODE_REVIEW_SUMMARY.md +414 -0
  12. package/COMPLETE_FIX_SUMMARY.md +415 -0
  13. package/CONFIGURATION.md +489 -0
  14. package/CONTEXT_FILES_GUIDE.md +419 -0
  15. package/CONTRIBUTING.md +55 -0
  16. package/CURSOR_POSITION_FIX.md +206 -0
  17. package/Cargo.toml +88 -0
  18. package/ERROR_HANDLING_REPORT.md +361 -0
  19. package/FINAL_FIX_SUMMARY.md +462 -0
  20. package/FIXES.md +37 -0
  21. package/FIXES_SUMMARY.md +87 -0
  22. package/GROK_API_MIGRATION_SUMMARY.md +111 -0
  23. package/LICENSE +22 -0
  24. package/MIGRATION_TO_GROK_API.md +223 -0
  25. package/README.md +504 -0
  26. package/REVIEW_COMPLETE.md +416 -0
  27. package/REVIEW_QUICK_REFERENCE.md +173 -0
  28. package/SECURITY.md +463 -0
  29. package/SECURITY_AUDIT.md +661 -0
  30. package/SETUP.md +287 -0
  31. package/TESTING_TOOLS.md +88 -0
  32. package/TESTING_TOOL_EXECUTION.md +239 -0
  33. package/TOOL_EXECUTION_FIX.md +491 -0
  34. package/VERIFICATION_CHECKLIST.md +419 -0
  35. package/docs/API.md +74 -0
  36. package/docs/CHAT_LOGGING.md +39 -0
  37. package/docs/CURSOR_FIX_DEMO.md +306 -0
  38. package/docs/ERROR_HANDLING_GUIDE.md +547 -0
  39. package/docs/FILE_OPERATIONS.md +449 -0
  40. package/docs/INTERACTIVE.md +401 -0
  41. package/docs/PROJECT_CREATION_GUIDE.md +570 -0
  42. package/docs/QUICKSTART.md +378 -0
  43. package/docs/QUICK_REFERENCE.md +691 -0
  44. package/docs/RELEASE_NOTES_0.1.2.md +240 -0
  45. package/docs/TOOLS.md +459 -0
  46. package/docs/TOOLS_QUICK_REFERENCE.md +210 -0
  47. package/docs/ZED_INTEGRATION.md +371 -0
  48. package/docs/extensions.md +464 -0
  49. package/docs/settings.md +293 -0
  50. package/examples/extensions/logging-hook/README.md +91 -0
  51. package/examples/extensions/logging-hook/extension.json +22 -0
  52. package/package.json +30 -0
  53. package/scripts/test_acp.py +252 -0
  54. package/scripts/test_acp.sh +143 -0
  55. package/scripts/test_acp_simple.sh +72 -0
  56. package/src/acp/mod.rs +741 -0
  57. package/src/acp/protocol.rs +323 -0
  58. package/src/acp/security.rs +298 -0
  59. package/src/acp/tools.rs +697 -0
  60. package/src/bin/banner_demo.rs +216 -0
  61. package/src/bin/docgen.rs +18 -0
  62. package/src/bin/installer.rs +217 -0
  63. package/src/cli/app.rs +310 -0
  64. package/src/cli/commands/acp.rs +721 -0
  65. package/src/cli/commands/chat.rs +485 -0
  66. package/src/cli/commands/code.rs +513 -0
  67. package/src/cli/commands/config.rs +394 -0
  68. package/src/cli/commands/health.rs +442 -0
  69. package/src/cli/commands/history.rs +421 -0
  70. package/src/cli/commands/mod.rs +14 -0
  71. package/src/cli/commands/settings.rs +1384 -0
  72. package/src/cli/mod.rs +166 -0
  73. package/src/config/mod.rs +2212 -0
  74. package/src/display/ascii_art.rs +139 -0
  75. package/src/display/banner.rs +289 -0
  76. package/src/display/components/input.rs +323 -0
  77. package/src/display/components/mod.rs +2 -0
  78. package/src/display/components/settings_list.rs +306 -0
  79. package/src/display/interactive.rs +1255 -0
  80. package/src/display/mod.rs +62 -0
  81. package/src/display/terminal.rs +42 -0
  82. package/src/display/tips.rs +316 -0
  83. package/src/grok_client_ext.rs +177 -0
  84. package/src/hooks/loader.rs +407 -0
  85. package/src/hooks/mod.rs +158 -0
  86. package/src/lib.rs +174 -0
  87. package/src/main.rs +65 -0
  88. package/src/mcp/client.rs +195 -0
  89. package/src/mcp/config.rs +20 -0
  90. package/src/mcp/mod.rs +6 -0
  91. package/src/mcp/protocol.rs +67 -0
  92. package/src/utils/auth.rs +41 -0
  93. package/src/utils/chat_logger.rs +568 -0
  94. package/src/utils/context.rs +390 -0
  95. package/src/utils/mod.rs +16 -0
  96. package/src/utils/network.rs +320 -0
  97. package/src/utils/rate_limiter.rs +166 -0
  98. package/src/utils/session.rs +73 -0
  99. package/src/utils/shell_permissions.rs +389 -0
  100. package/src/utils/telemetry.rs +41 -0
@@ -0,0 +1,721 @@
1
+ //! ACP (Agent Client Protocol) command handler for Zed integration
2
+ //!
3
+ //! This module handles all Agent Client Protocol operations, including starting
4
+ //! the ACP server for Zed editor integration, testing connections, and managing
5
+ //! ACP capabilities.
6
+
7
+ use anyhow::{Result, anyhow};
8
+ use colored::*;
9
+ use serde_json::{Value, json};
10
+ use std::net::SocketAddr;
11
+ use std::sync::Arc;
12
+ use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
13
+ use tokio::net::TcpListener;
14
+ use tokio::sync::RwLock;
15
+ use tracing::{debug, error, info, warn};
16
+
17
+ use crate::acp::protocol::{
18
+ AGENT_METHOD_NAMES, AgentCapabilities, ContentBlock, ContentChunk, Implementation,
19
+ InitializeRequest, InitializeResponse, NewSessionRequest, NewSessionResponse, PromptRequest,
20
+ PromptResponse, ProtocolVersion, SessionId, SessionNotification, SessionUpdate, StopReason,
21
+ TextContent,
22
+ };
23
+ use crate::acp::{GrokAcpAgent, SessionConfig};
24
+ use crate::cli::{create_spinner, print_error, print_info, print_success, print_warning};
25
+ use crate::config::Config;
26
+ use crate::utils::chat_logger;
27
+
28
+ /// Handle ACP-related commands
29
+ pub async fn handle_acp_action(action: crate::AcpAction, config: &Config) -> Result<()> {
30
+ match action {
31
+ crate::AcpAction::Server { port, host } => start_acp_server(port, &host, config).await,
32
+ crate::AcpAction::Stdio { model } => start_acp_stdio(config, model).await,
33
+ crate::AcpAction::Test { address } => test_acp_connection(&address, config).await,
34
+ crate::AcpAction::Capabilities => show_acp_capabilities().await,
35
+ }
36
+ }
37
+
38
+ /// Start the ACP server for Zed integration
39
+ async fn start_acp_server(port: Option<u16>, host: &str, config: &Config) -> Result<()> {
40
+ if !config.acp.enabled {
41
+ print_warning(
42
+ "ACP is disabled in configuration. Enable it with 'grok config set acp.enabled true'",
43
+ );
44
+ return Ok(());
45
+ }
46
+
47
+ let bind_port = port.or(config.acp.default_port).unwrap_or(0);
48
+ let bind_addr = format!("{}:{}", host, bind_port);
49
+
50
+ print_info(&format!("Starting ACP server on {}", bind_addr));
51
+
52
+ let listener = TcpListener::bind(&bind_addr)
53
+ .await
54
+ .map_err(|e| anyhow!("Failed to bind ACP server to {}: {}", bind_addr, e))?;
55
+
56
+ let actual_addr = listener.local_addr()?;
57
+ print_success(&format!("ACP server listening on {}", actual_addr));
58
+
59
+ if config.acp.dev_mode {
60
+ print_info("Development mode enabled - additional debugging features available");
61
+ }
62
+
63
+ println!();
64
+ println!("{}", "🔗 Zed Integration Instructions:".cyan().bold());
65
+ println!("1. Open Zed editor");
66
+ println!("2. Go to Settings → Extensions → Agent Client Protocol");
67
+ println!("3. Add a new agent configuration:");
68
+ println!(" {}", " - Name: Grok AI".green());
69
+ println!(
70
+ " {}",
71
+ format!(
72
+ " - Command: grok acp server --port {}",
73
+ actual_addr.port()
74
+ )
75
+ .green()
76
+ );
77
+ println!(" {}", format!(" - Address: {}", actual_addr).green());
78
+ println!("4. Enable the agent and start coding!");
79
+ println!();
80
+ println!("{}", "Press Ctrl+C to stop the server".dimmed());
81
+
82
+ let server_stats = Arc::new(RwLock::new(ServerStats::new()));
83
+ let server_config = config.clone();
84
+
85
+ loop {
86
+ match listener.accept().await {
87
+ Ok((stream, client_addr)) => {
88
+ info!("New ACP client connected: {}", client_addr);
89
+
90
+ let stats = Arc::clone(&server_stats);
91
+ let config = server_config.clone();
92
+
93
+ tokio::spawn(async move {
94
+ if let Err(e) = handle_acp_client(stream, client_addr, stats, config).await {
95
+ error!("ACP client error ({}): {}", client_addr, e);
96
+ }
97
+ });
98
+ }
99
+ Err(e) => {
100
+ error!("Failed to accept ACP connection: {}", e);
101
+ break;
102
+ }
103
+ }
104
+ }
105
+
106
+ Ok(())
107
+ }
108
+
109
+ async fn start_acp_stdio(config: &Config, model: Option<String>) -> Result<()> {
110
+ let stdin = tokio::io::stdin();
111
+ let stdout = tokio::io::stdout();
112
+ let agent = GrokAcpAgent::new(config.clone(), model).await?;
113
+
114
+ info!("Starting ACP session on stdio");
115
+ run_acp_session(stdin, stdout, agent).await
116
+ }
117
+
118
+ /// Handle individual ACP client connections
119
+ async fn handle_acp_client(
120
+ stream: tokio::net::TcpStream,
121
+ client_addr: SocketAddr,
122
+ stats: Arc<RwLock<ServerStats>>,
123
+ config: Config,
124
+ ) -> Result<()> {
125
+ info!("Handling ACP client: {}", client_addr);
126
+
127
+ // Update connection stats
128
+ {
129
+ let mut stats_guard = stats.write().await;
130
+ stats_guard.connections += 1;
131
+ stats_guard.active_connections += 1;
132
+ }
133
+
134
+ // Create the Grok ACP agent
135
+ let agent = GrokAcpAgent::new(config, None).await?;
136
+
137
+ // Split stream
138
+ let (reader, writer) = stream.into_split();
139
+
140
+ // Handle the ACP protocol over the stream
141
+ let result = run_acp_session(reader, writer, agent).await;
142
+
143
+ // Update stats on disconnect
144
+ {
145
+ let mut stats_guard = stats.write().await;
146
+ stats_guard.active_connections -= 1;
147
+ }
148
+
149
+ match result {
150
+ Ok(()) => info!("ACP client {} disconnected cleanly", client_addr),
151
+ Err(e) => {
152
+ warn!("ACP client {} disconnected with error: {}", client_addr, e);
153
+ return Err(e);
154
+ }
155
+ }
156
+
157
+ Ok(())
158
+ }
159
+
160
+ /// Run an ACP session with a connected client
161
+ async fn run_acp_session<R, W>(reader: R, mut writer: W, agent: GrokAcpAgent) -> Result<()>
162
+ where
163
+ R: tokio::io::AsyncRead + Unpin,
164
+ W: tokio::io::AsyncWrite + Unpin,
165
+ {
166
+ let mut reader = BufReader::new(reader);
167
+ let mut line = String::new();
168
+
169
+ loop {
170
+ line.clear();
171
+ match reader.read_line(&mut line).await {
172
+ Ok(0) => break, // EOF
173
+ Ok(_) => {
174
+ let trimmed = line.trim();
175
+ if trimmed.is_empty() {
176
+ continue;
177
+ }
178
+
179
+ debug!("Received message: {}", trimmed);
180
+
181
+ // Attempt to parse as JSON
182
+ match serde_json::from_str::<Value>(trimmed) {
183
+ Ok(json_msg) => {
184
+ // Handle JSON-RPC message
185
+ if let Err(e) = handle_json_rpc(&json_msg, &mut writer, &agent).await {
186
+ error!("Error handling message: {}", e);
187
+ // Optionally send error response back to client
188
+ }
189
+ }
190
+ Err(e) => {
191
+ warn!("Invalid JSON received: {} (Error: {})", trimmed, e);
192
+ }
193
+ }
194
+ }
195
+ Err(e) => return Err(e.into()),
196
+ }
197
+ }
198
+
199
+ info!("ACP session completed");
200
+
201
+ // End chat logging session
202
+ if let Err(e) = chat_logger::end_session() {
203
+ warn!("Failed to end chat logging session: {}", e);
204
+ }
205
+
206
+ Ok(())
207
+ }
208
+
209
+ async fn handle_json_rpc<W>(msg: &Value, writer: &mut W, agent: &GrokAcpAgent) -> Result<()>
210
+ where
211
+ W: tokio::io::AsyncWrite + Unpin,
212
+ {
213
+ // Check if it's a request (has "method" and "id")
214
+ if let (Some(method), Some(id)) = (msg.get("method").and_then(|m| m.as_str()), msg.get("id")) {
215
+ info!("Handling request: {} (id: {})", method, id);
216
+
217
+ let params = msg.get("params").cloned().unwrap_or(json!({}));
218
+
219
+ let response_result = if method == AGENT_METHOD_NAMES.initialize {
220
+ handle_initialize(&params, agent).await
221
+ } else if method == AGENT_METHOD_NAMES.session_new {
222
+ handle_session_new(&params, agent).await
223
+ } else if method == AGENT_METHOD_NAMES.session_prompt {
224
+ handle_session_prompt(&params, agent, writer).await
225
+ } else {
226
+ warn!("Unknown method: {}", method);
227
+ Err(anyhow!("Method not found: {}", method))
228
+ };
229
+
230
+ let response = match response_result {
231
+ Ok(result) => json!({
232
+ "jsonrpc": "2.0",
233
+ "id": id,
234
+ "result": result
235
+ }),
236
+ Err(e) => json!({
237
+ "jsonrpc": "2.0",
238
+ "id": id,
239
+ "error": {
240
+ "code": -32601,
241
+ "message": e.to_string()
242
+ }
243
+ }),
244
+ };
245
+
246
+ // Send response
247
+ let response_str = serde_json::to_string(&response)?;
248
+ writer.write_all(response_str.as_bytes()).await?;
249
+ writer.write_all(b"\n").await?;
250
+ writer.flush().await?;
251
+ } else if let Some(method) = msg.get("method").and_then(|m| m.as_str()) {
252
+ // Notification
253
+ info!("Received notification: {}", method);
254
+ }
255
+
256
+ Ok(())
257
+ }
258
+
259
+ async fn handle_initialize(params: &Value, _agent: &GrokAcpAgent) -> Result<Value> {
260
+ info!("Received initialize request with params: {}", params);
261
+
262
+ // Parse the initialize request with better error handling
263
+ let req: InitializeRequest = match serde_json::from_value::<InitializeRequest>(params.clone()) {
264
+ Ok(req) => {
265
+ info!(
266
+ "Successfully parsed initialize request: protocol_version={}",
267
+ req.protocol_version
268
+ );
269
+ req
270
+ }
271
+ Err(e) => {
272
+ error!("Failed to parse initialize parameters: {}", e);
273
+ error!(
274
+ "Raw params received: {}",
275
+ serde_json::to_string_pretty(params).unwrap_or_default()
276
+ );
277
+ return Err(anyhow!(
278
+ "Invalid initialize parameters: {}. Received: {}",
279
+ e,
280
+ params
281
+ ));
282
+ }
283
+ };
284
+
285
+ info!("Client info: {}", req.client_info);
286
+ info!("Client capabilities: {}", req.capabilities);
287
+
288
+ let mut caps = AgentCapabilities::new();
289
+ // Enable session capabilities
290
+ caps.session_capabilities = crate::acp::protocol::SessionCapabilities::new();
291
+ // Configure other capabilities as needed
292
+
293
+ // Echo back the client's protocol version
294
+ let response = InitializeResponse::new(&req.protocol_version)
295
+ .agent_capabilities(caps)
296
+ .agent_info(Implementation::new("grok-cli", env!("CARGO_PKG_VERSION")));
297
+
298
+ info!(
299
+ "Sending initialize response: protocol_version={}",
300
+ req.protocol_version
301
+ );
302
+ Ok(serde_json::to_value(response)?)
303
+ }
304
+
305
+ async fn handle_session_new(params: &Value, agent: &GrokAcpAgent) -> Result<Value> {
306
+ let req: NewSessionRequest = serde_json::from_value(params.clone())
307
+ .map_err(|e| anyhow!("Invalid session/new parameters: {}", e))?;
308
+
309
+ // Extract workspace context from request or environment
310
+ let workspace_root = req
311
+ .workspace_root
312
+ .or(req.working_directory)
313
+ .or_else(|| std::env::var("CODER_AGENT_WORKSPACE_PATH").ok())
314
+ .or_else(|| std::env::var("WORKSPACE_ROOT").ok());
315
+
316
+ // If workspace root is provided, update trusted directories
317
+ if let Some(workspace_path) = &workspace_root {
318
+ use std::path::PathBuf;
319
+ let path = PathBuf::from(workspace_path);
320
+ if let Ok(canonical_path) = path.canonicalize() {
321
+ info!(
322
+ "Adding workspace root to trusted directories: {:?}",
323
+ canonical_path
324
+ );
325
+ agent.security.add_trusted_directory(canonical_path);
326
+ } else {
327
+ warn!("Failed to canonicalize workspace path: {}", workspace_path);
328
+ }
329
+ } else {
330
+ info!("No workspace root provided, using current directory from agent initialization");
331
+ }
332
+
333
+ // Generate a session ID
334
+ let session_id_str = uuid::Uuid::new_v4().to_string();
335
+ let session_id = SessionId::new(session_id_str.clone());
336
+
337
+ // Initialize session in GrokAcpAgent
338
+ agent
339
+ .initialize_session(session_id, Some(SessionConfig::default()))
340
+ .await?;
341
+
342
+ // Start chat logging for this session
343
+ if let Err(e) = chat_logger::start_session(&session_id_str) {
344
+ warn!(
345
+ "Failed to start chat logging for session {}: {}",
346
+ session_id_str, e
347
+ );
348
+ } else {
349
+ info!("Chat logging started for session: {}", session_id_str);
350
+ // Log session initialization
351
+ if let Err(e) = chat_logger::log_system(format!("Session {} initialized", session_id_str)) {
352
+ warn!("Failed to log system message: {}", e);
353
+ }
354
+ }
355
+
356
+ let response = NewSessionResponse::new(SessionId::new(session_id_str));
357
+ Ok(serde_json::to_value(response)?)
358
+ }
359
+
360
+ async fn handle_session_prompt<W>(
361
+ params: &Value,
362
+ agent: &GrokAcpAgent,
363
+ writer: &mut W,
364
+ ) -> Result<Value>
365
+ where
366
+ W: tokio::io::AsyncWrite + Unpin,
367
+ {
368
+ let req: PromptRequest = serde_json::from_value(params.clone())
369
+ .map_err(|e| anyhow!("Invalid session/prompt parameters: {}", e))?;
370
+
371
+ let session_id = SessionId::new(req.session_id.0.clone());
372
+
373
+ // Extract text from prompt
374
+ let mut message_text = String::new();
375
+ for block in req.prompt {
376
+ match block {
377
+ ContentBlock::Text(text) => message_text.push_str(&text.text),
378
+ ContentBlock::ResourceLink(link) => {
379
+ message_text.push_str(&format!("\n[Resource: {} ({})]", link.name, link.uri));
380
+ }
381
+ ContentBlock::Resource(res) => {
382
+ let crate::acp::protocol::EmbeddedResourceResource::TextResourceContents(text_res) =
383
+ res.resource;
384
+ message_text.push_str(&format!(
385
+ "\n[Context: {}]\n{}\n",
386
+ text_res.uri, text_res.text
387
+ ));
388
+ }
389
+ }
390
+ }
391
+
392
+ if message_text.is_empty() {
393
+ return Err(anyhow!("Empty prompt received"));
394
+ }
395
+
396
+ // Log user prompt
397
+ if let Err(e) = chat_logger::log_user(&message_text) {
398
+ warn!("Failed to log user message: {}", e);
399
+ }
400
+
401
+ // Call Grok agent
402
+ // Note: options mapping is simplified here. Real implementation would map model/temp params.
403
+ info!(
404
+ "Calling Grok API for session {} with message: {}",
405
+ session_id.0, message_text
406
+ );
407
+ let response_text = agent
408
+ .handle_chat_completion(&session_id, &message_text, None)
409
+ .await?;
410
+
411
+ info!("Received response from Grok: {} chars", response_text.len());
412
+ debug!("Response text: {}", response_text);
413
+
414
+ let final_text = if response_text.is_empty() {
415
+ warn!("Grok returned empty response text!");
416
+ "[No response content received from model]".to_string()
417
+ } else {
418
+ response_text
419
+ };
420
+
421
+ // Log assistant response
422
+ if let Err(e) = chat_logger::log_assistant(&final_text) {
423
+ warn!("Failed to log assistant response: {}", e);
424
+ }
425
+
426
+ // Send update notification with response text
427
+ info!("About to send text update notification...");
428
+ send_text_update(writer, &session_id.0, &final_text).await?;
429
+ info!("Text update notification sent");
430
+
431
+ // Return response indicating turn end
432
+ info!("Returning final response with stopReason: EndTurn");
433
+ let response = PromptResponse::new(StopReason::EndTurn);
434
+ Ok(serde_json::to_value(response)?)
435
+ }
436
+
437
+ /// Helper to send text update notification
438
+ async fn send_text_update<W>(writer: &mut W, session_id: &str, text: &str) -> Result<()>
439
+ where
440
+ W: tokio::io::AsyncWrite + Unpin,
441
+ {
442
+ info!(
443
+ "Sending text update for session {}: {} chars",
444
+ session_id,
445
+ text.len()
446
+ );
447
+ debug!("Text content: {}", text);
448
+
449
+ // Create the content block with text
450
+ let content = ContentBlock::Text(TextContent::new(text));
451
+ debug!("Created content block: {:?}", content);
452
+
453
+ // Create the update chunk
454
+ let update = SessionUpdate::AgentMessageChunk(ContentChunk::new(content));
455
+ debug!("Created update: {:?}", update);
456
+
457
+ // Create the notification params
458
+ let params = SessionNotification::new(SessionId::new(session_id), update);
459
+ debug!("Created notification params: {:?}", params);
460
+
461
+ // ACP uses `session/update` notification
462
+ let notification = json!({
463
+ "jsonrpc": "2.0",
464
+ "method": "session/update",
465
+ "params": params
466
+ });
467
+
468
+ let msg = serde_json::to_string(&notification)?;
469
+ info!("Sending notification JSON:");
470
+ info!("{}", msg);
471
+
472
+ // Pretty print for debugging
473
+ if let Ok(pretty) = serde_json::to_string_pretty(&notification) {
474
+ debug!("Pretty notification:\n{}", pretty);
475
+ }
476
+
477
+ writer.write_all(msg.as_bytes()).await?;
478
+ writer.write_all(b"\n").await?;
479
+ writer.flush().await?;
480
+ info!("Text update notification sent successfully");
481
+ Ok(())
482
+ }
483
+
484
+ /// Test ACP connection to a running server
485
+ async fn test_acp_connection(address: &str, config: &Config) -> Result<()> {
486
+ print_info(&format!("Testing ACP connection to {}", address));
487
+
488
+ let spinner = create_spinner("Connecting...");
489
+
490
+ let result = tokio::time::timeout(
491
+ std::time::Duration::from_secs(10),
492
+ test_acp_connection_inner(address, config),
493
+ )
494
+ .await;
495
+
496
+ spinner.finish_and_clear();
497
+
498
+ match result {
499
+ Ok(Ok(())) => {
500
+ print_success("ACP connection test successful");
501
+ Ok(())
502
+ }
503
+ Ok(Err(e)) => {
504
+ print_error(&format!("ACP connection test failed: {}", e));
505
+ Err(e)
506
+ }
507
+ Err(_) => {
508
+ let err = anyhow!("ACP connection test timed out");
509
+ print_error(&err.to_string());
510
+ Err(err)
511
+ }
512
+ }
513
+ }
514
+
515
+ async fn test_acp_connection_inner(address: &str, _config: &Config) -> Result<()> {
516
+ // Try to connect to the ACP server
517
+ let stream = tokio::net::TcpStream::connect(address)
518
+ .await
519
+ .map_err(|e| anyhow!("Failed to connect to ACP server at {}: {}", address, e))?;
520
+
521
+ debug!("Connected to ACP server at {}", address);
522
+
523
+ // Send a basic ping/test message
524
+ // This would be a proper ACP message in a full implementation
525
+
526
+ // Close connection cleanly
527
+ drop(stream);
528
+
529
+ Ok(())
530
+ }
531
+
532
+ /// Show ACP capabilities and information
533
+ async fn show_acp_capabilities() -> Result<()> {
534
+ println!("{}", "🔧 Grok CLI ACP Capabilities".cyan().bold());
535
+ println!();
536
+
537
+ let capabilities = get_acp_capabilities();
538
+
539
+ // Protocol Information
540
+ println!("{}", "Protocol Information:".green().bold());
541
+ println!(" Version: {}", capabilities.protocol_version);
542
+ println!(" Implementation: {}", capabilities.implementation);
543
+ println!(" Features: {}", capabilities.features.join(", "));
544
+ println!();
545
+
546
+ // Tools
547
+ println!("{}", "Available Tools:".green().bold());
548
+ for tool in &capabilities.tools {
549
+ println!(" • {} - {}", tool.name.cyan(), tool.description);
550
+ if !tool.parameters.is_empty() {
551
+ println!(" Parameters: {}", tool.parameters.join(", "));
552
+ }
553
+ }
554
+ println!();
555
+
556
+ // Models
557
+ println!("{}", "Supported Models:".green().bold());
558
+ for model in &capabilities.models {
559
+ println!(" • {} - {}", model.name.cyan(), model.description);
560
+ println!(
561
+ " Max tokens: {}, Context: {}",
562
+ model.max_tokens, model.context_length
563
+ );
564
+ }
565
+ println!();
566
+
567
+ // Configuration
568
+ println!("{}", "Configuration:".green().bold());
569
+ println!(" Default timeout: {}s", capabilities.default_timeout);
570
+ println!(" Max retries: {}", capabilities.max_retries);
571
+ println!(
572
+ " Concurrent sessions: {}",
573
+ capabilities.max_concurrent_sessions
574
+ );
575
+
576
+ Ok(())
577
+ }
578
+
579
+ /// Get the current ACP capabilities
580
+ fn get_acp_capabilities() -> AcpCapabilities {
581
+ AcpCapabilities {
582
+ protocol_version: "1.0".to_string(),
583
+ implementation: "grok-cli".to_string(),
584
+ features: vec![
585
+ "chat_completion".to_string(),
586
+ "code_generation".to_string(),
587
+ "code_review".to_string(),
588
+ "code_explanation".to_string(),
589
+ "file_operations".to_string(),
590
+ ],
591
+ tools: vec![
592
+ ToolInfo {
593
+ name: "chat_complete".to_string(),
594
+ description: "Generate chat completions using Grok AI".to_string(),
595
+ parameters: vec![
596
+ "message".to_string(),
597
+ "temperature".to_string(),
598
+ "max_tokens".to_string(),
599
+ ],
600
+ },
601
+ ToolInfo {
602
+ name: "code_explain".to_string(),
603
+ description: "Explain code functionality and structure".to_string(),
604
+ parameters: vec!["code".to_string(), "language".to_string()],
605
+ },
606
+ ToolInfo {
607
+ name: "code_review".to_string(),
608
+ description: "Review code for issues and improvements".to_string(),
609
+ parameters: vec!["code".to_string(), "focus".to_string()],
610
+ },
611
+ ToolInfo {
612
+ name: "code_generate".to_string(),
613
+ description: "Generate code from natural language descriptions".to_string(),
614
+ parameters: vec!["description".to_string(), "language".to_string()],
615
+ },
616
+ ],
617
+ models: vec![
618
+ ModelInfo {
619
+ name: "grok-3".to_string(),
620
+ description: "Grok 3 flagship model".to_string(),
621
+ max_tokens: 131072,
622
+ context_length: 131072,
623
+ },
624
+ ModelInfo {
625
+ name: "grok-3-mini".to_string(),
626
+ description: "Efficient Grok 3 mini model".to_string(),
627
+ max_tokens: 131072,
628
+ context_length: 131072,
629
+ },
630
+ ModelInfo {
631
+ name: "grok-2".to_string(),
632
+ description: "Grok 2 model".to_string(),
633
+ max_tokens: 131072,
634
+ context_length: 131072,
635
+ },
636
+ ModelInfo {
637
+ name: "grok-beta".to_string(),
638
+ description: "Grok Beta model".to_string(),
639
+ max_tokens: 131072,
640
+ context_length: 131072,
641
+ },
642
+ ],
643
+ default_timeout: 30,
644
+ max_retries: 3,
645
+ max_concurrent_sessions: 10,
646
+ }
647
+ }
648
+
649
+ /// ACP capabilities structure
650
+ #[derive(Debug)]
651
+ struct AcpCapabilities {
652
+ protocol_version: String,
653
+ implementation: String,
654
+ features: Vec<String>,
655
+ tools: Vec<ToolInfo>,
656
+ models: Vec<ModelInfo>,
657
+ default_timeout: u64,
658
+ max_retries: u32,
659
+ max_concurrent_sessions: u32,
660
+ }
661
+
662
+ /// Tool information for ACP capabilities
663
+ #[derive(Debug)]
664
+ struct ToolInfo {
665
+ name: String,
666
+ description: String,
667
+ parameters: Vec<String>,
668
+ }
669
+
670
+ /// Model information for ACP capabilities
671
+ #[derive(Debug)]
672
+ struct ModelInfo {
673
+ name: String,
674
+ description: String,
675
+ max_tokens: u32,
676
+ context_length: u32,
677
+ }
678
+
679
+ /// Server statistics tracking
680
+ #[derive(Debug, Default)]
681
+ struct ServerStats {
682
+ connections: u64,
683
+ active_connections: u64,
684
+ requests_processed: u64,
685
+ errors: u64,
686
+ start_time: Option<std::time::Instant>,
687
+ }
688
+
689
+ impl ServerStats {
690
+ fn new() -> Self {
691
+ Self {
692
+ start_time: Some(std::time::Instant::now()),
693
+ ..Default::default()
694
+ }
695
+ }
696
+ }
697
+
698
+ #[cfg(test)]
699
+ mod tests {
700
+ use super::*;
701
+
702
+ #[tokio::test]
703
+ async fn test_acp_capabilities() {
704
+ let capabilities = get_acp_capabilities();
705
+ assert_eq!(capabilities.protocol_version, "1.0");
706
+ assert!(!capabilities.tools.is_empty());
707
+ assert!(!capabilities.models.is_empty());
708
+ }
709
+
710
+ #[test]
711
+ fn test_server_stats() {
712
+ let mut stats = ServerStats::new();
713
+ assert_eq!(stats.connections, 0);
714
+ assert_eq!(stats.active_connections, 0);
715
+
716
+ stats.connections += 1;
717
+ stats.active_connections += 1;
718
+ assert_eq!(stats.connections, 1);
719
+ assert_eq!(stats.active_connections, 1);
720
+ }
721
+ }