Refactor: improve readability in CLI modules
- project_files.rs: Fix UTF-8 safety in truncate_for_display (use char boundaries instead of byte slicing), add test for multi-byte chars - task_execution.rs: Extract recoverable_error_name() helper, use shared calculate_retry_delay() from error_handling.rs to eliminate duplication - ui_writer_impl.rs: Extract duration_color() helper for timing display, add clear_tool_state() to consolidate repeated mutex clearing patterns Agent: carmack
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
//! Task execution with retry logic for G3 CLI.
|
||||
|
||||
use g3_core::error_handling::{classify_error, ErrorType, RecoverableError};
|
||||
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;
|
||||
@@ -11,6 +11,19 @@ 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>,
|
||||
@@ -66,35 +79,16 @@ pub async fn execute_task_with_retry<W: UiWriter>(
|
||||
|
||||
if let ErrorType::Recoverable(recoverable_error) = error_type {
|
||||
if attempt < MAX_RETRIES {
|
||||
// Calculate retry delay with exponential backoff + jitter
|
||||
let base_delay_ms = match recoverable_error {
|
||||
RecoverableError::RateLimit => 5000, // Rate limits need longer waits
|
||||
RecoverableError::ServerError => 2000,
|
||||
RecoverableError::NetworkError => 1000,
|
||||
RecoverableError::Timeout => 1000,
|
||||
RecoverableError::ModelBusy => 3000,
|
||||
RecoverableError::TokenLimit => 1000,
|
||||
RecoverableError::ContextLengthExceeded => 1000,
|
||||
};
|
||||
let delay_ms = base_delay_ms * (2_u64.pow(attempt - 1));
|
||||
// Add jitter (±20%)
|
||||
let jitter = (delay_ms as f64 * 0.2 * (rand::random::<f64>() - 0.5)) as i64;
|
||||
let delay_ms = (delay_ms as i64 + jitter).max(100) as u64;
|
||||
let delay = std::time::Duration::from_millis(delay_ms);
|
||||
|
||||
let error_name = match recoverable_error {
|
||||
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",
|
||||
};
|
||||
// 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...",
|
||||
error_name, attempt, MAX_RETRIES, delay_ms as f64 / 1000.0
|
||||
recoverable_error_name(&recoverable_error),
|
||||
attempt,
|
||||
MAX_RETRIES,
|
||||
delay_ms as f64 / 1000.0
|
||||
));
|
||||
|
||||
// Wait before retrying
|
||||
|
||||
Reference in New Issue
Block a user