Files
g3/crates/g3-cli/src/task_execution.rs
Dhanji R. Prasanna cebec23075 Fix duplicate response printing in interactive mode
The response was being printed twice: once during streaming and again
after task completion. Removed the redundant print_smart() call since
streaming already displays the response in real-time.
2026-01-16 14:48:50 +05:30

136 lines
4.8 KiB
Rust

//! Task execution with retry logic for G3 CLI.
use g3_core::error_handling::{calculate_retry_delay, classify_error, ErrorType, RecoverableError};
use g3_core::ui_writer::UiWriter;
use g3_core::Agent;
use tokio_util::sync::CancellationToken;
use tracing::error;
use crate::simple_output::SimpleOutput;
/// Maximum number of retry attempts for recoverable errors
const MAX_RETRIES: u32 = 3;
/// Get a human-readable name for a recoverable error type.
fn recoverable_error_name(err: &RecoverableError) -> &'static str {
match err {
RecoverableError::RateLimit => "Rate limit",
RecoverableError::ServerError => "Server error",
RecoverableError::NetworkError => "Network error",
RecoverableError::Timeout => "Timeout",
RecoverableError::ModelBusy => "Model busy",
RecoverableError::TokenLimit => "Token limit",
RecoverableError::ContextLengthExceeded => "Context length",
}
}
/// Execute a task with retry logic for recoverable errors.
pub async fn execute_task_with_retry<W: UiWriter>(
agent: &mut Agent<W>,
input: &str,
show_prompt: bool,
show_code: bool,
output: &SimpleOutput,
) {
let mut attempt = 0;
output.print("🤔 Thinking...");
// Create cancellation token for this request
let cancellation_token = CancellationToken::new();
let cancel_token_clone = cancellation_token.clone();
loop {
attempt += 1;
// Execute task with cancellation support
let execution_result = tokio::select! {
result = agent.execute_task_with_timing_cancellable(
input, None, false, show_prompt, show_code, true, cancellation_token.clone(), None
) => {
result
}
_ = tokio::signal::ctrl_c() => {
cancel_token_clone.cancel();
output.print("\n⚠️ Operation cancelled by user (Ctrl+C)");
return;
}
};
match execution_result {
Ok(_) => {
if attempt > 1 {
output.print(&format!("✅ Request succeeded after {} attempts", attempt));
}
// Response was already displayed during streaming - don't print again
return;
}
Err(e) => {
if e.to_string().contains("cancelled") {
output.print("⚠️ Operation cancelled by user");
return;
}
// Check if this is a recoverable error that we should retry
let error_type = classify_error(&e);
if let ErrorType::Recoverable(recoverable_error) = error_type {
if attempt < MAX_RETRIES {
// Use shared retry delay calculation (non-autonomous mode)
let delay = calculate_retry_delay(attempt, false);
let delay_ms = delay.as_millis();
output.print(&format!(
"⚠️ {} detected (attempt {}/{}). Retrying in {:.1}s...",
recoverable_error_name(&recoverable_error),
attempt,
MAX_RETRIES,
delay_ms as f64 / 1000.0
));
// Wait before retrying
tokio::time::sleep(delay).await;
continue;
}
}
// For non-recoverable errors or after max retries
handle_execution_error(&e, input, output, attempt);
return;
}
}
}
}
/// Handle execution errors with detailed logging and user-friendly output.
pub fn handle_execution_error(e: &anyhow::Error, input: &str, output: &SimpleOutput, attempt: u32) {
// Enhanced error logging with detailed information
error!("=== TASK EXECUTION ERROR ===");
error!("Error: {}", e);
if attempt > 1 {
error!("Failed after {} attempts", attempt);
}
// Log error chain
let mut source = e.source();
let mut depth = 1;
while let Some(err) = source {
error!(" Caused by [{}]: {}", depth, err);
source = err.source();
depth += 1;
}
// Log additional context
error!("Task input: {}", input);
error!("Error type: {}", std::any::type_name_of_val(&e));
// Display user-friendly error message
output.print(&format!("❌ Error: {}", e));
// If it's a stream error, provide helpful guidance
if e.to_string().contains("No response received") || e.to_string().contains("timed out") {
output.print("💡 This may be a temporary issue. Please try again or check the logs for more details.");
output.print(" Log files are saved in the '.g3/sessions/' directory.");
}
}