This commit is contained in:
Dhanji Prasanna
2025-09-06 13:46:12 +10:00
parent 1834b8946c
commit 10970566b1
2 changed files with 26 additions and 514 deletions

View File

@@ -1,4 +1,4 @@
use clap::{Parser, Subcommand}; use clap::Parser;
use g3_core::Agent; use g3_core::Agent;
use g3_config::Config; use g3_config::Config;
use anyhow::Result; use anyhow::Result;
@@ -9,9 +9,6 @@ use tracing::{info, error};
#[command(about = "A modular, composable AI coding agent")] #[command(about = "A modular, composable AI coding agent")]
#[command(version)] #[command(version)]
pub struct Cli { pub struct Cli {
#[command(subcommand)]
pub command: Option<Commands>,
/// Enable verbose logging /// Enable verbose logging
#[arg(short, long)] #[arg(short, long)]
pub verbose: bool, pub verbose: bool,
@@ -27,79 +24,9 @@ pub struct Cli {
/// Configuration file path /// Configuration file path
#[arg(short, long)] #[arg(short, long)]
pub config: Option<String>, pub config: Option<String>,
}
#[derive(Subcommand)] /// Task to execute (if provided, runs in single-shot mode instead of interactive)
pub enum Commands { pub task: Option<String>,
/// Solve any task by writing and executing code
Task {
/// Description of the task to accomplish
description: String,
/// Programming language to prefer (auto-detect if not specified)
#[arg(short, long)]
language: Option<String>,
/// Execute the generated code automatically (default: ask for approval)
#[arg(short, long)]
execute: bool,
},
/// Create automation scripts for recurring tasks
Automate {
/// Description of the workflow to automate
workflow: String,
/// Output file for the automation script
#[arg(short, long)]
output: Option<String>,
},
/// Process and analyze data with code
Data {
/// Description of the data processing task
operation: String,
/// Input file or data source
#[arg(short, long)]
input: Option<String>,
/// Output format (json, csv, text)
#[arg(short = 'f', long, default_value = "text")]
format: String,
},
/// Web-related tasks (scraping, APIs, downloads)
Web {
/// Description of the web task
task: String,
/// Target URL (if applicable)
#[arg(short, long)]
url: Option<String>,
},
/// File system operations and management
File {
/// Description of the file operation
operation: String,
/// Target path
#[arg(short, long)]
path: Option<String>,
},
/// Legacy: Analyze code and provide insights
Analyze {
/// Path to analyze
path: String,
/// Output format (json, text)
#[arg(short, long, default_value = "text")]
format: String,
},
/// Legacy: Generate code based on description
Generate {
/// Description of what to generate
description: String,
/// Output file path
#[arg(short, long)]
output: Option<String>,
},
/// Legacy: Review code and suggest improvements
Review {
/// Path to review
path: String,
},
/// Interactive mode (default)
Interactive,
} }
pub async fn run() -> Result<()> { pub async fn run() -> Result<()> {
@@ -124,80 +51,24 @@ pub async fn run() -> Result<()> {
// Initialize agent // Initialize agent
let agent = Agent::new(config).await?; let agent = Agent::new(config).await?;
// Execute command - default to Interactive if no command provided // Execute task or start interactive mode
match cli.command.unwrap_or(Commands::Interactive) { if let Some(task) = cli.task {
Commands::Task { description, language, execute } => { // Single-shot mode
info!("Executing task: {}", description); info!("Executing task: {}", task);
let result = agent.execute_task(&description, language.as_deref(), execute).await?; let result = agent.execute_task_with_timing(&task, None, false, cli.show_prompt, cli.show_code, true).await?;
println!("{}", result); println!("{}", result);
} } else {
Commands::Automate { workflow, output } => { // Interactive mode (default)
info!("Creating automation: {}", workflow); info!("Starting interactive mode");
let result = agent.create_automation(&workflow).await?; run_interactive(agent, cli.show_prompt, cli.show_code).await?;
if let Some(output_path) = output {
std::fs::write(&output_path, &result)?;
println!("Automation script written to: {}", output_path);
} else {
println!("{}", result);
}
}
Commands::Data { operation, input, format } => {
info!("Processing data: {}", operation);
let result = agent.process_data(&operation, input.as_deref()).await?;
match format.as_str() {
"json" => println!("{}", serde_json::to_string_pretty(&result)?),
_ => println!("{}", result),
}
}
Commands::Web { task, url } => {
info!("Web task: {}", task);
let result = agent.execute_web_task(&task, url.as_deref()).await?;
println!("{}", result);
}
Commands::File { operation, path } => {
info!("File operation: {}", operation);
let result = agent.execute_file_operation(&operation, path.as_deref()).await?;
println!("{}", result);
}
Commands::Analyze { path, format } => {
info!("Analyzing: {}", path);
let result = agent.analyze(&path).await?;
match format.as_str() {
"json" => println!("{}", serde_json::to_string_pretty(&result)?),
_ => println!("{}", result),
}
}
Commands::Generate { description, output } => {
info!("Generating code: {}", description);
let result = agent.generate(&description).await?;
if let Some(output_path) = output {
std::fs::write(&output_path, &result)?;
println!("Generated code written to: {}", output_path);
} else {
println!("{}", result);
}
}
Commands::Review { path } => {
info!("Reviewing: {}", path);
let result = agent.review(&path).await?;
println!("{}", result);
}
Commands::Interactive => {
info!("Starting interactive mode");
run_interactive(agent, cli.show_prompt, cli.show_code).await?;
}
} }
Ok(()) Ok(())
} }
async fn run_interactive(agent: Agent, show_prompt: bool, show_code: bool) -> Result<()> { async fn run_interactive(agent: Agent, show_prompt: bool, show_code: bool) -> Result<()> {
println!("🤖 G3 General Purpose AI Agent - Interactive Mode"); println!("🤖 G3 AI Coding Agent - Interactive Mode");
println!("I solve problems by writing code. Tell me what you need to accomplish!"); println!("I solve problems by writing and executing code. Tell me what you need to accomplish!");
println!(); println!();
println!("Type 'exit' or 'quit' to exit"); println!("Type 'exit' or 'quit' to exit");
println!(); println!();
@@ -219,32 +90,7 @@ async fn run_interactive(agent: Agent, show_prompt: bool, show_code: bool) -> Re
continue; continue;
} }
// Handle legacy commands // Execute task (code-first approach)
if let Some(path) = input.strip_prefix("analyze ") {
match agent.analyze(path).await {
Ok(result) => println!("{}", result),
Err(e) => error!("Error analyzing {}: {}", path, e),
}
continue;
}
if let Some(description) = input.strip_prefix("generate ") {
match agent.generate(description).await {
Ok(result) => println!("{}", result),
Err(e) => error!("Error generating code: {}", e),
}
continue;
}
if let Some(path) = input.strip_prefix("review ") {
match agent.review(path).await {
Ok(result) => println!("{}", result),
Err(e) => error!("Error reviewing {}: {}", path, e),
}
continue;
}
// Default to task execution (code-first approach)
match agent.execute_task_with_timing(input, None, false, show_prompt, show_code, true).await { match agent.execute_task_with_timing(input, None, false, show_prompt, show_code, true).await {
Ok(response) => println!("{}", response), Ok(response) => println!("{}", response),
Err(e) => error!("Error: {}", e), Err(e) => error!("Error: {}", e),

View File

@@ -13,43 +13,6 @@ pub struct Agent {
config: Config, config: Config,
} }
#[derive(Debug, Serialize, Deserialize)]
pub struct AnalysisResult {
pub summary: String,
pub issues: Vec<Issue>,
pub suggestions: Vec<Suggestion>,
pub metrics: CodeMetrics,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Issue {
pub severity: IssueSeverity,
pub message: String,
pub line: Option<u32>,
pub column: Option<u32>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum IssueSeverity {
Error,
Warning,
Info,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Suggestion {
pub description: String,
pub code_example: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct CodeMetrics {
pub lines_of_code: u32,
pub complexity_score: f32,
pub maintainability_index: f32,
}
impl Agent { impl Agent {
pub async fn new(config: Config) -> Result<Self> { pub async fn new(config: Config) -> Result<Self> {
let mut providers = ProviderRegistry::new(); let mut providers = ProviderRegistry::new();
@@ -91,101 +54,6 @@ impl Agent {
Ok(Self { providers, config }) Ok(Self { providers, config })
} }
pub async fn analyze(&self, path: &str) -> Result<AnalysisResult> {
info!("Analyzing path: {}", path);
let content = self.read_file_or_directory(path)?;
let provider = self.providers.get(None)?;
let messages = vec![
Message {
role: MessageRole::System,
content: "You are a code analysis expert. Analyze the provided code and return a detailed analysis including issues, suggestions, and metrics.".to_string(),
},
Message {
role: MessageRole::User,
content: format!("Please analyze this code:\n\n{}", content),
},
];
let request = CompletionRequest {
messages,
max_tokens: Some(2048),
temperature: Some(0.1),
stream: false,
};
let response = provider.complete(request).await?;
// For now, return a simplified analysis
// In a real implementation, we'd parse the LLM response into structured data
Ok(AnalysisResult {
summary: response.content,
issues: vec![],
suggestions: vec![],
metrics: CodeMetrics {
lines_of_code: content.lines().count() as u32,
complexity_score: 1.0,
maintainability_index: 85.0,
},
})
}
pub async fn generate(&self, description: &str) -> Result<String> {
info!("Generating code for: {}", description);
let provider = self.providers.get(None)?;
let messages = vec![
Message {
role: MessageRole::System,
content: "You are a code generation expert. Generate clean, well-documented code based on the user's description.".to_string(),
},
Message {
role: MessageRole::User,
content: description.to_string(),
},
];
let request = CompletionRequest {
messages,
max_tokens: Some(2048),
temperature: Some(0.2),
stream: false,
};
let response = provider.complete(request).await?;
Ok(response.content)
}
pub async fn review(&self, path: &str) -> Result<String> {
info!("Reviewing path: {}", path);
let content = self.read_file_or_directory(path)?;
let provider = self.providers.get(None)?;
let messages = vec![
Message {
role: MessageRole::System,
content: "You are a code review expert. Review the provided code and suggest improvements focusing on best practices, performance, and maintainability.".to_string(),
},
Message {
role: MessageRole::User,
content: format!("Please review this code:\n\n{}", content),
},
];
let request = CompletionRequest {
messages,
max_tokens: Some(2048),
temperature: Some(0.1),
stream: false,
};
let response = provider.complete(request).await?;
Ok(response.content)
}
pub async fn execute_task( pub async fn execute_task(
&self, &self,
description: &str, description: &str,
@@ -240,19 +108,19 @@ When given a task:
4. Add error handling where appropriate 4. Add error handling where appropriate
5. EXECUTE the code immediately to solve the user's problem 5. EXECUTE the code immediately to solve the user's problem
Prefer these languages for different tasks (in order of preference): Prefer these languages:
- Bash/Shell: File operations, system administration, simple tasks, process management, text processing - Bash/Shell: File operations, system administration, simple tasks
- Python: Complex data processing, web scraping, APIs, when libraries are needed - Python: Complex data processing, when libraries are needed
- Rust: Performance-critical tasks, system programming - Rust: Performance-critical tasks, system programming
For simple tasks like listing files, checking processes, basic text manipulation, etc. - prefer bash/shell.
Only use Rust/Python when you need libraries or complex logic that bash can't handle easily. Only use Rust/Python when you need libraries or complex logic that bash can't handle easily.
Format your response as: Format your response as:
```[language] ```[language]
[code] [code]
``` ```
Then execute it and show the output.",
with nothing afterwards.",
if let Some(lang) = language { if let Some(lang) = language {
format!(" (prefer {})", lang) format!(" (prefer {})", lang)
} else { } else {
@@ -291,13 +159,6 @@ Then execute it and show the output.",
let response = provider.complete(request).await?; let response = provider.complete(request).await?;
let llm_duration = llm_start.elapsed(); let llm_duration = llm_start.elapsed();
// Log the LLM response before passing to CodeExecutor
// debug!(
// "LLM Response received ({} chars): {}\n=== END ===",
// response.content.len(),
// response.content
// );
// Time the code execution // Time the code execution
let exec_start = Instant::now(); let exec_start = Instant::now();
let executor = CodeExecutor::new(); let executor = CodeExecutor::new();
@@ -310,10 +171,10 @@ Then execute it and show the output.",
if show_timing { if show_timing {
let timing_summary = format!( let timing_summary = format!(
"\n⏱️ Task Summary:\n LLM call: {}\n Code execution: {}\n Total time: {}", "\n{} [💡: {} ⚡️: {}]",
Self::format_duration(total_duration),
Self::format_duration(llm_duration), Self::format_duration(llm_duration),
Self::format_duration(exec_duration), Self::format_duration(exec_duration)
Self::format_duration(total_duration)
); );
Ok(format!("{}\n{}", result, timing_summary)) Ok(format!("{}\n{}", result, timing_summary))
} else { } else {
@@ -335,205 +196,10 @@ Then execute it and show the output.",
format!("{}m {:.1}s", minutes, remaining_seconds) format!("{}m {:.1}s", minutes, remaining_seconds)
} }
} }
pub async fn create_automation(&self, workflow: &str) -> Result<String> {
info!("Creating automation for: {}", workflow);
let provider = self.providers.get(None)?;
let messages = vec![
Message {
role: MessageRole::System,
content: "You are G3, a code-first AI agent. Create automation scripts that can be saved and reused. Focus on creating robust, well-documented scripts with error handling and logging.".to_string(),
},
Message {
role: MessageRole::User,
content: format!("Create an automation script for: {}", workflow),
},
];
let request = CompletionRequest {
messages,
max_tokens: Some(2048),
temperature: Some(0.1),
stream: false,
};
let response = provider.complete(request).await?;
Ok(response.content)
}
pub async fn process_data(&self, operation: &str, input_file: Option<&str>) -> Result<String> {
info!("Processing data: {}", operation);
let provider = self.providers.get(None)?;
let context = if let Some(file) = input_file {
format!("Operation: {}\nInput file: {}", operation, file)
} else {
format!("Operation: {}", operation)
};
let messages = vec![
Message {
role: MessageRole::System,
content: "You are G3, a code-first AI agent specializing in data processing. Write Python code using pandas, numpy, or other appropriate libraries to process and analyze data. Always include data validation and error handling.".to_string(),
},
Message {
role: MessageRole::User,
content: context,
},
];
let request = CompletionRequest {
messages,
max_tokens: Some(2048),
temperature: Some(0.1),
stream: false,
};
let response = provider.complete(request).await?;
Ok(response.content)
}
pub async fn execute_web_task(&self, task: &str, url: Option<&str>) -> Result<String> {
info!("Executing web task: {}", task);
let provider = self.providers.get(None)?;
let context = if let Some(url) = url {
format!("Task: {}\nURL: {}", task, url)
} else {
format!("Task: {}", task)
};
let messages = vec![
Message {
role: MessageRole::System,
content: "You are G3, a code-first AI agent for web tasks. Write code for web scraping, API calls, downloads, or web automation. Use appropriate libraries like requests, BeautifulSoup, selenium, or similar. Always respect robots.txt and rate limits.".to_string(),
},
Message {
role: MessageRole::User,
content: context,
},
];
let request = CompletionRequest {
messages,
max_tokens: Some(2048),
temperature: Some(0.2),
stream: false,
};
let response = provider.complete(request).await?;
Ok(response.content)
}
pub async fn execute_file_operation(
&self,
operation: &str,
path: Option<&str>,
) -> Result<String> {
info!("Executing file operation: {}", operation);
let provider = self.providers.get(None)?;
let context = if let Some(path) = path {
format!("Operation: {}\nPath: {}", operation, path)
} else {
format!("Operation: {}", operation)
};
let messages = vec![
Message {
role: MessageRole::System,
content: "You are G3, a code-first AI agent for file operations. Write scripts (bash, Python, or other appropriate languages) for file management, organization, backup, compression, format conversion, etc. Always include safety checks and confirmation prompts for destructive operations.".to_string(),
},
Message {
role: MessageRole::User,
content: context,
},
];
let request = CompletionRequest {
messages,
max_tokens: Some(2048),
temperature: Some(0.1),
stream: false,
};
let response = provider.complete(request).await?;
Ok(response.content)
}
fn read_file_or_directory(&self, path: &str) -> Result<String> {
let path = Path::new(path);
if path.is_file() {
Ok(std::fs::read_to_string(path)?)
} else if path.is_dir() {
// For directories, read multiple files and combine them
let mut content = String::new();
self.read_directory_recursive(path, &mut content)?;
Ok(content)
} else {
anyhow::bail!("Path does not exist: {}", path.display())
}
}
fn read_directory_recursive(&self, dir: &Path, content: &mut String) -> Result<()> {
for entry in std::fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.is_file() {
if let Some(ext) = path.extension() {
// Only read common code files
if matches!(
ext.to_str(),
Some("rs" | "py" | "js" | "ts" | "go" | "java" | "cpp" | "c" | "h")
) {
content.push_str(&format!("\n--- {} ---\n", path.display()));
if let Ok(file_content) = std::fs::read_to_string(&path) {
content.push_str(&file_content);
}
}
}
} else if path.is_dir() && !path.file_name().unwrap().to_str().unwrap().starts_with('.')
{
self.read_directory_recursive(&path, content)?;
}
}
Ok(())
}
}
impl std::fmt::Display for AnalysisResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Code Analysis Results")?;
writeln!(f, "====================")?;
writeln!(f)?;
writeln!(f, "Summary:")?;
writeln!(f, "{}", self.summary)?;
writeln!(f)?;
writeln!(f, "Metrics:")?;
writeln!(f, "- Lines of Code: {}", self.metrics.lines_of_code)?;
writeln!(
f,
"- Complexity Score: {:.2}",
self.metrics.complexity_score
)?;
writeln!(
f,
"- Maintainability Index: {:.2}",
self.metrics.maintainability_index
)?;
Ok(())
}
} }
pub mod providers { pub mod providers {
pub mod anthropic; pub mod anthropic;
pub mod openai;
pub mod embedded; pub mod embedded;
pub mod openai;
} }