acp: Have AcpThread handle all interrupting (#36417)

The view was cancelling the generation, but `AcpThread` already handles
that, so we removed that extra code and fixed a bug where an update from
the first user message would appear after the second one.

Release Notes:

- N/A

Co-authored-by: Danilo <danilo@zed.dev>
This commit is contained in:
Agus Zubiaga 2025-08-18 12:34:27 -03:00 committed by GitHub
parent 9b78c46902
commit 48fed866e6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 164 additions and 20 deletions

View file

@ -1200,17 +1200,21 @@ impl AcpThread {
} else {
None
};
self.push_entry(
AgentThreadEntry::UserMessage(UserMessage {
id: message_id.clone(),
content: block,
chunks: message,
checkpoint: None,
}),
cx,
);
self.run_turn(cx, async move |this, cx| {
this.update(cx, |this, cx| {
this.push_entry(
AgentThreadEntry::UserMessage(UserMessage {
id: message_id.clone(),
content: block,
chunks: message,
checkpoint: None,
}),
cx,
);
})
.ok();
let old_checkpoint = git_store
.update(cx, |git, cx| git.checkpoint(cx))?
.await

View file

@ -201,7 +201,7 @@ mod test_support {
struct Session {
thread: WeakEntity<AcpThread>,
response_tx: Option<oneshot::Sender<()>>,
response_tx: Option<oneshot::Sender<acp::StopReason>>,
}
impl StubAgentConnection {
@ -242,12 +242,12 @@ mod test_support {
.unwrap()
.thread
.update(cx, |thread, cx| {
thread.handle_session_update(update.clone(), cx).unwrap();
thread.handle_session_update(update, cx).unwrap();
})
.unwrap();
}
pub fn end_turn(&self, session_id: acp::SessionId) {
pub fn end_turn(&self, session_id: acp::SessionId, stop_reason: acp::StopReason) {
self.sessions
.lock()
.get_mut(&session_id)
@ -255,7 +255,7 @@ mod test_support {
.response_tx
.take()
.expect("No pending turn")
.send(())
.send(stop_reason)
.unwrap();
}
}
@ -308,10 +308,8 @@ mod test_support {
let (tx, rx) = oneshot::channel();
response_tx.replace(tx);
cx.spawn(async move |_| {
rx.await?;
Ok(acp::PromptResponse {
stop_reason: acp::StopReason::EndTurn,
})
let stop_reason = rx.await?;
Ok(acp::PromptResponse { stop_reason })
})
} else {
for update in self.next_prompt_updates.lock().drain(..) {
@ -353,8 +351,17 @@ mod test_support {
}
}
fn cancel(&self, _session_id: &acp::SessionId, _cx: &mut App) {
unimplemented!()
fn cancel(&self, session_id: &acp::SessionId, _cx: &mut App) {
if let Some(end_turn_tx) = self
.sessions
.lock()
.get_mut(session_id)
.unwrap()
.response_tx
.take()
{
end_turn_tx.send(acp::StopReason::Canceled).unwrap();
}
}
fn session_editor(

View file

@ -4283,7 +4283,7 @@ pub(crate) mod tests {
},
cx,
);
connection.end_turn(session_id);
connection.end_turn(session_id, acp::StopReason::EndTurn);
});
thread_view.read_with(cx, |view, _cx| {
@ -4302,4 +4302,137 @@ pub(crate) mod tests {
);
});
}
#[gpui::test]
async fn test_interrupt(cx: &mut TestAppContext) {
init_test(cx);
let connection = StubAgentConnection::new();
let (thread_view, cx) =
setup_thread_view(StubAgentServer::new(connection.clone()), cx).await;
add_to_workspace(thread_view.clone(), cx);
let message_editor = cx.read(|cx| thread_view.read(cx).message_editor.clone());
message_editor.update_in(cx, |editor, window, cx| {
editor.set_text("Message 1", window, cx);
});
thread_view.update_in(cx, |thread_view, window, cx| {
thread_view.send(window, cx);
});
let (thread, session_id) = thread_view.read_with(cx, |view, cx| {
let thread = view.thread().unwrap();
(thread.clone(), thread.read(cx).session_id().clone())
});
cx.run_until_parked();
cx.update(|_, cx| {
connection.send_update(
session_id.clone(),
acp::SessionUpdate::AgentMessageChunk {
content: "Message 1 resp".into(),
},
cx,
);
});
cx.run_until_parked();
thread.read_with(cx, |thread, cx| {
assert_eq!(
thread.to_markdown(cx),
indoc::indoc! {"
## User
Message 1
## Assistant
Message 1 resp
"}
)
});
message_editor.update_in(cx, |editor, window, cx| {
editor.set_text("Message 2", window, cx);
});
thread_view.update_in(cx, |thread_view, window, cx| {
thread_view.send(window, cx);
});
cx.update(|_, cx| {
// Simulate a response sent after beginning to cancel
connection.send_update(
session_id.clone(),
acp::SessionUpdate::AgentMessageChunk {
content: "onse".into(),
},
cx,
);
});
cx.run_until_parked();
// Last Message 1 response should appear before Message 2
thread.read_with(cx, |thread, cx| {
assert_eq!(
thread.to_markdown(cx),
indoc::indoc! {"
## User
Message 1
## Assistant
Message 1 response
## User
Message 2
"}
)
});
cx.update(|_, cx| {
connection.send_update(
session_id.clone(),
acp::SessionUpdate::AgentMessageChunk {
content: "Message 2 response".into(),
},
cx,
);
connection.end_turn(session_id.clone(), acp::StopReason::EndTurn);
});
cx.run_until_parked();
thread.read_with(cx, |thread, cx| {
assert_eq!(
thread.to_markdown(cx),
indoc::indoc! {"
## User
Message 1
## Assistant
Message 1 response
## User
Message 2
## Assistant
Message 2 response
"}
)
});
}
}