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.
- package/.env.example +42 -0
- package/.github/workflows/ci.yml +30 -0
- package/.github/workflows/rust.yml +22 -0
- package/.grok/.env.example +85 -0
- package/.grok/COMPLETE_FIX_SUMMARY.md +466 -0
- package/.grok/ENV_CONFIG_GUIDE.md +173 -0
- package/.grok/QUICK_REFERENCE.md +180 -0
- package/.grok/README.md +104 -0
- package/.grok/TESTING_GUIDE.md +393 -0
- package/CHANGELOG.md +465 -0
- package/CODE_REVIEW_SUMMARY.md +414 -0
- package/COMPLETE_FIX_SUMMARY.md +415 -0
- package/CONFIGURATION.md +489 -0
- package/CONTEXT_FILES_GUIDE.md +419 -0
- package/CONTRIBUTING.md +55 -0
- package/CURSOR_POSITION_FIX.md +206 -0
- package/Cargo.toml +88 -0
- package/ERROR_HANDLING_REPORT.md +361 -0
- package/FINAL_FIX_SUMMARY.md +462 -0
- package/FIXES.md +37 -0
- package/FIXES_SUMMARY.md +87 -0
- package/GROK_API_MIGRATION_SUMMARY.md +111 -0
- package/LICENSE +22 -0
- package/MIGRATION_TO_GROK_API.md +223 -0
- package/README.md +504 -0
- package/REVIEW_COMPLETE.md +416 -0
- package/REVIEW_QUICK_REFERENCE.md +173 -0
- package/SECURITY.md +463 -0
- package/SECURITY_AUDIT.md +661 -0
- package/SETUP.md +287 -0
- package/TESTING_TOOLS.md +88 -0
- package/TESTING_TOOL_EXECUTION.md +239 -0
- package/TOOL_EXECUTION_FIX.md +491 -0
- package/VERIFICATION_CHECKLIST.md +419 -0
- package/docs/API.md +74 -0
- package/docs/CHAT_LOGGING.md +39 -0
- package/docs/CURSOR_FIX_DEMO.md +306 -0
- package/docs/ERROR_HANDLING_GUIDE.md +547 -0
- package/docs/FILE_OPERATIONS.md +449 -0
- package/docs/INTERACTIVE.md +401 -0
- package/docs/PROJECT_CREATION_GUIDE.md +570 -0
- package/docs/QUICKSTART.md +378 -0
- package/docs/QUICK_REFERENCE.md +691 -0
- package/docs/RELEASE_NOTES_0.1.2.md +240 -0
- package/docs/TOOLS.md +459 -0
- package/docs/TOOLS_QUICK_REFERENCE.md +210 -0
- package/docs/ZED_INTEGRATION.md +371 -0
- package/docs/extensions.md +464 -0
- package/docs/settings.md +293 -0
- package/examples/extensions/logging-hook/README.md +91 -0
- package/examples/extensions/logging-hook/extension.json +22 -0
- package/package.json +30 -0
- package/scripts/test_acp.py +252 -0
- package/scripts/test_acp.sh +143 -0
- package/scripts/test_acp_simple.sh +72 -0
- package/src/acp/mod.rs +741 -0
- package/src/acp/protocol.rs +323 -0
- package/src/acp/security.rs +298 -0
- package/src/acp/tools.rs +697 -0
- package/src/bin/banner_demo.rs +216 -0
- package/src/bin/docgen.rs +18 -0
- package/src/bin/installer.rs +217 -0
- package/src/cli/app.rs +310 -0
- package/src/cli/commands/acp.rs +721 -0
- package/src/cli/commands/chat.rs +485 -0
- package/src/cli/commands/code.rs +513 -0
- package/src/cli/commands/config.rs +394 -0
- package/src/cli/commands/health.rs +442 -0
- package/src/cli/commands/history.rs +421 -0
- package/src/cli/commands/mod.rs +14 -0
- package/src/cli/commands/settings.rs +1384 -0
- package/src/cli/mod.rs +166 -0
- package/src/config/mod.rs +2212 -0
- package/src/display/ascii_art.rs +139 -0
- package/src/display/banner.rs +289 -0
- package/src/display/components/input.rs +323 -0
- package/src/display/components/mod.rs +2 -0
- package/src/display/components/settings_list.rs +306 -0
- package/src/display/interactive.rs +1255 -0
- package/src/display/mod.rs +62 -0
- package/src/display/terminal.rs +42 -0
- package/src/display/tips.rs +316 -0
- package/src/grok_client_ext.rs +177 -0
- package/src/hooks/loader.rs +407 -0
- package/src/hooks/mod.rs +158 -0
- package/src/lib.rs +174 -0
- package/src/main.rs +65 -0
- package/src/mcp/client.rs +195 -0
- package/src/mcp/config.rs +20 -0
- package/src/mcp/mod.rs +6 -0
- package/src/mcp/protocol.rs +67 -0
- package/src/utils/auth.rs +41 -0
- package/src/utils/chat_logger.rs +568 -0
- package/src/utils/context.rs +390 -0
- package/src/utils/mod.rs +16 -0
- package/src/utils/network.rs +320 -0
- package/src/utils/rate_limiter.rs +166 -0
- package/src/utils/session.rs +73 -0
- package/src/utils/shell_permissions.rs +389 -0
- 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
|
+
}
|