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
package/src/acp/mod.rs ADDED
@@ -0,0 +1,741 @@
1
+ //! ACP (Agent Client Protocol) integration module
2
+ //!
3
+ //! This module provides the Grok AI agent implementation for the Agent Client Protocol,
4
+ //! enabling seamless integration with Zed editor and other ACP-compatible clients.
5
+
6
+ use crate::acp::protocol::SessionId;
7
+ use anyhow::{Result, anyhow};
8
+ use serde_json::{Value, json};
9
+ use std::collections::HashMap;
10
+ use std::sync::Arc;
11
+ use tokio::sync::RwLock;
12
+ use tracing::{debug, info};
13
+
14
+ use crate::GrokClient;
15
+ use crate::config::Config;
16
+ use crate::hooks::HookManager;
17
+
18
+ pub mod protocol;
19
+ pub mod security;
20
+ pub mod tools;
21
+
22
+ use crate::acp::protocol::{
23
+ AGENT_METHOD_NAMES, AgentCapabilities, ContentBlock, ContentChunk, Implementation,
24
+ InitializeRequest, InitializeResponse, NewSessionRequest, NewSessionResponse, PromptRequest,
25
+ PromptResponse, ProtocolVersion, SessionId as ProtocolSessionId, SessionNotification,
26
+ SessionUpdate, StopReason, TextContent,
27
+ };
28
+ use security::SecurityManager;
29
+
30
+ /// Grok AI agent implementation for ACP
31
+ pub struct GrokAcpAgent {
32
+ /// Grok API client
33
+ grok_client: GrokClient,
34
+
35
+ /// Agent configuration
36
+ config: Config,
37
+
38
+ /// Active sessions
39
+ sessions: Arc<RwLock<HashMap<String, SessionData>>>,
40
+
41
+ /// Agent capabilities
42
+ capabilities: GrokAgentCapabilities,
43
+
44
+ /// Security manager
45
+ pub security: SecurityManager,
46
+
47
+ /// Hook manager
48
+ hook_manager: Arc<RwLock<HookManager>>,
49
+
50
+ /// Default model override
51
+ default_model: Option<String>,
52
+ }
53
+
54
+ /// Session data for tracking conversation state
55
+ #[derive(Debug, Clone)]
56
+ struct SessionData {
57
+ /// Conversation history
58
+ messages: Vec<Value>,
59
+
60
+ /// Session configuration
61
+ config: SessionConfig,
62
+
63
+ /// Creation timestamp
64
+ created_at: std::time::Instant,
65
+
66
+ /// Last activity timestamp
67
+ last_activity: std::time::Instant,
68
+ }
69
+
70
+ /// Session-specific configuration
71
+ #[derive(Debug, Clone)]
72
+ pub struct SessionConfig {
73
+ /// Model to use for this session
74
+ pub model: String,
75
+
76
+ /// Temperature setting
77
+ pub temperature: f32,
78
+
79
+ /// Maximum tokens per response
80
+ pub max_tokens: u32,
81
+
82
+ /// System prompt for this session
83
+ pub system_prompt: Option<String>,
84
+ }
85
+
86
+ /// Agent capabilities for ACP
87
+ #[derive(Debug, Clone)]
88
+ pub struct GrokAgentCapabilities {
89
+ /// Supported models
90
+ pub models: Vec<String>,
91
+
92
+ /// Maximum context length
93
+ pub max_context_length: u32,
94
+
95
+ /// Supported features
96
+ pub features: Vec<String>,
97
+
98
+ /// Tool definitions
99
+ pub tools: Vec<ToolDefinition>,
100
+ }
101
+
102
+ /// Tool definition for ACP
103
+ #[derive(Debug, Clone)]
104
+ pub struct ToolDefinition {
105
+ /// Tool name
106
+ pub name: String,
107
+
108
+ /// Tool description
109
+ pub description: String,
110
+
111
+ /// Tool parameters schema
112
+ pub parameters: Value,
113
+ }
114
+
115
+ impl Default for SessionConfig {
116
+ fn default() -> Self {
117
+ Self {
118
+ model: "grok-3".to_string(),
119
+ temperature: 0.5, // Lower temperature for more deterministic coding output
120
+ max_tokens: 4096,
121
+ system_prompt: Some(
122
+ "You are an expert software engineer and coding assistant. \
123
+ Your primary goal is to write high-quality, efficient, and maintainable code. \
124
+ You have access to tools to read files, write files, and list directories. \
125
+ Use these tools to understand the codebase and perform tasks. \
126
+ Follow these guidelines:\n\
127
+ 1. Write clean, idiomatic code adhering to standard conventions.\n\
128
+ 2. Prioritize correctness, performance, and security.\n\
129
+ 3. Provide clear explanations for your design choices.\n\
130
+ 4. When modifying existing code, respect the existing style and structure.\n\
131
+ 5. Always consider edge cases and error handling.\n\
132
+ 6. Suggest tests to verify your code when appropriate."
133
+ .to_string(),
134
+ ),
135
+ }
136
+ }
137
+ }
138
+
139
+ impl GrokAcpAgent {
140
+ /// Create a new Grok ACP agent
141
+ pub async fn new(config: Config, default_model: Option<String>) -> Result<Self> {
142
+ let api_key = config
143
+ .api_key
144
+ .as_ref()
145
+ .ok_or_else(|| anyhow!("API key not configured"))?;
146
+
147
+ let grok_client =
148
+ GrokClient::with_settings(api_key, config.timeout_secs, config.max_retries)?;
149
+
150
+ let capabilities = Self::create_capabilities();
151
+
152
+ let security = SecurityManager::new();
153
+ // Trust current directory by default, canonicalizing to resolve symlinks
154
+ if let Ok(cwd) = std::env::current_dir() {
155
+ let canonical_cwd = cwd.canonicalize().unwrap_or(cwd);
156
+ security.add_trusted_directory(canonical_cwd);
157
+ }
158
+
159
+ Ok(Self {
160
+ grok_client,
161
+ config,
162
+ sessions: Arc::new(RwLock::new(HashMap::new())),
163
+ capabilities,
164
+ security,
165
+ hook_manager: Arc::new(RwLock::new(HookManager::new())),
166
+ default_model,
167
+ })
168
+ }
169
+
170
+ /// Create agent capabilities
171
+ fn create_capabilities() -> GrokAgentCapabilities {
172
+ GrokAgentCapabilities {
173
+ models: vec![
174
+ "grok-4-1-fast-reasoning".to_string(),
175
+ "grok-4-1-fast-non-reasoning".to_string(),
176
+ "grok-code-fast-1".to_string(),
177
+ "grok-4-fast-reasoning".to_string(),
178
+ "grok-4-fast-non-reasoning".to_string(),
179
+ "grok-4-0709".to_string(),
180
+ "grok-3".to_string(),
181
+ "grok-3-mini".to_string(),
182
+ "grok-2-vision-1212".to_string(),
183
+ "grok-2".to_string(), // Fallback
184
+ ],
185
+ max_context_length: 131072,
186
+ features: vec![
187
+ "chat_completion".to_string(),
188
+ "code_generation".to_string(),
189
+ "code_review".to_string(),
190
+ "code_explanation".to_string(),
191
+ "streaming".to_string(),
192
+ "function_calling".to_string(),
193
+ ],
194
+ tools: vec![
195
+ ToolDefinition {
196
+ name: "chat_complete".to_string(),
197
+ description: "Generate chat completions using Grok AI".to_string(),
198
+ parameters: json!({
199
+ "type": "object",
200
+ "properties": {
201
+ "message": {
202
+ "type": "string",
203
+ "description": "The message to send to Grok"
204
+ },
205
+ "temperature": {
206
+ "type": "number",
207
+ "minimum": 0.0,
208
+ "maximum": 2.0,
209
+ "description": "Creativity level (0.0 to 2.0)"
210
+ },
211
+ "max_tokens": {
212
+ "type": "integer",
213
+ "minimum": 1,
214
+ "maximum": 131072,
215
+ "description": "Maximum tokens in response"
216
+ }
217
+ },
218
+ "required": ["message"]
219
+ }),
220
+ },
221
+ ToolDefinition {
222
+ name: "code_explain".to_string(),
223
+ description: "Explain code functionality and structure".to_string(),
224
+ parameters: json!({
225
+ "type": "object",
226
+ "properties": {
227
+ "code": {
228
+ "type": "string",
229
+ "description": "The code to explain"
230
+ },
231
+ "language": {
232
+ "type": "string",
233
+ "description": "Programming language (optional)"
234
+ },
235
+ "detail_level": {
236
+ "type": "string",
237
+ "enum": ["basic", "detailed", "expert"],
238
+ "description": "Level of detail in explanation"
239
+ }
240
+ },
241
+ "required": ["code"]
242
+ }),
243
+ },
244
+ ToolDefinition {
245
+ name: "code_review".to_string(),
246
+ description: "Review code for issues and improvements".to_string(),
247
+ parameters: json!({
248
+ "type": "object",
249
+ "properties": {
250
+ "code": {
251
+ "type": "string",
252
+ "description": "The code to review"
253
+ },
254
+ "focus": {
255
+ "type": "array",
256
+ "items": {
257
+ "type": "string",
258
+ "enum": ["security", "performance", "style", "bugs", "maintainability"]
259
+ },
260
+ "description": "Areas to focus on during review"
261
+ },
262
+ "language": {
263
+ "type": "string",
264
+ "description": "Programming language"
265
+ }
266
+ },
267
+ "required": ["code"]
268
+ }),
269
+ },
270
+ ToolDefinition {
271
+ name: "code_generate".to_string(),
272
+ description: "Generate code from natural language descriptions".to_string(),
273
+ parameters: json!({
274
+ "type": "object",
275
+ "properties": {
276
+ "description": {
277
+ "type": "string",
278
+ "description": "Description of what to generate"
279
+ },
280
+ "language": {
281
+ "type": "string",
282
+ "description": "Target programming language"
283
+ },
284
+ "style": {
285
+ "type": "string",
286
+ "enum": ["functional", "object-oriented", "procedural"],
287
+ "description": "Programming style preference"
288
+ },
289
+ "include_tests": {
290
+ "type": "boolean",
291
+ "description": "Whether to include unit tests"
292
+ }
293
+ },
294
+ "required": ["description"]
295
+ }),
296
+ },
297
+ ],
298
+ }
299
+ }
300
+
301
+ /// Initialize a new session
302
+ pub async fn initialize_session(
303
+ &self,
304
+ session_id: SessionId,
305
+ config: Option<SessionConfig>,
306
+ ) -> Result<()> {
307
+ let mut session_config = config.unwrap_or_default();
308
+
309
+ // Apply default model override if present and config matches default
310
+ if let Some(model) = &self.default_model {
311
+ session_config.model = model.clone();
312
+ }
313
+
314
+ let session_data = SessionData {
315
+ messages: Vec::new(),
316
+ config: session_config,
317
+ created_at: std::time::Instant::now(),
318
+ last_activity: std::time::Instant::now(),
319
+ };
320
+
321
+ let mut sessions = self.sessions.write().await;
322
+ sessions.insert(session_id.0.clone(), session_data);
323
+
324
+ info!("Initialized new ACP session: {}", session_id.0);
325
+ Ok(())
326
+ }
327
+
328
+ /// Handle a chat completion request
329
+ pub async fn handle_chat_completion(
330
+ &self,
331
+ session_id: &SessionId,
332
+ message: &str,
333
+ options: Option<Value>,
334
+ ) -> Result<String> {
335
+ let mut sessions = self.sessions.write().await;
336
+ let session = sessions
337
+ .get_mut(&session_id.0)
338
+ .ok_or_else(|| anyhow!("Session not found: {}", session_id.0))?;
339
+
340
+ // Update last activity
341
+ session.last_activity = std::time::Instant::now();
342
+
343
+ // Add user message to history
344
+ session.messages.push(json!({
345
+ "role": "user",
346
+ "content": message
347
+ }));
348
+
349
+ // Extract options
350
+ let temperature = options
351
+ .as_ref()
352
+ .and_then(|o| o.get("temperature"))
353
+ .and_then(|t| t.as_f64())
354
+ .map(|t| t as f32)
355
+ .unwrap_or(session.config.temperature);
356
+
357
+ let max_tokens = options
358
+ .as_ref()
359
+ .and_then(|o| o.get("max_tokens"))
360
+ .and_then(|t| t.as_u64())
361
+ .map(|t| t as u32)
362
+ .unwrap_or(session.config.max_tokens);
363
+
364
+ let tool_defs = tools::get_tool_definitions();
365
+ let mut loop_count = 0;
366
+ let max_loops = 10;
367
+
368
+ loop {
369
+ if loop_count >= max_loops {
370
+ return Err(anyhow!("Max tool loop iterations reached"));
371
+ }
372
+ loop_count += 1;
373
+
374
+ // Make request to Grok
375
+ let response_msg = self
376
+ .grok_client
377
+ .chat_completion_with_history(
378
+ &session.messages,
379
+ temperature,
380
+ max_tokens,
381
+ &session.config.model,
382
+ Some(tool_defs.clone()),
383
+ )
384
+ .await?;
385
+
386
+ // Add assistant response to history
387
+ session.messages.push(serde_json::to_value(&response_msg)?);
388
+
389
+ if let Some(tool_calls) = &response_msg.tool_calls {
390
+ if tool_calls.is_empty() {
391
+ return Ok(response_msg.content.unwrap_or_default());
392
+ }
393
+
394
+ for tool_call in tool_calls {
395
+ let function_name = &tool_call.function.name;
396
+ let arguments = &tool_call.function.arguments;
397
+ let args: Value = serde_json::from_str(arguments).map_err(|e| {
398
+ anyhow!("Invalid tool arguments for {}: {}", function_name, e)
399
+ })?;
400
+
401
+ debug!("Executing tool: {} with args: {}", function_name, arguments);
402
+
403
+ // Execute before_tool hooks
404
+ {
405
+ let hooks = self.hook_manager.read().await;
406
+ if !hooks.execute_before_tool(function_name, &args)? {
407
+ session.messages.push(json!({
408
+ "role": "tool",
409
+ "tool_call_id": tool_call.id,
410
+ "content": "Tool execution blocked by hook."
411
+ }));
412
+ continue;
413
+ }
414
+ }
415
+
416
+ let result = match function_name.as_str() {
417
+ "read_file" => {
418
+ let path = args["path"].as_str().ok_or(anyhow!("Missing path"))?;
419
+ tools::read_file(path, &self.security.get_policy())
420
+ }
421
+ "write_file" => {
422
+ let path = args["path"].as_str().ok_or(anyhow!("Missing path"))?;
423
+ let content =
424
+ args["content"].as_str().ok_or(anyhow!("Missing content"))?;
425
+ tools::write_file(path, content, &self.security.get_policy())
426
+ }
427
+ "list_directory" => {
428
+ let path = args["path"].as_str().ok_or(anyhow!("Missing path"))?;
429
+ tools::list_directory(path, &self.security.get_policy())
430
+ }
431
+ "glob_search" => {
432
+ let pattern =
433
+ args["pattern"].as_str().ok_or(anyhow!("Missing pattern"))?;
434
+ tools::glob_search(pattern, &self.security.get_policy())
435
+ }
436
+ "search_file_content" => {
437
+ let path = args["path"].as_str().ok_or(anyhow!("Missing path"))?;
438
+ let pattern =
439
+ args["pattern"].as_str().ok_or(anyhow!("Missing pattern"))?;
440
+ tools::search_file_content(path, pattern, &self.security.get_policy())
441
+ }
442
+ "run_shell_command" => {
443
+ let command =
444
+ args["command"].as_str().ok_or(anyhow!("Missing command"))?;
445
+ tools::run_shell_command(command, &self.security.get_policy())
446
+ }
447
+ "replace" => {
448
+ let path = args["path"].as_str().ok_or(anyhow!("Missing path"))?;
449
+ let old_string = args["old_string"]
450
+ .as_str()
451
+ .ok_or(anyhow!("Missing old_string"))?;
452
+ let new_string = args["new_string"]
453
+ .as_str()
454
+ .ok_or(anyhow!("Missing new_string"))?;
455
+ let expected_replacements =
456
+ args["expected_replacements"].as_u64().map(|n| n as u32);
457
+ tools::replace(
458
+ path,
459
+ old_string,
460
+ new_string,
461
+ expected_replacements,
462
+ &self.security.get_policy(),
463
+ )
464
+ }
465
+ "save_memory" => {
466
+ let fact = args["fact"].as_str().ok_or(anyhow!("Missing fact"))?;
467
+ tools::save_memory(fact)
468
+ }
469
+ "web_search" => {
470
+ let query = args["query"].as_str().ok_or(anyhow!("Missing query"))?;
471
+ tools::web_search(query).await
472
+ }
473
+ "web_fetch" => {
474
+ let url = args["url"].as_str().ok_or(anyhow!("Missing url"))?;
475
+ tools::web_fetch(url).await
476
+ }
477
+ _ => Err(anyhow!("Unknown tool: {}", function_name)),
478
+ };
479
+
480
+ let content = match result {
481
+ Ok(s) => s,
482
+ Err(e) => format!("Error executing tool {}: {}", function_name, e),
483
+ };
484
+
485
+ // Execute after_tool hooks
486
+ {
487
+ let hooks = self.hook_manager.read().await;
488
+ hooks.execute_after_tool(function_name, &args, &content)?;
489
+ }
490
+
491
+ // Add tool result to history
492
+ session.messages.push(json!({
493
+ "role": "tool",
494
+ "tool_call_id": tool_call.id,
495
+ "content": content
496
+ }));
497
+ }
498
+ // Continue loop to get next response from model
499
+ } else {
500
+ // No tool calls, return content
501
+ let final_content = response_msg.content.unwrap_or_default();
502
+ debug!(
503
+ "Chat completion for session {}: {} -> {}",
504
+ session_id.0, message, final_content
505
+ );
506
+ return Ok(final_content);
507
+ }
508
+ }
509
+ }
510
+
511
+ /// Handle code explanation request
512
+ pub async fn handle_code_explain(
513
+ &self,
514
+ session_id: &SessionId,
515
+ code: &str,
516
+ language: Option<&str>,
517
+ detail_level: Option<&str>,
518
+ ) -> Result<String> {
519
+ let detail = detail_level.unwrap_or("detailed");
520
+ let lang_hint = language
521
+ .map(|l| format!(" (language: {})", l))
522
+ .unwrap_or_default();
523
+
524
+ let system_prompt = format!(
525
+ "You are an expert code reviewer and teacher. Explain the provided code with {} detail. Focus on:\n\
526
+ - What the code does\n\
527
+ - How it works\n\
528
+ - Key concepts and patterns used\n\
529
+ - Potential improvements\n\
530
+ Be clear and educational in your explanation.",
531
+ detail
532
+ );
533
+
534
+ let user_message = format!(
535
+ "Please explain this code{}:\n\n```\n{}\n```",
536
+ lang_hint, code
537
+ );
538
+
539
+ self.handle_chat_with_system_prompt(session_id, &user_message, &system_prompt)
540
+ .await
541
+ }
542
+
543
+ /// Handle code review request
544
+ pub async fn handle_code_review(
545
+ &self,
546
+ session_id: &SessionId,
547
+ code: &str,
548
+ focus_areas: Option<&[String]>,
549
+ language: Option<&str>,
550
+ ) -> Result<String> {
551
+ let focus = focus_areas
552
+ .map(|areas| format!("Focus areas: {}", areas.join(", ")))
553
+ .unwrap_or_else(|| "Comprehensive review".to_string());
554
+
555
+ let lang_hint = language
556
+ .map(|l| format!(" (language: {})", l))
557
+ .unwrap_or_default();
558
+
559
+ let system_prompt = format!(
560
+ "You are an expert code reviewer. Review the provided code for:\n\
561
+ - Bugs and potential issues\n\
562
+ - Security vulnerabilities\n\
563
+ - Performance improvements\n\
564
+ - Code style and best practices\n\
565
+ - Maintainability\n\
566
+ Provide specific, actionable feedback. {}",
567
+ focus
568
+ );
569
+
570
+ let user_message = format!(
571
+ "Please review this code{}:\n\n```\n{}\n```",
572
+ lang_hint, code
573
+ );
574
+
575
+ self.handle_chat_with_system_prompt(session_id, &user_message, &system_prompt)
576
+ .await
577
+ }
578
+
579
+ /// Handle code generation request
580
+ pub async fn handle_code_generate(
581
+ &self,
582
+ session_id: &SessionId,
583
+ description: &str,
584
+ language: Option<&str>,
585
+ style: Option<&str>,
586
+ include_tests: Option<bool>,
587
+ ) -> Result<String> {
588
+ let lang = language.unwrap_or("Python");
589
+ let prog_style = style.unwrap_or("object-oriented");
590
+ let tests = if include_tests.unwrap_or(false) {
591
+ "Include comprehensive unit tests."
592
+ } else {
593
+ ""
594
+ };
595
+
596
+ let system_prompt = format!(
597
+ "You are an expert software developer. Generate clean, well-documented {} code \
598
+ using {} programming style. Follow best practices and include helpful comments. {}",
599
+ lang, prog_style, tests
600
+ );
601
+
602
+ let user_message = format!("Generate code for: {}", description);
603
+
604
+ self.handle_chat_with_system_prompt(session_id, &user_message, &system_prompt)
605
+ .await
606
+ }
607
+
608
+ /// Handle chat with a specific system prompt
609
+ async fn handle_chat_with_system_prompt(
610
+ &self,
611
+ session_id: &SessionId,
612
+ message: &str,
613
+ system_prompt: &str,
614
+ ) -> Result<String> {
615
+ // Create a temporary session with the system prompt
616
+ let messages = vec![
617
+ json!({
618
+ "role": "system",
619
+ "content": system_prompt
620
+ }),
621
+ json!({
622
+ "role": "user",
623
+ "content": message
624
+ }),
625
+ ];
626
+
627
+ let sessions = self.sessions.read().await;
628
+ let session = sessions
629
+ .get(&session_id.0)
630
+ .ok_or_else(|| anyhow!("Session not found: {}", session_id.0))?;
631
+
632
+ let response = self
633
+ .grok_client
634
+ .chat_completion_with_history(
635
+ &messages,
636
+ session.config.temperature,
637
+ session.config.max_tokens,
638
+ &session.config.model,
639
+ None,
640
+ )
641
+ .await?;
642
+
643
+ debug!(
644
+ "Code operation for session {}: {} -> {}",
645
+ session_id.0,
646
+ message,
647
+ response.content.clone().unwrap_or_default()
648
+ );
649
+
650
+ Ok(response.content.unwrap_or_default())
651
+ }
652
+
653
+ /// Get agent capabilities
654
+ pub fn get_capabilities(&self) -> &GrokAgentCapabilities {
655
+ &self.capabilities
656
+ }
657
+
658
+ /// Clean up expired sessions
659
+ pub async fn cleanup_sessions(&self, max_age: std::time::Duration) -> Result<usize> {
660
+ let mut sessions = self.sessions.write().await;
661
+ let now = std::time::Instant::now();
662
+ let initial_count = sessions.len();
663
+
664
+ sessions.retain(|session_id, session_data| {
665
+ let expired = now.duration_since(session_data.last_activity) > max_age;
666
+ if expired {
667
+ info!("Cleaning up expired session: {}", session_id);
668
+ }
669
+ !expired
670
+ });
671
+
672
+ let cleaned = initial_count - sessions.len();
673
+ if cleaned > 0 {
674
+ info!("Cleaned up {} expired sessions", cleaned);
675
+ }
676
+
677
+ Ok(cleaned)
678
+ }
679
+
680
+ /// Get session statistics
681
+ pub async fn get_session_stats(&self) -> Result<Value> {
682
+ let sessions = self.sessions.read().await;
683
+ let now = std::time::Instant::now();
684
+
685
+ let mut active_sessions = 0;
686
+ let mut total_messages = 0;
687
+ let mut oldest_session = now;
688
+
689
+ for session_data in sessions.values() {
690
+ active_sessions += 1;
691
+ total_messages += session_data.messages.len();
692
+ if session_data.created_at < oldest_session {
693
+ oldest_session = session_data.created_at;
694
+ }
695
+ }
696
+
697
+ let uptime = now.duration_since(oldest_session).as_secs();
698
+
699
+ Ok(json!({
700
+ "active_sessions": active_sessions,
701
+ "total_messages": total_messages,
702
+ "uptime_seconds": uptime,
703
+ "capabilities": {
704
+ "models": self.capabilities.models,
705
+ "features": self.capabilities.features,
706
+ "max_context_length": self.capabilities.max_context_length
707
+ }
708
+ }))
709
+ }
710
+ }
711
+
712
+ #[cfg(test)]
713
+ mod tests {
714
+ use super::*;
715
+
716
+ #[test]
717
+ fn test_session_config_default() {
718
+ let config = SessionConfig::default();
719
+ assert_eq!(config.model, "grok-3");
720
+ assert_eq!(config.temperature, 0.5);
721
+ assert_eq!(config.max_tokens, 4096);
722
+ assert!(config.system_prompt.is_some());
723
+ }
724
+
725
+ #[test]
726
+ fn test_capabilities_creation() {
727
+ let capabilities = GrokAcpAgent::create_capabilities();
728
+ assert!(!capabilities.models.is_empty());
729
+ assert!(!capabilities.features.is_empty());
730
+ assert!(!capabilities.tools.is_empty());
731
+ assert!(capabilities.max_context_length > 0);
732
+ }
733
+
734
+ #[tokio::test]
735
+ async fn test_session_management() {
736
+ // This would require a mock config and API key for full testing
737
+ // For now, just test the structure
738
+ let session_id = SessionId::new("test-session");
739
+ assert_eq!(session_id.0.as_str(), "test-session");
740
+ }
741
+ }