@@ -1303,6 +1303,10 @@ impl AcpThread {
&self.session_id
}
+ pub fn supports_truncate(&self, cx: &App) -> bool {
+ self.connection.truncate(&self.session_id, cx).is_some()
+ }
+
pub fn work_dirs(&self) -> Option<&PathList> {
self.work_dirs.as_ref()
}
@@ -4362,6 +4366,7 @@ mod tests {
#[derive(Clone, Default)]
struct FakeAgentConnection {
auth_methods: Vec<acp::AuthMethod>,
+ supports_truncate: bool,
sessions: Arc<parking_lot::Mutex<HashMap<acp::SessionId, WeakEntity<AcpThread>>>>,
set_title_calls: Rc<RefCell<Vec<SharedString>>>,
on_user_message: Option<
@@ -4380,12 +4385,18 @@ mod tests {
fn new() -> Self {
Self {
auth_methods: Vec::new(),
+ supports_truncate: true,
on_user_message: None,
sessions: Arc::default(),
set_title_calls: Default::default(),
}
}
+ fn without_truncate_support(mut self) -> Self {
+ self.supports_truncate = false;
+ self
+ }
+
#[expect(unused)]
fn with_auth_methods(mut self, auth_methods: Vec<acp::AuthMethod>) -> Self {
self.auth_methods = auth_methods;
@@ -4487,9 +4498,11 @@ mod tests {
session_id: &acp::SessionId,
_cx: &App,
) -> Option<Rc<dyn AgentSessionTruncate>> {
- Some(Rc::new(FakeAgentSessionEditor {
- _session_id: session_id.clone(),
- }))
+ self.supports_truncate.then(|| {
+ Rc::new(FakeAgentSessionEditor {
+ _session_id: session_id.clone(),
+ }) as Rc<dyn AgentSessionTruncate>
+ })
}
fn set_title(
@@ -5044,6 +5057,37 @@ mod tests {
);
}
+ #[gpui::test]
+ async fn test_send_assigns_message_id_without_truncate_support(cx: &mut TestAppContext) {
+ init_test(cx);
+
+ let fs = FakeFs::new(cx.executor());
+ let project = Project::test(fs, [], cx).await;
+
+ let connection = Rc::new(FakeAgentConnection::new().without_truncate_support());
+ let thread = cx
+ .update(|cx| {
+ connection.new_session(project, PathList::new(&[Path::new(path!("/test"))]), cx)
+ })
+ .await
+ .unwrap();
+
+ let response = thread
+ .update(cx, |thread, cx| thread.send_raw("test message", cx))
+ .await;
+
+ assert!(response.is_ok(), "send should not fail: {response:?}");
+ thread.read_with(cx, |thread, _| {
+ let AgentThreadEntry::UserMessage(message) = &thread.entries[0] else {
+ panic!("expected first entry to be a user message")
+ };
+ assert!(
+ message.id.is_some(),
+ "user message should always have an id"
+ );
+ });
+ }
+
#[gpui::test]
async fn test_send_returns_cancelled_response_and_marks_tools_as_cancelled(
cx: &mut TestAppContext,
@@ -1972,12 +1972,7 @@ impl ConversationView {
.read(cx)
.entries()
.iter()
- .any(|entry| {
- matches!(
- entry,
- AgentThreadEntry::UserMessage(user_message) if user_message.id.is_some()
- )
- })
+ .any(|entry| matches!(entry, AgentThreadEntry::UserMessage(_)))
})
}
@@ -754,6 +754,7 @@ impl ThreadView {
ViewEvent::MessageEditorEvent(_editor, MessageEditorEvent::Focus) => {
if let Some(AgentThreadEntry::UserMessage(user_message)) =
self.thread.read(cx).entries().get(event.entry_index)
+ && self.thread.read(cx).supports_truncate(cx)
&& user_message.id.is_some()
&& !self.is_subagent()
{
@@ -764,6 +765,7 @@ impl ThreadView {
ViewEvent::MessageEditorEvent(editor, MessageEditorEvent::LostFocus) => {
if let Some(AgentThreadEntry::UserMessage(user_message)) =
self.thread.read(cx).entries().get(event.entry_index)
+ && self.thread.read(cx).supports_truncate(cx)
&& user_message.id.is_some()
&& !self.is_subagent()
{
@@ -4495,7 +4497,8 @@ impl ThreadView {
.is_some_and(|checkpoint| checkpoint.show);
let is_subagent = self.is_subagent();
- let is_editable = message.id.is_some() && !is_subagent;
+ let can_rewind = self.thread.read(cx).supports_truncate(cx);
+ let is_editable = can_rewind && message.id.is_some() && !is_subagent;
let agent_name = if is_subagent {
"subagents".into()
} else {
@@ -72,6 +72,7 @@ impl EntryViewState {
match thread_entry {
AgentThreadEntry::UserMessage(message) => {
+ let can_rewind = thread.read(cx).supports_truncate(cx);
let has_id = message.id.is_some();
let is_subagent = thread.read(cx).parent_session_id().is_some();
let chunks = message.chunks.clone();
@@ -101,7 +102,7 @@ impl EntryViewState {
window,
cx,
);
- if !has_id || is_subagent {
+ if !can_rewind || !has_id || is_subagent {
editor.set_read_only(true, cx);
}
editor.set_message(chunks, window, cx);