Port terminal tool to agent2 (#35918)

Release Notes:

- N/A

---------

Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
This commit is contained in:
Antonio Scandurra 2025-08-11 12:31:13 +02:00 committed by GitHub
parent 422e0a2eb7
commit 086ea3c619
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 882 additions and 112 deletions

View file

@ -1,17 +1,13 @@
use acp_thread::{
AcpThread, AcpThreadEvent, AgentThreadEntry, AssistantMessage, AssistantMessageChunk,
LoadError, MentionPath, ThreadStatus, ToolCall, ToolCallContent, ToolCallStatus,
};
use acp_thread::{AgentConnection, Plan};
use action_log::ActionLog;
use agent_client_protocol as acp;
use agent_servers::AgentServer;
use agent_settings::{AgentSettings, NotifyWhenAgentWaiting};
use audio::{Audio, Sound};
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::path::Path;
use std::process::ExitStatus;
use std::rc::Rc;
use std::sync::Arc;
use std::time::Duration;
use action_log::ActionLog;
use agent_client_protocol as acp;
use buffer_diff::BufferDiff;
use collections::{HashMap, HashSet};
use editor::{
@ -32,6 +28,11 @@ use markdown::{HeadingLevelStyles, Markdown, MarkdownElement, MarkdownStyle};
use parking_lot::Mutex;
use project::{CompletionIntent, Project};
use settings::{Settings as _, SettingsStore};
use std::{
cell::RefCell, collections::BTreeMap, path::Path, process::ExitStatus, rc::Rc, sync::Arc,
time::Duration,
};
use terminal_view::TerminalView;
use text::{Anchor, BufferSnapshot};
use theme::ThemeSettings;
use ui::{
@ -41,11 +42,6 @@ use util::ResultExt;
use workspace::{CollaboratorId, Workspace};
use zed_actions::agent::{Chat, NextHistoryMessage, PreviousHistoryMessage};
use ::acp_thread::{
AcpThread, AcpThreadEvent, AgentThreadEntry, AssistantMessage, AssistantMessageChunk,
LoadError, MentionPath, ThreadStatus, ToolCall, ToolCallContent, ToolCallStatus,
};
use crate::acp::completion_provider::{ContextPickerCompletionProvider, MentionSet};
use crate::acp::message_history::MessageHistory;
use crate::agent_diff::AgentDiff;
@ -63,6 +59,7 @@ pub struct AcpThreadView {
project: Entity<Project>,
thread_state: ThreadState,
diff_editors: HashMap<EntityId, Entity<Editor>>,
terminal_views: HashMap<EntityId, Entity<TerminalView>>,
message_editor: Entity<Editor>,
message_set_from_history: Option<BufferSnapshot>,
_message_editor_subscription: Subscription,
@ -193,6 +190,7 @@ impl AcpThreadView {
notifications: Vec::new(),
notification_subscriptions: HashMap::default(),
diff_editors: Default::default(),
terminal_views: Default::default(),
list_state: list_state.clone(),
scrollbar_state: ScrollbarState::new(list_state).parent_entity(&cx.entity()),
last_error: None,
@ -676,6 +674,16 @@ impl AcpThreadView {
entry_ix: usize,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.sync_diff_multibuffers(entry_ix, window, cx);
self.sync_terminals(entry_ix, window, cx);
}
fn sync_diff_multibuffers(
&mut self,
entry_ix: usize,
window: &mut Window,
cx: &mut Context<Self>,
) {
let Some(multibuffers) = self.entry_diff_multibuffers(entry_ix, cx) else {
return;
@ -739,6 +747,50 @@ impl AcpThreadView {
)
}
fn sync_terminals(&mut self, entry_ix: usize, window: &mut Window, cx: &mut Context<Self>) {
let Some(terminals) = self.entry_terminals(entry_ix, cx) else {
return;
};
let terminals = terminals.collect::<Vec<_>>();
for terminal in terminals {
if self.terminal_views.contains_key(&terminal.entity_id()) {
return;
}
let terminal_view = cx.new(|cx| {
let mut view = TerminalView::new(
terminal.read(cx).inner().clone(),
self.workspace.clone(),
None,
self.project.downgrade(),
window,
cx,
);
view.set_embedded_mode(None, cx);
view
});
let entity_id = terminal.entity_id();
cx.observe_release(&terminal, move |this, _, _| {
this.terminal_views.remove(&entity_id);
})
.detach();
self.terminal_views.insert(entity_id, terminal_view);
}
}
fn entry_terminals(
&self,
entry_ix: usize,
cx: &App,
) -> Option<impl Iterator<Item = Entity<acp_thread::Terminal>>> {
let entry = self.thread()?.read(cx).entries().get(entry_ix)?;
Some(entry.terminals().map(|terminal| terminal.clone()))
}
fn authenticate(
&mut self,
method: acp::AuthMethodId,
@ -1106,7 +1158,7 @@ impl AcpThreadView {
_ => tool_call
.content
.iter()
.any(|content| matches!(content, ToolCallContent::Diff { .. })),
.any(|content| matches!(content, ToolCallContent::Diff(_))),
};
let is_collapsible = !tool_call.content.is_empty() && !needs_confirmation;
@ -1303,7 +1355,7 @@ impl AcpThreadView {
cx: &Context<Self>,
) -> AnyElement {
match content {
ToolCallContent::ContentBlock { content } => {
ToolCallContent::ContentBlock(content) => {
if let Some(md) = content.markdown() {
div()
.p_2()
@ -1318,9 +1370,8 @@ impl AcpThreadView {
Empty.into_any_element()
}
}
ToolCallContent::Diff { diff, .. } => {
self.render_diff_editor(&diff.read(cx).multibuffer())
}
ToolCallContent::Diff(diff) => self.render_diff_editor(&diff.read(cx).multibuffer()),
ToolCallContent::Terminal(terminal) => self.render_terminal(terminal),
}
}
@ -1389,6 +1440,21 @@ impl AcpThreadView {
.into_any()
}
fn render_terminal(&self, terminal: &Entity<acp_thread::Terminal>) -> AnyElement {
v_flex()
.h_72()
.child(
if let Some(terminal_view) = self.terminal_views.get(&terminal.entity_id()) {
// TODO: terminal has all the state we need to reproduce
// what we had in the terminal card.
terminal_view.clone().into_any_element()
} else {
Empty.into_any()
},
)
.into_any()
}
fn render_agent_logo(&self) -> AnyElement {
Icon::new(self.agent.logo())
.color(Color::Muted)