Context usage progress bar
This commit is contained in:
55
Cargo.lock
generated
55
Cargo.lock
generated
@@ -321,6 +321,19 @@ dependencies = [
|
||||
"yaml-rust2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "console"
|
||||
version = "0.15.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8"
|
||||
dependencies = [
|
||||
"encode_unicode",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"unicode-width",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-random"
|
||||
version = "0.1.18"
|
||||
@@ -488,6 +501,12 @@ version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||
|
||||
[[package]]
|
||||
name = "encode_unicode"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.35"
|
||||
@@ -685,6 +704,7 @@ dependencies = [
|
||||
"dirs 5.0.1",
|
||||
"g3-config",
|
||||
"g3-core",
|
||||
"indicatif",
|
||||
"rustyline",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -1053,6 +1073,19 @@ dependencies = [
|
||||
"hashbrown 0.15.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indicatif"
|
||||
version = "0.17.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235"
|
||||
dependencies = [
|
||||
"console",
|
||||
"number_prefix",
|
||||
"portable-atomic",
|
||||
"unicode-width",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "io-uring"
|
||||
version = "0.7.10"
|
||||
@@ -1335,6 +1368,12 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "number_prefix"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.36.7"
|
||||
@@ -1513,6 +1552,12 @@ version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
|
||||
|
||||
[[package]]
|
||||
name = "potential_utf"
|
||||
version = "0.1.3"
|
||||
@@ -2472,6 +2517,16 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-time"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "which"
|
||||
version = "4.4.2"
|
||||
|
||||
@@ -17,3 +17,4 @@ serde_json = { workspace = true }
|
||||
rustyline = "17.0.1"
|
||||
dirs = "5.0"
|
||||
tokio-util = "0.7"
|
||||
indicatif = "0.17"
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use clap::Parser;
|
||||
use g3_core::Agent;
|
||||
use g3_config::Config;
|
||||
use anyhow::Result;
|
||||
use tracing::{info, error};
|
||||
use clap::Parser;
|
||||
use g3_config::Config;
|
||||
use g3_core::Agent;
|
||||
use indicatif::{ProgressBar, ProgressStyle};
|
||||
use rustyline::error::ReadlineError;
|
||||
use rustyline::DefaultEditor;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing::{error, info};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "g3")]
|
||||
@@ -15,103 +16,107 @@ pub struct Cli {
|
||||
/// Enable verbose logging
|
||||
#[arg(short, long)]
|
||||
pub verbose: bool,
|
||||
|
||||
|
||||
/// Show the system prompt being sent to the LLM
|
||||
#[arg(long)]
|
||||
pub show_prompt: bool,
|
||||
|
||||
|
||||
/// Show the generated code before execution
|
||||
#[arg(long)]
|
||||
pub show_code: bool,
|
||||
|
||||
|
||||
/// Configuration file path
|
||||
#[arg(short, long)]
|
||||
pub config: Option<String>,
|
||||
|
||||
|
||||
/// Task to execute (if provided, runs in single-shot mode instead of interactive)
|
||||
pub task: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn run() -> Result<()> {
|
||||
let cli = Cli::parse();
|
||||
|
||||
|
||||
// Initialize logging
|
||||
let level = if cli.verbose {
|
||||
tracing::Level::DEBUG
|
||||
} else {
|
||||
tracing::Level::INFO
|
||||
};
|
||||
|
||||
tracing_subscriber::fmt()
|
||||
.with_max_level(level)
|
||||
.init();
|
||||
|
||||
|
||||
tracing_subscriber::fmt().with_max_level(level).init();
|
||||
|
||||
info!("Starting G3 AI Coding Agent");
|
||||
|
||||
|
||||
// Load configuration
|
||||
let config = Config::load(cli.config.as_deref())?;
|
||||
|
||||
|
||||
// Initialize agent
|
||||
let agent = Agent::new(config).await?;
|
||||
|
||||
let mut agent = Agent::new(config).await?;
|
||||
|
||||
// Execute task or start interactive mode
|
||||
if let Some(task) = cli.task {
|
||||
// Single-shot mode
|
||||
info!("Executing task: {}", task);
|
||||
let result = agent.execute_task_with_timing(&task, None, false, cli.show_prompt, cli.show_code, true).await?;
|
||||
let result = agent
|
||||
.execute_task_with_timing(&task, None, false, cli.show_prompt, cli.show_code, true)
|
||||
.await?;
|
||||
println!("{}", result);
|
||||
} else {
|
||||
// Interactive mode (default)
|
||||
info!("Starting interactive mode");
|
||||
run_interactive(agent, cli.show_prompt, cli.show_code).await?;
|
||||
}
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_interactive(agent: Agent, show_prompt: bool, show_code: bool) -> Result<()> {
|
||||
async fn run_interactive(mut agent: Agent, show_prompt: bool, show_code: bool) -> Result<()> {
|
||||
println!("🤖 G3 AI Coding Agent - Interactive Mode");
|
||||
println!("I solve problems by writing and executing 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!("Type 'exit' or 'quit' to exit, use Up/Down arrows for command history");
|
||||
println!("Press ESC during operations to cancel the current request");
|
||||
println!();
|
||||
|
||||
|
||||
// Initialize rustyline editor with history
|
||||
let mut rl = DefaultEditor::new()?;
|
||||
|
||||
|
||||
// Try to load history from a file in the user's home directory
|
||||
let history_file = dirs::home_dir()
|
||||
.map(|mut path| {
|
||||
path.push(".g3_history");
|
||||
path
|
||||
});
|
||||
|
||||
let history_file = dirs::home_dir().map(|mut path| {
|
||||
path.push(".g3_history");
|
||||
path
|
||||
});
|
||||
|
||||
if let Some(ref history_path) = history_file {
|
||||
let _ = rl.load_history(history_path);
|
||||
}
|
||||
|
||||
|
||||
loop {
|
||||
// Display context window progress bar before each prompt
|
||||
display_context_progress(&agent);
|
||||
|
||||
let readline = rl.readline("g3> ");
|
||||
match readline {
|
||||
Ok(line) => {
|
||||
let input = line.trim();
|
||||
|
||||
|
||||
if input == "exit" || input == "quit" {
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if input.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// Add to history
|
||||
rl.add_history_entry(input)?;
|
||||
|
||||
|
||||
// Create cancellation token for this request
|
||||
let cancellation_token = CancellationToken::new();
|
||||
let cancel_token_clone = cancellation_token.clone();
|
||||
|
||||
|
||||
// Spawn a task to monitor for ESC key during execution
|
||||
let esc_monitor = tokio::spawn(async move {
|
||||
// This is a simplified approach - in a real implementation,
|
||||
@@ -119,7 +124,7 @@ async fn run_interactive(agent: Agent, show_prompt: bool, show_code: bool) -> Re
|
||||
// For now, we'll just provide the cancellation infrastructure
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(3600)).await;
|
||||
});
|
||||
|
||||
|
||||
// Execute task with cancellation support
|
||||
let execution_result = tokio::select! {
|
||||
result = agent.execute_task_with_timing_cancellable(
|
||||
@@ -135,7 +140,7 @@ async fn run_interactive(agent: Agent, show_prompt: bool, show_code: bool) -> Re
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
match execution_result {
|
||||
Ok(response) => println!("{}", response),
|
||||
Err(e) => {
|
||||
@@ -146,27 +151,47 @@ async fn run_interactive(agent: Agent, show_prompt: bool, show_code: bool) -> Re
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
Err(ReadlineError::Interrupted) => {
|
||||
println!("CTRL-C");
|
||||
continue;
|
||||
},
|
||||
}
|
||||
Err(ReadlineError::Eof) => {
|
||||
println!("CTRL-D");
|
||||
break;
|
||||
},
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Error: {:?}", err);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Save history before exiting
|
||||
if let Some(ref history_path) = history_file {
|
||||
let _ = rl.save_history(history_path);
|
||||
}
|
||||
|
||||
|
||||
println!("👋 Goodbye!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn display_context_progress(agent: &Agent) {
|
||||
let context = agent.get_context_window();
|
||||
let percentage = context.percentage_used();
|
||||
|
||||
// Create a simple visual progress bar using the requested characters (10 dots max)
|
||||
let bar_width = 10;
|
||||
let filled_width = ((percentage / 100.0) * bar_width as f32) as usize;
|
||||
let empty_width = bar_width - filled_width;
|
||||
|
||||
let filled_chars = "●".repeat(filled_width);
|
||||
let empty_chars = "○".repeat(empty_width);
|
||||
let progress_bar = format!("{}{}", filled_chars, empty_chars);
|
||||
|
||||
// Print context info with visual progress bar
|
||||
println!(
|
||||
"Context: {} {:.1}% | {}/{} tokens",
|
||||
progress_bar, percentage, context.used_tokens, context.total_tokens
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,9 +9,51 @@ use tracing::field::debug;
|
||||
use tracing::info;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ContextWindow {
|
||||
pub used_tokens: u32,
|
||||
pub total_tokens: u32,
|
||||
pub conversation_history: Vec<Message>,
|
||||
}
|
||||
|
||||
impl ContextWindow {
|
||||
pub fn new(total_tokens: u32) -> Self {
|
||||
Self {
|
||||
used_tokens: 0,
|
||||
total_tokens,
|
||||
conversation_history: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_message(&mut self, message: Message) {
|
||||
// Simple token estimation: ~4 characters per token
|
||||
let estimated_tokens = (message.content.len() as f32 / 4.0).ceil() as u32;
|
||||
self.used_tokens += estimated_tokens;
|
||||
self.conversation_history.push(message);
|
||||
}
|
||||
|
||||
pub fn update_usage(&mut self, usage: &g3_providers::Usage) {
|
||||
// Update with actual token usage from the provider
|
||||
self.used_tokens = usage.total_tokens;
|
||||
}
|
||||
|
||||
pub fn percentage_used(&self) -> f32 {
|
||||
if self.total_tokens == 0 {
|
||||
0.0
|
||||
} else {
|
||||
(self.used_tokens as f32 / self.total_tokens as f32) * 100.0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remaining_tokens(&self) -> u32 {
|
||||
self.total_tokens.saturating_sub(self.used_tokens)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Agent {
|
||||
providers: ProviderRegistry,
|
||||
config: Config,
|
||||
context_window: ContextWindow,
|
||||
}
|
||||
|
||||
impl Agent {
|
||||
@@ -52,11 +94,18 @@ impl Agent {
|
||||
// Set default provider
|
||||
providers.set_default(&config.providers.default_provider)?;
|
||||
|
||||
Ok(Self { providers, config })
|
||||
// Initialize context window with configured max context length
|
||||
let context_window = ContextWindow::new(config.agent.max_context_length as u32);
|
||||
|
||||
Ok(Self {
|
||||
providers,
|
||||
config,
|
||||
context_window,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn execute_task(
|
||||
&self,
|
||||
&mut self,
|
||||
description: &str,
|
||||
language: Option<&str>,
|
||||
_auto_execute: bool,
|
||||
@@ -66,7 +115,7 @@ impl Agent {
|
||||
}
|
||||
|
||||
pub async fn execute_task_with_options(
|
||||
&self,
|
||||
&mut self,
|
||||
description: &str,
|
||||
language: Option<&str>,
|
||||
_auto_execute: bool,
|
||||
@@ -85,7 +134,7 @@ impl Agent {
|
||||
}
|
||||
|
||||
pub async fn execute_task_with_timing(
|
||||
&self,
|
||||
&mut self,
|
||||
description: &str,
|
||||
language: Option<&str>,
|
||||
_auto_execute: bool,
|
||||
@@ -108,7 +157,7 @@ impl Agent {
|
||||
}
|
||||
|
||||
pub async fn execute_task_with_timing_cancellable(
|
||||
&self,
|
||||
&mut self,
|
||||
description: &str,
|
||||
language: Option<&str>,
|
||||
_auto_execute: bool,
|
||||
@@ -161,16 +210,21 @@ with nothing afterwards.",
|
||||
println!();
|
||||
}
|
||||
|
||||
let messages = vec![
|
||||
Message {
|
||||
role: MessageRole::System,
|
||||
content: system_prompt,
|
||||
},
|
||||
Message {
|
||||
role: MessageRole::User,
|
||||
content: format!("Task: {}", description),
|
||||
},
|
||||
];
|
||||
// Add system message to context window
|
||||
let system_message = Message {
|
||||
role: MessageRole::System,
|
||||
content: system_prompt.clone(),
|
||||
};
|
||||
self.context_window.add_message(system_message.clone());
|
||||
|
||||
// Add user message to context window
|
||||
let user_message = Message {
|
||||
role: MessageRole::User,
|
||||
content: format!("Task: {}", description),
|
||||
};
|
||||
self.context_window.add_message(user_message.clone());
|
||||
|
||||
let messages = vec![system_message, user_message];
|
||||
|
||||
let request = CompletionRequest {
|
||||
messages,
|
||||
@@ -189,6 +243,16 @@ with nothing afterwards.",
|
||||
};
|
||||
let llm_duration = llm_start.elapsed();
|
||||
|
||||
// Update context window with actual token usage
|
||||
self.context_window.update_usage(&response.usage);
|
||||
|
||||
// Add assistant response to context window
|
||||
let assistant_message = Message {
|
||||
role: MessageRole::Assistant,
|
||||
content: response.content.clone(),
|
||||
};
|
||||
self.context_window.add_message(assistant_message);
|
||||
|
||||
// Time the code execution with cancellation support
|
||||
let exec_start = Instant::now();
|
||||
let executor = CodeExecutor::new();
|
||||
@@ -215,6 +279,10 @@ with nothing afterwards.",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_context_window(&self) -> &ContextWindow {
|
||||
&self.context_window
|
||||
}
|
||||
|
||||
fn format_duration(duration: Duration) -> String {
|
||||
let total_ms = duration.as_millis();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user