From dd20e0bb010f5aa0f9b381eb8129192cabb60819 Mon Sep 17 00:00:00 2001 From: Dhanji Prasanna Date: Mon, 22 Sep 2025 20:38:44 +1000 Subject: [PATCH] some cleanup of converstation mgmt --- crates/g3-cli/src/lib.rs | 6 ++- crates/g3-core/src/lib.rs | 31 ++++++++------ crates/g3-core/src/providers/embedded.rs | 9 +--- crates/g3-providers/src/anthropic.rs | 52 +----------------------- 4 files changed, 27 insertions(+), 71 deletions(-) diff --git a/crates/g3-cli/src/lib.rs b/crates/g3-cli/src/lib.rs index 4c81913..26d85b8 100644 --- a/crates/g3-cli/src/lib.rs +++ b/crates/g3-cli/src/lib.rs @@ -2,9 +2,9 @@ use anyhow::Result; use clap::Parser; use g3_config::Config; use g3_core::Agent; -use indicatif::{ProgressBar, ProgressStyle}; use rustyline::error::ReadlineError; use rustyline::DefaultEditor; +use std::io::Write; use tokio_util::sync::CancellationToken; use tracing::{error, info}; @@ -142,6 +142,10 @@ async fn run_interactive(mut agent: Agent, show_prompt: bool, show_code: bool) - // Add to history rl.add_history_entry(input)?; + // Show thinking indicator immediately + print!("šŸ¤” Thinking..."); + std::io::stdout().flush()?; + // Create cancellation token for this request let cancellation_token = CancellationToken::new(); let cancel_token_clone = cancellation_token.clone(); diff --git a/crates/g3-core/src/lib.rs b/crates/g3-core/src/lib.rs index 74538be..8635260 100644 --- a/crates/g3-core/src/lib.rs +++ b/crates/g3-core/src/lib.rs @@ -250,7 +250,6 @@ impl ContextWindow { pub struct Agent { providers: ProviderRegistry, - config: Config, context_window: ContextWindow, session_id: Option, } @@ -311,7 +310,6 @@ impl Agent { Ok(Self { providers, - config, context_window, session_id: None, }) @@ -838,9 +836,7 @@ The tool will execute immediately and you'll receive the result (success or erro let mut total_execution_time = Duration::new(0, 0); let mut iteration_count = 0; const MAX_ITERATIONS: usize = 10; // Prevent infinite loops - - print!("šŸ¤– "); // Start the response indicator - io::stdout().flush()?; + let mut response_started = false; loop { iteration_count += 1; @@ -953,6 +949,11 @@ The tool will execute immediately and you'll receive the result (success or erro // Only print if there's actually new content to show if !new_content.trim().is_empty() { + // Replace thinking indicator with response indicator if not already done + if !response_started { + print!("\ršŸ¤– "); // Clear thinking indicator and show response indicator + response_started = true; + } print!("{}", new_content); io::stdout().flush()?; } @@ -1050,10 +1051,10 @@ The tool will execute immediately and you'll receive the result (success or erro } full_response.push_str(final_display_content); - full_response.push_str(&format!( - "\n\nTool executed: {} -> {}\n\n", - tool_call.tool, tool_result - )); + // full_response.push_str(&format!( + // "\n\nTool executed: {} -> {}\n\n", + // tool_call.tool, tool_result + // )); tool_executed = true; // Break out of current stream to start a new one with updated context @@ -1069,6 +1070,12 @@ The tool will execute immediately and you'll receive the result (success or erro .replace("<>", ""); if !clean_content.is_empty() { + // Replace thinking indicator with response indicator on first content + if !response_started { + print!("\ršŸ¤– "); // Clear thinking indicator and show response indicator + response_started = true; + } + debug!("Printing clean content: '{}'", clean_content); print!("{}", clean_content); let _ = io::stdout().flush(); // Force immediate output @@ -1317,8 +1324,8 @@ fn fix_nested_quotes_in_shell_command(json_str: &str) -> String { } '\\' => { // Check what follows the backslash - if let Some(&next_ch) = chars.peek() { - if next_ch == '"' { + if let Some(&_next_ch) = chars.peek() { + if _next_ch == '"' { // This is an escaped quote, keep the backslash fixed_command.push(ch); } else { @@ -1382,7 +1389,7 @@ fn fix_mixed_quotes_in_json(json_str: &str) -> String { '\\' if in_string => { // Escape sequence - preserve it result.push(ch); - if let Some(&next_ch) = chars.peek() { + if let Some(&_next_ch) = chars.peek() { result.push(chars.next().unwrap()); } } diff --git a/crates/g3-core/src/providers/embedded.rs b/crates/g3-core/src/providers/embedded.rs index a45611d..c0d83aa 100644 --- a/crates/g3-core/src/providers/embedded.rs +++ b/crates/g3-core/src/providers/embedded.rs @@ -8,22 +8,18 @@ use llama_cpp::{ LlamaModel, LlamaParams, LlamaSession, SessionParams, }; use std::path::{Path, PathBuf}; -use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; use std::sync::Arc; -use std::time::Duration; use tokio::sync::mpsc; use tokio::sync::Mutex; use tokio_stream::wrappers::ReceiverStream; -use tracing::{debug, error, info, warn}; +use tracing::{debug, error, info}; pub struct EmbeddedProvider { - model: Arc, session: Arc>, model_name: String, max_tokens: u32, temperature: f32, context_length: u32, - generation_active: Arc, } impl EmbeddedProvider { @@ -84,13 +80,11 @@ impl EmbeddedProvider { info!("Successfully loaded {} model", model_type); Ok(Self { - model: Arc::new(model), session: Arc::new(Mutex::new(session)), model_name: format!("embedded-{}", model_type), max_tokens: max_tokens.unwrap_or(2048), temperature: temperature.unwrap_or(0.1), context_length: context_size, - generation_active: Arc::new(AtomicBool::new(false)), }) } @@ -429,7 +423,6 @@ impl EmbeddedProvider { // Download the Qwen 2.5 7B model if it doesn't exist fn download_qwen_model(model_path: &Path) -> Result<()> { use std::fs; - use std::io::Write; use std::process::Command; const MODEL_URL: &str = "https://huggingface.co/Qwen/Qwen2.5-7B-Instruct-GGUF/resolve/main/qwen2.5-7b-instruct-q3_k_m.gguf"; diff --git a/crates/g3-providers/src/anthropic.rs b/crates/g3-providers/src/anthropic.rs index b1b5b75..1d3e18d 100644 --- a/crates/g3-providers/src/anthropic.rs +++ b/crates/g3-providers/src/anthropic.rs @@ -194,20 +194,6 @@ impl AnthropicProvider { .collect() } - fn convert_anthropic_tool_calls(&self, content: &[AnthropicContent]) -> Vec { - content - .iter() - .filter_map(|c| match c { - AnthropicContent::ToolUse { id, name, input } => Some(ToolCall { - id: id.clone(), - tool: name.clone(), - args: input.clone(), - }), - _ => None, - }) - .collect() - } - fn convert_messages(&self, messages: &[Message]) -> Result<(Option, Vec)> { let mut system_message = None; let mut anthropic_messages = Vec::new(); @@ -668,14 +654,8 @@ enum AnthropicContent { #[derive(Debug, Deserialize)] struct AnthropicResponse { - id: String, - #[serde(rename = "type")] - response_type: String, - role: String, content: Vec, model: String, - stop_reason: Option, - stop_sequence: Option, usage: AnthropicUsage, } @@ -701,8 +681,6 @@ struct AnthropicStreamEvent { #[derive(Debug, Deserialize)] struct AnthropicDelta { - #[serde(rename = "type")] - delta_type: Option, text: Option, partial_json: Option, } @@ -710,7 +688,9 @@ struct AnthropicDelta { #[derive(Debug, Deserialize)] struct AnthropicError { #[serde(rename = "type")] + #[allow(dead_code)] error_type: String, + #[allow(dead_code)] message: String, } @@ -813,32 +793,4 @@ mod tests { assert!(anthropic_tools[0].input_schema.required.is_some()); assert_eq!(anthropic_tools[0].input_schema.required.as_ref().unwrap()[0], "location"); } - - #[test] - fn test_tool_call_conversion() { - let provider = AnthropicProvider::new( - "test-key".to_string(), - None, - None, - None, - ).unwrap(); - - let content = vec![ - AnthropicContent::Text { - text: "I'll help you get the weather.".to_string(), - }, - AnthropicContent::ToolUse { - id: "toolu_123".to_string(), - name: "get_weather".to_string(), - input: serde_json::json!({"location": "San Francisco, CA"}), - }, - ]; - - let tool_calls = provider.convert_anthropic_tool_calls(&content); - - assert_eq!(tool_calls.len(), 1); - assert_eq!(tool_calls[0].id, "toolu_123"); - assert_eq!(tool_calls[0].tool, "get_weather"); - assert_eq!(tool_calls[0].args["location"], "San Francisco, CA"); - } }