diff --git a/assets/icons/zed_burn_mode.svg b/assets/icons/zed_burn_mode.svg
new file mode 100644
index 0000000000..544368d8e0
--- /dev/null
+++ b/assets/icons/zed_burn_mode.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/icons/zed_burn_mode_on.svg b/assets/icons/zed_burn_mode_on.svg
new file mode 100644
index 0000000000..94230b6fd6
--- /dev/null
+++ b/assets/icons/zed_burn_mode_on.svg
@@ -0,0 +1,13 @@
+
diff --git a/assets/icons/zed_max_mode.svg b/assets/icons/zed_max_mode.svg
deleted file mode 100644
index 969785a83f..0000000000
--- a/assets/icons/zed_max_mode.svg
+++ /dev/null
@@ -1,14 +0,0 @@
-
diff --git a/assets/keymaps/default-linux.json b/assets/keymaps/default-linux.json
index eab1f72ff1..243406277e 100644
--- a/assets/keymaps/default-linux.json
+++ b/assets/keymaps/default-linux.json
@@ -248,7 +248,9 @@
"ctrl-shift-i": "agent::ToggleOptionsMenu",
"shift-alt-escape": "agent::ExpandMessageEditor",
"ctrl-alt-e": "agent::RemoveAllContext",
- "ctrl-shift-e": "project_panel::ToggleFocus"
+ "ctrl-shift-e": "project_panel::ToggleFocus",
+ "ctrl-shift-enter": "agent::ContinueThread",
+ "alt-enter": "agent::ContinueWithBurnMode"
}
},
{
diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json
index 570be05a31..5afb6e97c4 100644
--- a/assets/keymaps/default-macos.json
+++ b/assets/keymaps/default-macos.json
@@ -283,7 +283,9 @@
"cmd-shift-i": "agent::ToggleOptionsMenu",
"shift-alt-escape": "agent::ExpandMessageEditor",
"cmd-alt-e": "agent::RemoveAllContext",
- "cmd-shift-e": "project_panel::ToggleFocus"
+ "cmd-shift-e": "project_panel::ToggleFocus",
+ "cmd-shift-enter": "agent::ContinueThread",
+ "alt-enter": "agent::ContinueWithBurnMode"
}
},
{
diff --git a/crates/agent/src/active_thread.rs b/crates/agent/src/active_thread.rs
index 6dbbd2b69f..46f924c153 100644
--- a/crates/agent/src/active_thread.rs
+++ b/crates/agent/src/active_thread.rs
@@ -1778,6 +1778,11 @@ impl ActiveThread {
let Some(message) = self.thread.read(cx).message(message_id) else {
return Empty.into_any();
};
+
+ if message.is_hidden {
+ return Empty.into_any();
+ }
+
let message_creases = message.creases.clone();
let Some(rendered_message) = self.rendered_messages_by_id.get(&message_id) else {
diff --git a/crates/agent/src/agent.rs b/crates/agent/src/agent.rs
index b4d1abdea4..f5ae6097a5 100644
--- a/crates/agent/src/agent.rs
+++ b/crates/agent/src/agent.rs
@@ -87,6 +87,8 @@ actions!(
Follow,
ResetTrialUpsell,
ResetTrialEndUpsell,
+ ContinueThread,
+ ContinueWithBurnMode,
]
);
diff --git a/crates/agent/src/agent_panel.rs b/crates/agent/src/agent_panel.rs
index 0d59ad9595..324f98c2fd 100644
--- a/crates/agent/src/agent_panel.rs
+++ b/crates/agent/src/agent_panel.rs
@@ -7,7 +7,7 @@ use std::time::Duration;
use db::kvp::{Dismissable, KEY_VALUE_STORE};
use serde::{Deserialize, Serialize};
-use agent_settings::{AgentDockPosition, AgentSettings, DefaultView};
+use agent_settings::{AgentDockPosition, AgentSettings, CompletionMode, DefaultView};
use anyhow::{Result, anyhow};
use assistant_context_editor::{
AgentPanelDelegate, AssistantContext, ConfigurationError, ContextEditor, ContextEvent,
@@ -41,8 +41,8 @@ use theme::ThemeSettings;
use time::UtcOffset;
use ui::utils::WithRemSize;
use ui::{
- Banner, CheckboxWithLabel, ContextMenu, KeyBinding, PopoverMenu, PopoverMenuHandle,
- ProgressBar, Tab, Tooltip, Vector, VectorName, prelude::*,
+ Banner, CheckboxWithLabel, ContextMenu, ElevationIndex, KeyBinding, PopoverMenu,
+ PopoverMenuHandle, ProgressBar, Tab, Tooltip, Vector, VectorName, prelude::*,
};
use util::{ResultExt as _, maybe};
use workspace::dock::{DockPosition, Panel, PanelEvent};
@@ -64,10 +64,11 @@ use crate::thread_history::{HistoryEntryElement, ThreadHistory};
use crate::thread_store::ThreadStore;
use crate::ui::AgentOnboardingModal;
use crate::{
- AddContextServer, AgentDiffPane, ContextStore, DeleteRecentlyOpenThread, ExpandMessageEditor,
- Follow, InlineAssistant, NewTextThread, NewThread, OpenActiveThreadAsMarkdown, OpenAgentDiff,
- OpenHistory, ResetTrialEndUpsell, ResetTrialUpsell, TextThreadStore, ThreadEvent,
- ToggleContextPicker, ToggleNavigationMenu, ToggleOptionsMenu,
+ AddContextServer, AgentDiffPane, ContextStore, ContinueThread, ContinueWithBurnMode,
+ DeleteRecentlyOpenThread, ExpandMessageEditor, Follow, InlineAssistant, NewTextThread,
+ NewThread, OpenActiveThreadAsMarkdown, OpenAgentDiff, OpenHistory, ResetTrialEndUpsell,
+ ResetTrialUpsell, TextThreadStore, ThreadEvent, ToggleContextPicker, ToggleNavigationMenu,
+ ToggleOptionsMenu,
};
const AGENT_PANEL_KEY: &str = "agent_panel";
@@ -1283,6 +1284,26 @@ impl AgentPanel {
matches!(self.active_view, ActiveView::Thread { .. })
}
+ fn continue_conversation(&mut self, window: &mut Window, cx: &mut Context) {
+ let thread_state = self.thread.read(cx).thread().read(cx);
+ if !thread_state.tool_use_limit_reached() {
+ return;
+ }
+
+ let model = thread_state.configured_model().map(|cm| cm.model.clone());
+ if let Some(model) = model {
+ self.thread.update(cx, |active_thread, cx| {
+ active_thread.thread().update(cx, |thread, cx| {
+ thread.insert_invisible_continue_message(cx);
+ thread.advance_prompt_id();
+ thread.send_to_model(model, Some(window.window_handle()), cx);
+ });
+ });
+ } else {
+ log::warn!("No configured model available for continuation");
+ }
+ }
+
pub(crate) fn active_context_editor(&self) -> Option> {
match &self.active_view {
ActiveView::PromptEditor { context_editor, .. } => Some(context_editor.clone()),
@@ -2574,7 +2595,11 @@ impl AgentPanel {
})
}
- fn render_tool_use_limit_reached(&self, cx: &mut Context) -> Option {
+ fn render_tool_use_limit_reached(
+ &self,
+ window: &mut Window,
+ cx: &mut Context,
+ ) -> Option {
let tool_use_limit_reached = self
.thread
.read(cx)
@@ -2593,17 +2618,59 @@ impl AgentPanel {
.configured_model()?
.model;
- let max_mode_upsell = if model.supports_max_mode() {
- " Enable max mode for unlimited tool use."
- } else {
- ""
- };
+ let focus_handle = self.focus_handle(cx);
let banner = Banner::new()
.severity(ui::Severity::Info)
- .child(h_flex().child(Label::new(format!(
- "Consecutive tool use limit reached.{max_mode_upsell}"
- ))));
+ .child(Label::new("Consecutive tool use limit reached.").size(LabelSize::Small))
+ .action_slot(
+ h_flex()
+ .gap_1()
+ .child(
+ Button::new("continue-conversation", "Continue")
+ .layer(ElevationIndex::ModalSurface)
+ .label_size(LabelSize::Small)
+ .key_binding(
+ KeyBinding::for_action_in(
+ &ContinueThread,
+ &focus_handle,
+ window,
+ cx,
+ )
+ .map(|kb| kb.size(rems_from_px(10.))),
+ )
+ .on_click(cx.listener(|this, _, window, cx| {
+ this.continue_conversation(window, cx);
+ })),
+ )
+ .when(model.supports_max_mode(), |this| {
+ this.child(
+ Button::new("continue-burn-mode", "Continue with Burn Mode")
+ .style(ButtonStyle::Filled)
+ .style(ButtonStyle::Tinted(ui::TintColor::Accent))
+ .layer(ElevationIndex::ModalSurface)
+ .label_size(LabelSize::Small)
+ .key_binding(
+ KeyBinding::for_action_in(
+ &ContinueWithBurnMode,
+ &focus_handle,
+ window,
+ cx,
+ )
+ .map(|kb| kb.size(rems_from_px(10.))),
+ )
+ .tooltip(Tooltip::text("Enable Burn Mode for unlimited tool use."))
+ .on_click(cx.listener(|this, _, window, cx| {
+ this.thread.update(cx, |active_thread, cx| {
+ active_thread.thread().update(cx, |thread, _cx| {
+ thread.set_completion_mode(CompletionMode::Max);
+ });
+ });
+ this.continue_conversation(window, cx);
+ })),
+ )
+ }),
+ );
Some(div().px_2().pb_2().child(banner).into_any_element())
}
@@ -2958,9 +3025,9 @@ impl Render for AgentPanel {
// non-obvious implications to the layout of children.
//
// If you need to change it, please confirm:
- // - The message editor expands (⌘esc) correctly
+ // - The message editor expands (cmd-option-esc) correctly
// - When expanded, the buttons at the bottom of the panel are displayed correctly
- // - Font size works as expected and can be changed with ⌘+/⌘-
+ // - Font size works as expected and can be changed with cmd-+/cmd-
// - Scrolling in all views works as expected
// - Files can be dropped into the panel
let content = v_flex()
@@ -2987,6 +3054,17 @@ impl Render for AgentPanel {
.on_action(cx.listener(Self::decrease_font_size))
.on_action(cx.listener(Self::reset_font_size))
.on_action(cx.listener(Self::toggle_zoom))
+ .on_action(cx.listener(|this, _: &ContinueThread, window, cx| {
+ this.continue_conversation(window, cx);
+ }))
+ .on_action(cx.listener(|this, _: &ContinueWithBurnMode, window, cx| {
+ this.thread.update(cx, |active_thread, cx| {
+ active_thread.thread().update(cx, |thread, _cx| {
+ thread.set_completion_mode(CompletionMode::Max);
+ });
+ });
+ this.continue_conversation(window, cx);
+ }))
.child(self.render_toolbar(window, cx))
.children(self.render_upsell(window, cx))
.children(self.render_trial_end_upsell(window, cx))
@@ -2994,7 +3072,7 @@ impl Render for AgentPanel {
ActiveView::Thread { .. } => parent
.relative()
.child(self.render_active_thread_or_empty_state(window, cx))
- .children(self.render_tool_use_limit_reached(cx))
+ .children(self.render_tool_use_limit_reached(window, cx))
.child(h_flex().child(self.message_editor.clone()))
.children(self.render_last_error(cx))
.child(self.render_drag_target(cx)),
diff --git a/crates/agent/src/message_editor.rs b/crates/agent/src/message_editor.rs
index 3256299c89..a53fc475f4 100644
--- a/crates/agent/src/message_editor.rs
+++ b/crates/agent/src/message_editor.rs
@@ -480,16 +480,18 @@ impl MessageEditor {
let active_completion_mode = thread.completion_mode();
let max_mode_enabled = active_completion_mode == CompletionMode::Max;
+ let icon = if max_mode_enabled {
+ IconName::ZedBurnModeOn
+ } else {
+ IconName::ZedBurnMode
+ };
Some(
- Button::new("max-mode", "Max Mode")
- .label_size(LabelSize::Small)
- .color(Color::Muted)
- .icon(IconName::ZedMaxMode)
+ IconButton::new("burn-mode", icon)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
- .icon_position(IconPosition::Start)
.toggle_state(max_mode_enabled)
+ .selected_icon_color(Color::Error)
.on_click(cx.listener(move |this, _event, _window, cx| {
this.thread.update(cx, |thread, _cx| {
thread.set_completion_mode(match active_completion_mode {
@@ -686,7 +688,6 @@ impl MessageEditor {
.justify_between()
.child(
h_flex()
- .gap_1()
.child(self.render_follow_toggle(cx))
.children(self.render_max_mode_toggle(cx)),
)
diff --git a/crates/agent/src/thread.rs b/crates/agent/src/thread.rs
index d0b63e0157..78a0f855ef 100644
--- a/crates/agent/src/thread.rs
+++ b/crates/agent/src/thread.rs
@@ -115,6 +115,7 @@ pub struct Message {
pub segments: Vec,
pub loaded_context: LoadedContext,
pub creases: Vec,
+ pub is_hidden: bool,
}
impl Message {
@@ -540,6 +541,7 @@ impl Thread {
context: None,
})
.collect(),
+ is_hidden: message.is_hidden,
})
.collect(),
next_message_id,
@@ -560,7 +562,7 @@ impl Thread {
cumulative_token_usage: serialized.cumulative_token_usage,
exceeded_window_error: None,
last_usage: None,
- tool_use_limit_reached: false,
+ tool_use_limit_reached: serialized.tool_use_limit_reached,
feedback: None,
message_feedback: HashMap::default(),
last_auto_capture_at: None,
@@ -849,7 +851,7 @@ impl Thread {
.get(ix + 1)
.and_then(|message| {
self.message(message.id)
- .map(|next_message| next_message.role == Role::User)
+ .map(|next_message| next_message.role == Role::User && !next_message.is_hidden)
})
.unwrap_or(false)
}
@@ -951,6 +953,7 @@ impl Thread {
vec![MessageSegment::Text(text.into())],
loaded_context.loaded_context,
creases,
+ false,
cx,
);
@@ -966,6 +969,20 @@ impl Thread {
message_id
}
+ pub fn insert_invisible_continue_message(&mut self, cx: &mut Context) -> MessageId {
+ let id = self.insert_message(
+ Role::User,
+ vec![MessageSegment::Text("Continue where you left off".into())],
+ LoadedContext::default(),
+ vec![],
+ true,
+ cx,
+ );
+ self.pending_checkpoint = None;
+
+ id
+ }
+
pub fn insert_assistant_message(
&mut self,
segments: Vec,
@@ -976,6 +993,7 @@ impl Thread {
segments,
LoadedContext::default(),
Vec::new(),
+ false,
cx,
)
}
@@ -986,6 +1004,7 @@ impl Thread {
segments: Vec,
loaded_context: LoadedContext,
creases: Vec,
+ is_hidden: bool,
cx: &mut Context,
) -> MessageId {
let id = self.next_message_id.post_inc();
@@ -995,6 +1014,7 @@ impl Thread {
segments,
loaded_context,
creases,
+ is_hidden,
});
self.touch_updated_at();
cx.emit(ThreadEvent::MessageAdded(id));
@@ -1135,6 +1155,7 @@ impl Thread {
label: crease.metadata.label.clone(),
})
.collect(),
+ is_hidden: message.is_hidden,
})
.collect(),
initial_project_snapshot,
@@ -1150,6 +1171,7 @@ impl Thread {
model: model.model.id().0.to_string(),
}),
completion_mode: Some(this.completion_mode),
+ tool_use_limit_reached: this.tool_use_limit_reached,
})
})
}
@@ -1781,6 +1803,7 @@ impl Thread {
thread.cancel_last_completion(window, cx);
}
}
+
cx.emit(ThreadEvent::Stopped(result.map_err(Arc::new)));
if let Some((request_callback, (request, response_events))) = thread
diff --git a/crates/agent/src/thread_store.rs b/crates/agent/src/thread_store.rs
index 8cc29e32ab..8c6fc909e9 100644
--- a/crates/agent/src/thread_store.rs
+++ b/crates/agent/src/thread_store.rs
@@ -676,6 +676,8 @@ pub struct SerializedThread {
pub model: Option,
#[serde(default)]
pub completion_mode: Option,
+ #[serde(default)]
+ pub tool_use_limit_reached: bool,
}
#[derive(Serialize, Deserialize, Debug)]
@@ -757,6 +759,8 @@ pub struct SerializedMessage {
pub context: String,
#[serde(default)]
pub creases: Vec,
+ #[serde(default)]
+ pub is_hidden: bool,
}
#[derive(Debug, Serialize, Deserialize)]
@@ -815,6 +819,7 @@ impl LegacySerializedThread {
exceeded_window_error: None,
model: None,
completion_mode: None,
+ tool_use_limit_reached: false,
}
}
}
@@ -840,6 +845,7 @@ impl LegacySerializedMessage {
tool_results: self.tool_results,
context: String::new(),
creases: Vec::new(),
+ is_hidden: false,
}
}
}
diff --git a/crates/agent/src/ui/max_mode_tooltip.rs b/crates/agent/src/ui/max_mode_tooltip.rs
index c6a5116e2e..d1bd94c201 100644
--- a/crates/agent/src/ui/max_mode_tooltip.rs
+++ b/crates/agent/src/ui/max_mode_tooltip.rs
@@ -18,18 +18,24 @@ impl MaxModeTooltip {
impl Render for MaxModeTooltip {
fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement {
+ let icon = if self.selected {
+ IconName::ZedBurnModeOn
+ } else {
+ IconName::ZedBurnMode
+ };
+
+ let title = h_flex()
+ .gap_1()
+ .child(Icon::new(icon).size(IconSize::Small))
+ .child(Label::new("Burn Mode"));
+
tooltip_container(window, cx, |this, _, _| {
- this.gap_1()
+ this.gap_0p5()
.map(|header| if self.selected {
header.child(
h_flex()
.justify_between()
- .child(
- h_flex()
- .gap_1p5()
- .child(Icon::new(IconName::ZedMaxMode).size(IconSize::Small).color(Color::Accent))
- .child(Label::new("Zed's Max Mode"))
- )
+ .child(title)
.child(
h_flex()
.gap_0p5()
@@ -38,18 +44,13 @@ impl Render for MaxModeTooltip {
)
)
} else {
- header.child(
- h_flex()
- .gap_1p5()
- .child(Icon::new(IconName::ZedMaxMode).size(IconSize::Small))
- .child(Label::new("Zed's Max Mode"))
- )
+ header.child(title)
})
.child(
div()
.max_w_72()
.child(
- Label::new("This mode enables models to use large context windows, unlimited tool calls, and other capabilities for expanded reasoning, offering an unfettered agentic experience.")
+ Label::new("Enables models to use large context windows, unlimited tool calls, and other capabilities for expanded reasoning, offering an unfettered agentic experience.")
.size(LabelSize::Small)
.color(Color::Muted)
)
diff --git a/crates/icons/src/icons.rs b/crates/icons/src/icons.rs
index 3f51383f21..6d12edff83 100644
--- a/crates/icons/src/icons.rs
+++ b/crates/icons/src/icons.rs
@@ -256,7 +256,8 @@ pub enum IconName {
XCircle,
ZedAssistant,
ZedAssistantFilled,
- ZedMaxMode,
+ ZedBurnMode,
+ ZedBurnModeOn,
ZedPredict,
ZedPredictDisabled,
ZedPredictDown,
diff --git a/crates/ui/src/components/banner.rs b/crates/ui/src/components/banner.rs
index d5bee5463f..043791cdd8 100644
--- a/crates/ui/src/components/banner.rs
+++ b/crates/ui/src/components/banner.rs
@@ -86,7 +86,7 @@ impl RenderOnce for Banner {
IconName::Info,
Color::Muted,
cx.theme().status().info_background.opacity(0.5),
- cx.theme().colors().border_variant,
+ cx.theme().colors().border.opacity(0.5),
),
Severity::Success => (
IconName::Check,