agent2: Port retry logic (#36421)

Release Notes:

- N/A
This commit is contained in:
Bennet Bo Fenner 2025-08-19 11:41:55 +02:00 committed by GitHub
parent 47e1d4511c
commit 0ea0d466d2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 514 additions and 52 deletions

View file

@ -1,7 +1,7 @@
use acp_thread::{
AcpThread, AcpThreadEvent, AgentThreadEntry, AssistantMessage, AssistantMessageChunk,
AuthRequired, LoadError, MentionUri, ThreadStatus, ToolCall, ToolCallContent, ToolCallStatus,
UserMessageId,
AuthRequired, LoadError, MentionUri, RetryStatus, ThreadStatus, ToolCall, ToolCallContent,
ToolCallStatus, UserMessageId,
};
use acp_thread::{AgentConnection, Plan};
use action_log::ActionLog;
@ -35,6 +35,7 @@ use prompt_store::PromptId;
use rope::Point;
use settings::{Settings as _, SettingsStore};
use std::sync::Arc;
use std::time::Instant;
use std::{collections::BTreeMap, process::ExitStatus, rc::Rc, time::Duration};
use text::Anchor;
use theme::ThemeSettings;
@ -115,6 +116,7 @@ pub struct AcpThreadView {
profile_selector: Option<Entity<ProfileSelector>>,
notifications: Vec<WindowHandle<AgentNotification>>,
notification_subscriptions: HashMap<WindowHandle<AgentNotification>, Vec<Subscription>>,
thread_retry_status: Option<RetryStatus>,
thread_error: Option<ThreadError>,
list_state: ListState,
scrollbar_state: ScrollbarState,
@ -209,6 +211,7 @@ impl AcpThreadView {
notification_subscriptions: HashMap::default(),
list_state: list_state.clone(),
scrollbar_state: ScrollbarState::new(list_state).parent_entity(&cx.entity()),
thread_retry_status: None,
thread_error: None,
auth_task: None,
expanded_tool_calls: HashSet::default(),
@ -445,6 +448,7 @@ impl AcpThreadView {
pub fn cancel_generation(&mut self, cx: &mut Context<Self>) {
self.thread_error.take();
self.thread_retry_status.take();
if let Some(thread) = self.thread() {
self._cancel_task = Some(thread.update(cx, |thread, cx| thread.cancel(cx)));
@ -775,7 +779,11 @@ impl AcpThreadView {
AcpThreadEvent::ToolAuthorizationRequired => {
self.notify_with_sound("Waiting for tool confirmation", IconName::Info, window, cx);
}
AcpThreadEvent::Retry(retry) => {
self.thread_retry_status = Some(retry.clone());
}
AcpThreadEvent::Stopped => {
self.thread_retry_status.take();
let used_tools = thread.read(cx).used_tools_since_last_user_message();
self.notify_with_sound(
if used_tools {
@ -789,6 +797,7 @@ impl AcpThreadView {
);
}
AcpThreadEvent::Error => {
self.thread_retry_status.take();
self.notify_with_sound(
"Agent stopped due to an error",
IconName::Warning,
@ -797,6 +806,7 @@ impl AcpThreadView {
);
}
AcpThreadEvent::ServerExited(status) => {
self.thread_retry_status.take();
self.thread_state = ThreadState::ServerExited { status: *status };
}
}
@ -3413,7 +3423,51 @@ impl AcpThreadView {
})
}
fn render_thread_error(&self, window: &mut Window, cx: &mut Context<'_, Self>) -> Option<Div> {
fn render_thread_retry_status_callout(
&self,
_window: &mut Window,
_cx: &mut Context<Self>,
) -> Option<Callout> {
let state = self.thread_retry_status.as_ref()?;
let next_attempt_in = state
.duration
.saturating_sub(Instant::now().saturating_duration_since(state.started_at));
if next_attempt_in.is_zero() {
return None;
}
let next_attempt_in_secs = next_attempt_in.as_secs() + 1;
let retry_message = if state.max_attempts == 1 {
if next_attempt_in_secs == 1 {
"Retrying. Next attempt in 1 second.".to_string()
} else {
format!("Retrying. Next attempt in {next_attempt_in_secs} seconds.")
}
} else {
if next_attempt_in_secs == 1 {
format!(
"Retrying. Next attempt in 1 second (Attempt {} of {}).",
state.attempt, state.max_attempts,
)
} else {
format!(
"Retrying. Next attempt in {next_attempt_in_secs} seconds (Attempt {} of {}).",
state.attempt, state.max_attempts,
)
}
};
Some(
Callout::new()
.severity(Severity::Warning)
.title(state.last_error.clone())
.description(retry_message),
)
}
fn render_thread_error(&self, window: &mut Window, cx: &mut Context<Self>) -> Option<Div> {
let content = match self.thread_error.as_ref()? {
ThreadError::Other(error) => self.render_any_thread_error(error.clone(), cx),
ThreadError::PaymentRequired => self.render_payment_required_error(cx),
@ -3678,6 +3732,7 @@ impl Render for AcpThreadView {
}
_ => this,
})
.children(self.render_thread_retry_status_callout(window, cx))
.children(self.render_thread_error(window, cx))
.child(self.render_message_editor(window, cx))
}

View file

@ -1523,6 +1523,7 @@ impl AgentDiff {
AcpThreadEvent::EntriesRemoved(_)
| AcpThreadEvent::Stopped
| AcpThreadEvent::ToolAuthorizationRequired
| AcpThreadEvent::Retry(_)
| AcpThreadEvent::Error
| AcpThreadEvent::ServerExited(_) => {}
}