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,62 @@
1
+ //! Display module for Grok CLI
2
+ //!
3
+ //! Handles ASCII art, banners, tips, and other visual elements
4
+
5
+ pub mod ascii_art;
6
+ pub mod banner;
7
+ pub mod components;
8
+ pub mod interactive;
9
+ pub mod terminal;
10
+ pub mod tips;
11
+
12
+ pub use ascii_art::print_grok_logo;
13
+ pub use banner::{
14
+ clear_current_line, print_directory_recommendation, print_welcome_banner,
15
+ BannerConfig,
16
+ };
17
+
18
+ use colored::*;
19
+ use std::io::{self, Write};
20
+ use terminal_size::{terminal_size, Height, Width};
21
+
22
+ /// Get terminal dimensions
23
+ pub fn get_terminal_size() -> (u16, u16) {
24
+ if let Some((Width(w), Height(h))) = terminal_size() {
25
+ (w, h)
26
+ } else {
27
+ (80, 24) // Default fallback
28
+ }
29
+ }
30
+
31
+ /// Clear the terminal screen
32
+ pub fn clear_screen() {
33
+ print!("\x1B[2J\x1B[1;1H");
34
+ io::stdout().flush().unwrap_or(());
35
+ }
36
+
37
+ /// Print a separator line
38
+ pub fn print_separator(width: u16, color: Option<Color>) {
39
+ let line = "─".repeat(width as usize);
40
+ if let Some(c) = color {
41
+ println!("{}", line.color(c));
42
+ } else {
43
+ println!("{}", line.dimmed());
44
+ }
45
+ }
46
+
47
+ /// Print centered text
48
+ pub fn print_centered(text: &str, width: u16, color: Option<Color>) {
49
+ let text_len = text.len();
50
+ let padding = if width as usize > text_len {
51
+ (width as usize - text_len) / 2
52
+ } else {
53
+ 0
54
+ };
55
+
56
+ let centered = format!("{}{}", " ".repeat(padding), text);
57
+ if let Some(c) = color {
58
+ println!("{}", centered.color(c));
59
+ } else {
60
+ println!("{}", centered);
61
+ }
62
+ }
@@ -0,0 +1,42 @@
1
+ use anyhow::Result;
2
+ use crossterm::{
3
+ execute,
4
+ terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
5
+ };
6
+ use ratatui::backend::CrosstermBackend;
7
+ use ratatui::Terminal;
8
+ use std::io::{self, Stdout};
9
+
10
+ pub type Tui = Terminal<CrosstermBackend<Stdout>>;
11
+
12
+ pub fn init_tui() -> Result<Tui> {
13
+ enable_raw_mode()?;
14
+ let mut stdout = io::stdout();
15
+ execute!(stdout, EnterAlternateScreen)?;
16
+ let backend = CrosstermBackend::new(stdout);
17
+ let terminal = Terminal::new(backend)?;
18
+ Ok(terminal)
19
+ }
20
+
21
+ pub fn restore_tui() -> Result<()> {
22
+ disable_raw_mode()?;
23
+ execute!(io::stdout(), LeaveAlternateScreen)?;
24
+ Ok(())
25
+ }
26
+
27
+ /// A lighter version of init_tui that doesn't enter alternate screen
28
+ /// Useful for the chat interface where we want to keep history visible
29
+ pub fn init_inline_tui() -> Result<Tui> {
30
+ enable_raw_mode()?;
31
+ let stdout = io::stdout();
32
+ // We don't enter alternate screen here
33
+ let backend = CrosstermBackend::new(stdout);
34
+ let terminal = Terminal::new(backend)?;
35
+ Ok(terminal)
36
+ }
37
+
38
+ pub fn restore_inline_tui() -> Result<()> {
39
+ disable_raw_mode()?;
40
+ // We don't leave alternate screen here
41
+ Ok(())
42
+ }
@@ -0,0 +1,316 @@
1
+ //! Tips display module for Grok CLI
2
+ //!
3
+ //! Handles display of helpful tips and getting started information
4
+
5
+ use colored::*;
6
+ use rand::prelude::IndexedMutRandom;
7
+ use rand::seq::SliceRandom;
8
+
9
+ /// Collection of helpful tips for users
10
+ pub static GETTING_STARTED_TIPS: &[&str] = &[
11
+ "Ask questions, edit files, or run commands.",
12
+ "Be specific for the best results.",
13
+ "/help for more information.",
14
+ "Use 'grok code explain <file>' to understand code.",
15
+ "Use 'grok code review <file>' for code improvements.",
16
+ "Use 'grok chat --interactive' for ongoing conversations.",
17
+ "Set GROK_API_KEY environment variable to avoid typing --api-key.",
18
+ "Use 'grok config show' to view your current configuration.",
19
+ "Create a config.toml file to customize default settings.",
20
+ ];
21
+
22
+ /// Advanced usage tips
23
+ pub static ADVANCED_TIPS: &[&str] = &[
24
+ "Use system prompts with 'grok chat --system \"You are a...\"' for specialized assistance.",
25
+ "Adjust temperature with --temperature for more creative or focused responses.",
26
+ "Use 'grok acp server' to integrate with Zed editor.",
27
+ "Pipe input to Grok CLI: 'cat file.py | grok chat \"Explain this code\"'.",
28
+ "Use 'grok health --all' to diagnose connectivity issues.",
29
+ "Set custom models with --model flag (e.g., grok-3, grok-2-latest).",
30
+ "Use 'grok code generate' to create new code from descriptions.",
31
+ ];
32
+
33
+ /// Productivity tips
34
+ pub static PRODUCTIVITY_TIPS: &[&str] = &[
35
+ "Create shell aliases: alias gc='grok chat' for faster access.",
36
+ "Use tab completion in your shell for Grok CLI commands.",
37
+ "Combine with other CLI tools: 'git diff | grok chat \"Review these changes\"'.",
38
+ "Set up your preferred editor integration for seamless workflow.",
39
+ "Use 'grok config set' to save frequently used settings.",
40
+ ];
41
+
42
+ /// Troubleshooting tips
43
+ pub static TROUBLESHOOTING_TIPS: &[&str] = &[
44
+ "Having connection issues? Check 'grok health --api' for diagnostics.",
45
+ "API key not working? Verify it's set correctly with 'grok config show'.",
46
+ "Slow responses? Try reducing --max-tokens or adjusting --timeout.",
47
+ "Use --verbose flag to see detailed debug information.",
48
+ "Check your internet connection - Starlink users may experience drops.",
49
+ ];
50
+
51
+ /// Configuration for tip display
52
+ #[derive(Debug, Clone)]
53
+ pub struct TipConfig {
54
+ pub show_tips: bool,
55
+ pub randomize: bool,
56
+ pub max_tips: usize,
57
+ pub width: Option<u16>,
58
+ }
59
+
60
+ impl Default for TipConfig {
61
+ fn default() -> Self {
62
+ Self {
63
+ show_tips: true,
64
+ randomize: true,
65
+ max_tips: 3,
66
+ width: None,
67
+ }
68
+ }
69
+ }
70
+
71
+ /// Print getting started tips
72
+ pub fn print_getting_started_tips(config: &TipConfig) {
73
+ if !config.show_tips {
74
+ return;
75
+ }
76
+
77
+ println!("{}", "Tips for getting started:".bright_cyan());
78
+
79
+ let tips = if config.randomize {
80
+ get_random_tips(GETTING_STARTED_TIPS, config.max_tips)
81
+ } else {
82
+ GETTING_STARTED_TIPS
83
+ .iter()
84
+ .take(config.max_tips)
85
+ .cloned()
86
+ .collect()
87
+ };
88
+
89
+ for (i, tip) in tips.iter().enumerate() {
90
+ println!("{}. {}", (i + 1).to_string().bright_white(), tip);
91
+ }
92
+ println!();
93
+ }
94
+
95
+ /// Print advanced tips for experienced users
96
+ pub fn print_advanced_tips(config: &TipConfig) {
97
+ if !config.show_tips {
98
+ return;
99
+ }
100
+
101
+ println!("{}", "Advanced tips:".bright_magenta());
102
+
103
+ let tips = if config.randomize {
104
+ get_random_tips(ADVANCED_TIPS, config.max_tips)
105
+ } else {
106
+ ADVANCED_TIPS
107
+ .iter()
108
+ .take(config.max_tips)
109
+ .cloned()
110
+ .collect()
111
+ };
112
+
113
+ for tip in tips {
114
+ println!("• {}", tip);
115
+ }
116
+ println!();
117
+ }
118
+
119
+ /// Print productivity tips
120
+ pub fn print_productivity_tips(config: &TipConfig) {
121
+ if !config.show_tips {
122
+ return;
123
+ }
124
+
125
+ println!("{}", "Productivity tips:".bright_green());
126
+
127
+ let tips = if config.randomize {
128
+ get_random_tips(PRODUCTIVITY_TIPS, config.max_tips)
129
+ } else {
130
+ PRODUCTIVITY_TIPS
131
+ .iter()
132
+ .take(config.max_tips)
133
+ .cloned()
134
+ .collect()
135
+ };
136
+
137
+ for tip in tips {
138
+ println!("💡 {}", tip);
139
+ }
140
+ println!();
141
+ }
142
+
143
+ /// Print troubleshooting tips
144
+ pub fn print_troubleshooting_tips(config: &TipConfig) {
145
+ if !config.show_tips {
146
+ return;
147
+ }
148
+
149
+ println!("{}", "Troubleshooting tips:".bright_yellow());
150
+
151
+ let tips = if config.randomize {
152
+ get_random_tips(TROUBLESHOOTING_TIPS, config.max_tips)
153
+ } else {
154
+ TROUBLESHOOTING_TIPS
155
+ .iter()
156
+ .take(config.max_tips)
157
+ .cloned()
158
+ .collect()
159
+ };
160
+
161
+ for tip in tips {
162
+ println!("🔧 {}", tip);
163
+ }
164
+ println!();
165
+ }
166
+
167
+ /// Get random tips from a collection
168
+ fn get_random_tips<'a>(tips: &'a [&'a str], count: usize) -> Vec<&'a str> {
169
+ let mut rng = rand::rng();
170
+ let mut selected_tips: Vec<&str> = tips.to_vec();
171
+ selected_tips.shuffle(&mut rng);
172
+ selected_tips.into_iter().take(count).collect()
173
+ }
174
+
175
+ /// Print a tip of the day
176
+ pub fn print_tip_of_the_day() {
177
+ let mut all_tips: Vec<&str> = GETTING_STARTED_TIPS
178
+ .iter()
179
+ .chain(ADVANCED_TIPS.iter())
180
+ .chain(PRODUCTIVITY_TIPS.iter())
181
+ .chain(TROUBLESHOOTING_TIPS.iter())
182
+ .cloned()
183
+ .collect();
184
+
185
+ if let Some(tip) = all_tips.choose_mut(&mut rand::rng()) {
186
+ println!("{} {}", "💡 Tip of the day:".bright_yellow(), tip);
187
+ println!();
188
+ }
189
+ }
190
+
191
+ /// Print contextual tips based on current operation
192
+ pub fn print_contextual_tips(context: &str, config: &TipConfig) {
193
+ if !config.show_tips {
194
+ return;
195
+ }
196
+
197
+ match context {
198
+ "first-run" => print_getting_started_tips(config),
199
+ "error" => print_troubleshooting_tips(config),
200
+ "config" => print_config_tips(config),
201
+ "code" => print_code_tips(config),
202
+ "chat" => print_chat_tips(config),
203
+ _ => print_getting_started_tips(config),
204
+ }
205
+ }
206
+
207
+ /// Print configuration-specific tips
208
+ fn print_config_tips(config: &TipConfig) {
209
+ println!("{}", "Configuration tips:".bright_blue());
210
+ let tips = vec![
211
+ "Use 'grok config init' to create a default configuration file",
212
+ "Set your preferred model with 'grok config set default_model grok-3'",
213
+ "Configure API settings with 'grok config set api_key <your-key>'",
214
+ "Use 'grok config validate' to check your configuration",
215
+ ];
216
+
217
+ let display_tips = if config.randomize {
218
+ get_random_tips(&tips, config.max_tips)
219
+ } else {
220
+ tips.iter().take(config.max_tips).copied().collect()
221
+ };
222
+
223
+ for tip in display_tips {
224
+ println!("⚙️ {}", tip);
225
+ }
226
+ println!();
227
+ }
228
+
229
+ /// Print code-specific tips
230
+ fn print_code_tips(config: &TipConfig) {
231
+ println!("{}", "Code assistance tips:".bright_purple());
232
+ let tips = vec![
233
+ "Use 'grok code explain' to understand complex code",
234
+ "Get code reviews with 'grok code review --focus security'",
235
+ "Generate code with 'grok code generate --language rust'",
236
+ "Fix issues with 'grok code fix <file> \"description of problem\"'",
237
+ ];
238
+
239
+ let display_tips = if config.randomize {
240
+ get_random_tips(&tips, config.max_tips)
241
+ } else {
242
+ tips.iter().take(config.max_tips).copied().collect()
243
+ };
244
+
245
+ for tip in display_tips {
246
+ println!("💻 {}", tip);
247
+ }
248
+ println!();
249
+ }
250
+
251
+ /// Print chat-specific tips
252
+ fn print_chat_tips(config: &TipConfig) {
253
+ println!("{}", "Chat tips:".bright_cyan());
254
+ let tips = vec![
255
+ "Use --interactive for ongoing conversations",
256
+ "Set system prompts with --system for specialized help",
257
+ "Adjust creativity with --temperature (0.1 = focused, 1.5 = creative)",
258
+ "Use --max-tokens to control response length",
259
+ ];
260
+
261
+ let display_tips = if config.randomize {
262
+ get_random_tips(&tips, config.max_tips)
263
+ } else {
264
+ tips.iter().take(config.max_tips).copied().collect()
265
+ };
266
+
267
+ for tip in display_tips {
268
+ println!("💬 {}", tip);
269
+ }
270
+ println!();
271
+ }
272
+
273
+ /// Print a formatted help section
274
+ pub fn print_help_section(title: &str, items: &[(&str, &str)]) {
275
+ println!("{}", title.bright_cyan().bold());
276
+ println!();
277
+
278
+ for (command, description) in items {
279
+ println!(" {:<25} {}", command.bright_white(), description);
280
+ }
281
+ println!();
282
+ }
283
+
284
+ #[cfg(test)]
285
+ mod tests {
286
+ use super::*;
287
+
288
+ #[test]
289
+ fn test_get_random_tips() {
290
+ let tips = ["tip1", "tip2", "tip3", "tip4", "tip5"];
291
+ let random_tips = get_random_tips(&tips, 3);
292
+ assert_eq!(random_tips.len(), 3);
293
+
294
+ // All returned tips should be from the original set
295
+ for tip in random_tips {
296
+ assert!(tips.contains(&tip));
297
+ }
298
+ }
299
+
300
+ #[test]
301
+ fn test_tip_config_default() {
302
+ let config = TipConfig::default();
303
+ assert!(config.show_tips);
304
+ assert!(config.randomize);
305
+ assert_eq!(config.max_tips, 3);
306
+ assert!(config.width.is_none());
307
+ }
308
+
309
+ #[test]
310
+ fn test_tips_not_empty() {
311
+ assert!(!GETTING_STARTED_TIPS.is_empty());
312
+ assert!(!ADVANCED_TIPS.is_empty());
313
+ assert!(!PRODUCTIVITY_TIPS.is_empty());
314
+ assert!(!TROUBLESHOOTING_TIPS.is_empty());
315
+ }
316
+ }
@@ -0,0 +1,177 @@
1
+ //! Grok Client Extensions
2
+ //!
3
+ //! This module provides compatibility extensions for the grok_api::GrokClient
4
+ //! to maintain API compatibility with the previous local implementation.
5
+
6
+ use anyhow::Result;
7
+ use grok_api::{ChatMessage, ChatResponse as GrokApiChatResponse, Message};
8
+ use serde_json::Value;
9
+
10
+ use crate::config::RateLimitConfig;
11
+
12
+ /// Extended Grok client that wraps grok_api::GrokClient with additional methods
13
+ #[derive(Clone, Debug)]
14
+ pub struct GrokClient {
15
+ inner: grok_api::GrokClient,
16
+ rate_limit_config: Option<RateLimitConfig>,
17
+ }
18
+
19
+ impl GrokClient {
20
+ /// Create a new GrokClient with default settings
21
+ pub fn new(api_key: &str) -> Result<Self> {
22
+ let inner = grok_api::GrokClient::new(api_key)?;
23
+ Ok(Self {
24
+ inner,
25
+ rate_limit_config: None,
26
+ })
27
+ }
28
+
29
+ /// Create a new GrokClient with custom timeout and retry settings
30
+ pub fn with_settings(api_key: &str, timeout_secs: u64, max_retries: u32) -> Result<Self> {
31
+ let inner = grok_api::GrokClient::builder()
32
+ .api_key(api_key)
33
+ .timeout_secs(timeout_secs)
34
+ .max_retries(max_retries)
35
+ .build()?;
36
+
37
+ Ok(Self {
38
+ inner,
39
+ rate_limit_config: None,
40
+ })
41
+ }
42
+
43
+ /// Set rate limit configuration (for compatibility - currently a no-op)
44
+ pub fn with_rate_limits(mut self, config: RateLimitConfig) -> Self {
45
+ self.rate_limit_config = Some(config);
46
+ self
47
+ }
48
+
49
+ /// Send a single chat completion request to Grok
50
+ pub async fn chat_completion(
51
+ &self,
52
+ message: &str,
53
+ system_prompt: Option<&str>,
54
+ temperature: f32,
55
+ max_tokens: u32,
56
+ model: &str,
57
+ ) -> Result<String> {
58
+ let mut messages = Vec::new();
59
+
60
+ // Add system message if provided
61
+ if let Some(system) = system_prompt {
62
+ messages.push(ChatMessage::system(system));
63
+ }
64
+
65
+ // Add user message
66
+ messages.push(ChatMessage::user(message));
67
+
68
+ let response = self
69
+ .inner
70
+ .chat_with_history(&messages)
71
+ .temperature(temperature)
72
+ .max_tokens(max_tokens)
73
+ .model(model)
74
+ .send()
75
+ .await?;
76
+
77
+ Ok(response.content().unwrap_or("").to_string())
78
+ }
79
+
80
+ /// Send chat completion with conversation history and optional tools
81
+ pub async fn chat_completion_with_history(
82
+ &self,
83
+ messages: &[Value],
84
+ temperature: f32,
85
+ max_tokens: u32,
86
+ model: &str,
87
+ tools: Option<Vec<Value>>,
88
+ ) -> Result<Message> {
89
+ // Convert JSON messages to ChatMessage format
90
+ let chat_messages: Vec<ChatMessage> = messages
91
+ .iter()
92
+ .filter_map(|msg| {
93
+ let role = msg.get("role")?.as_str()?;
94
+ let content = msg.get("content")?.as_str()?;
95
+
96
+ Some(match role {
97
+ "system" => ChatMessage::system(content),
98
+ "user" => ChatMessage::user(content),
99
+ "assistant" => ChatMessage::assistant(content),
100
+ _ => return None,
101
+ })
102
+ })
103
+ .collect();
104
+
105
+ let mut request = self
106
+ .inner
107
+ .chat_with_history(&chat_messages)
108
+ .temperature(temperature)
109
+ .max_tokens(max_tokens)
110
+ .model(model);
111
+
112
+ // Add tools if provided
113
+ if let Some(tool_defs) = tools {
114
+ // Convert tools to the format expected by grok_api
115
+ // Note: This is a simplified conversion - you may need to adjust
116
+ // based on the exact tool format expected by grok_api
117
+ request = request.tools(tool_defs);
118
+ }
119
+
120
+ let response = request.send().await?;
121
+
122
+ // Convert the response to the Message format
123
+ convert_response_to_message(response)
124
+ }
125
+
126
+ /// Test the connection to the Grok API
127
+ pub async fn test_connection(&self) -> Result<()> {
128
+ self.inner.test_connection().await.map_err(|e| e.into())
129
+ }
130
+
131
+ /// List available models
132
+ pub async fn list_models(&self) -> Result<Vec<String>> {
133
+ self.inner.list_models().await.map_err(|e| e.into())
134
+ }
135
+
136
+ /// Get the underlying grok_api client
137
+ pub fn inner(&self) -> &grok_api::GrokClient {
138
+ &self.inner
139
+ }
140
+ }
141
+
142
+ /// Convert ChatResponse to Message format for compatibility
143
+ fn convert_response_to_message(response: GrokApiChatResponse) -> Result<Message> {
144
+ // Get the message from the first choice
145
+ if let Some(message) = response.message() {
146
+ Ok(message.clone())
147
+ } else {
148
+ Ok(Message {
149
+ role: "assistant".to_string(),
150
+ content: response.content().map(|s| s.to_string()),
151
+ tool_calls: None,
152
+ })
153
+ }
154
+ }
155
+
156
+ #[cfg(test)]
157
+ mod tests {
158
+ use super::*;
159
+
160
+ #[tokio::test]
161
+ async fn test_grok_client_creation() {
162
+ let client = GrokClient::with_settings("test-key", 30, 3);
163
+ assert!(client.is_ok());
164
+
165
+ let empty_key_client = GrokClient::with_settings("", 30, 3);
166
+ assert!(empty_key_client.is_err());
167
+ }
168
+
169
+ #[test]
170
+ fn test_with_rate_limits() {
171
+ let client = GrokClient::new("test-key").unwrap();
172
+ let rate_config = RateLimitConfig::default();
173
+ let client_with_limits = client.with_rate_limits(rate_config);
174
+
175
+ assert!(client_with_limits.rate_limit_config.is_some());
176
+ }
177
+ }