agent: Show a notice when reaching consecutive tool use limits (#29833)

This PR adds a notice when reaching consecutive tool use limits when
using normal mode.

Here's an example with the limit artificially lowered to 2 consecutive
tool uses:


https://github.com/user-attachments/assets/32da8d38-67de-4d6b-8f24-754d2518e5d4

Release Notes:

- agent: Added a notice when reaching consecutive tool use limits when
using a model in normal mode.
This commit is contained in:
Marshall Bowers 2025-05-02 22:09:54 -04:00 committed by GitHub
parent 10a7f2a972
commit f0515d1c34
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 134 additions and 25 deletions

View file

@ -1957,6 +1957,41 @@ impl AssistantPanel {
Some(UsageBanner::new(plan, usage).into_any_element())
}
fn render_tool_use_limit_reached(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
let tool_use_limit_reached = self
.thread
.read(cx)
.thread()
.read(cx)
.tool_use_limit_reached();
if !tool_use_limit_reached {
return None;
}
let model = self
.thread
.read(cx)
.thread()
.read(cx)
.configured_model()?
.model;
let max_mode_upsell = if model.supports_max_mode() {
" Enable max mode for unlimited tool use."
} else {
""
};
Some(
Banner::new()
.severity(ui::Severity::Info)
.children(h_flex().child(Label::new(format!(
"Consecutive tool use limit reached.{max_mode_upsell}"
))))
.into_any_element(),
)
}
fn render_last_error(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
let last_error = self.thread.read(cx).last_error()?;
@ -2238,6 +2273,7 @@ impl Render for AssistantPanel {
.map(|parent| match &self.active_view {
ActiveView::Thread { .. } => parent
.child(self.render_active_thread_or_empty_state(window, cx))
.children(self.render_tool_use_limit_reached(cx))
.children(self.render_usage_banner(cx))
.child(h_flex().child(self.message_editor.clone()))
.children(self.render_last_error(cx)),

View file

@ -355,6 +355,7 @@ pub struct Thread {
request_token_usage: Vec<TokenUsage>,
cumulative_token_usage: TokenUsage,
exceeded_window_error: Option<ExceededWindowError>,
tool_use_limit_reached: bool,
feedback: Option<ThreadFeedback>,
message_feedback: HashMap<MessageId, ThreadFeedback>,
last_auto_capture_at: Option<Instant>,
@ -417,6 +418,7 @@ impl Thread {
request_token_usage: Vec::new(),
cumulative_token_usage: TokenUsage::default(),
exceeded_window_error: None,
tool_use_limit_reached: false,
feedback: None,
message_feedback: HashMap::default(),
last_auto_capture_at: None,
@ -524,6 +526,7 @@ impl Thread {
request_token_usage: serialized.request_token_usage,
cumulative_token_usage: serialized.cumulative_token_usage,
exceeded_window_error: None,
tool_use_limit_reached: false,
feedback: None,
message_feedback: HashMap::default(),
last_auto_capture_at: None,
@ -814,6 +817,10 @@ impl Thread {
.unwrap_or(false)
}
pub fn tool_use_limit_reached(&self) -> bool {
self.tool_use_limit_reached
}
/// Returns whether all of the tool uses have finished running.
pub fn all_tools_finished(&self) -> bool {
// If the only pending tool uses left are the ones with errors, then
@ -1331,6 +1338,8 @@ impl Thread {
window: Option<AnyWindowHandle>,
cx: &mut Context<Self>,
) {
self.tool_use_limit_reached = false;
let pending_completion_id = post_inc(&mut self.completion_count);
let mut request_callback_parameters = if self.request_callback.is_some() {
Some((request.clone(), Vec::new()))
@ -1506,17 +1515,27 @@ impl Thread {
});
}
}
LanguageModelCompletionEvent::QueueUpdate(queue_event) => {
LanguageModelCompletionEvent::QueueUpdate(status) => {
if let Some(completion) = thread
.pending_completions
.iter_mut()
.find(|completion| completion.id == pending_completion_id)
{
completion.queue_state = match queue_event {
language_model::QueueState::Queued { position } => {
QueueState::Queued { position }
let queue_state = match status {
language_model::CompletionRequestStatus::Queued {
position,
} => Some(QueueState::Queued { position }),
language_model::CompletionRequestStatus::Started => {
Some(QueueState::Started)
}
language_model::QueueState::Started => QueueState::Started,
language_model::CompletionRequestStatus::ToolUseLimitReached => {
thread.tool_use_limit_reached = true;
None
}
};
if let Some(queue_state) = queue_state {
completion.queue_state = queue_state;
}
}
}