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,5 +1,4 @@
use crate::{SystemPromptTemplate, Template, Templates};
use acp_thread::Diff;
use action_log::ActionLog;
use agent_client_protocol as acp;
use anyhow::{Context as _, Result, anyhow};
@ -802,47 +801,6 @@ impl AgentResponseEventStream {
.ok();
}
fn authorize_tool_call(
&self,
id: &LanguageModelToolUseId,
title: String,
kind: acp::ToolKind,
input: serde_json::Value,
) -> impl use<> + Future<Output = Result<()>> {
let (response_tx, response_rx) = oneshot::channel();
self.0
.unbounded_send(Ok(AgentResponseEvent::ToolCallAuthorization(
ToolCallAuthorization {
tool_call: Self::initial_tool_call(id, title, kind, input),
options: vec![
acp::PermissionOption {
id: acp::PermissionOptionId("always_allow".into()),
name: "Always Allow".into(),
kind: acp::PermissionOptionKind::AllowAlways,
},
acp::PermissionOption {
id: acp::PermissionOptionId("allow".into()),
name: "Allow".into(),
kind: acp::PermissionOptionKind::AllowOnce,
},
acp::PermissionOption {
id: acp::PermissionOptionId("deny".into()),
name: "Deny".into(),
kind: acp::PermissionOptionKind::RejectOnce,
},
],
response: response_tx,
},
)))
.ok();
async move {
match response_rx.await?.0.as_ref() {
"allow" | "always_allow" => Ok(()),
_ => Err(anyhow!("Permission to run tool denied by user")),
}
}
}
fn send_tool_call(
&self,
id: &LanguageModelToolUseId,
@ -894,18 +852,6 @@ impl AgentResponseEventStream {
.ok();
}
fn update_tool_call_diff(&self, tool_use_id: &LanguageModelToolUseId, diff: Entity<Diff>) {
self.0
.unbounded_send(Ok(AgentResponseEvent::ToolCallUpdate(
acp_thread::ToolCallUpdateDiff {
id: acp::ToolCallId(tool_use_id.to_string().into()),
diff,
}
.into(),
)))
.ok();
}
fn send_stop(&self, reason: StopReason) {
match reason {
StopReason::EndTurn => {
@ -979,17 +925,71 @@ impl ToolCallEventStream {
.update_tool_call_fields(&self.tool_use_id, fields);
}
pub fn update_diff(&self, diff: Entity<Diff>) {
self.stream.update_tool_call_diff(&self.tool_use_id, diff);
pub fn update_diff(&self, diff: Entity<acp_thread::Diff>) {
self.stream
.0
.unbounded_send(Ok(AgentResponseEvent::ToolCallUpdate(
acp_thread::ToolCallUpdateDiff {
id: acp::ToolCallId(self.tool_use_id.to_string().into()),
diff,
}
.into(),
)))
.ok();
}
pub fn update_terminal(&self, terminal: Entity<acp_thread::Terminal>) {
self.stream
.0
.unbounded_send(Ok(AgentResponseEvent::ToolCallUpdate(
acp_thread::ToolCallUpdateTerminal {
id: acp::ToolCallId(self.tool_use_id.to_string().into()),
terminal,
}
.into(),
)))
.ok();
}
pub fn authorize(&self, title: String) -> impl use<> + Future<Output = Result<()>> {
self.stream.authorize_tool_call(
&self.tool_use_id,
title,
self.kind.clone(),
self.input.clone(),
)
let (response_tx, response_rx) = oneshot::channel();
self.stream
.0
.unbounded_send(Ok(AgentResponseEvent::ToolCallAuthorization(
ToolCallAuthorization {
tool_call: AgentResponseEventStream::initial_tool_call(
&self.tool_use_id,
title,
self.kind.clone(),
self.input.clone(),
),
options: vec![
acp::PermissionOption {
id: acp::PermissionOptionId("always_allow".into()),
name: "Always Allow".into(),
kind: acp::PermissionOptionKind::AllowAlways,
},
acp::PermissionOption {
id: acp::PermissionOptionId("allow".into()),
name: "Allow".into(),
kind: acp::PermissionOptionKind::AllowOnce,
},
acp::PermissionOption {
id: acp::PermissionOptionId("deny".into()),
name: "Deny".into(),
kind: acp::PermissionOptionKind::RejectOnce,
},
],
response: response_tx,
},
)))
.ok();
async move {
match response_rx.await?.0.as_ref() {
"allow" | "always_allow" => Ok(()),
_ => Err(anyhow!("Permission to run tool denied by user")),
}
}
}
}
@ -1000,7 +1000,7 @@ pub struct ToolCallEventStreamReceiver(
#[cfg(test)]
impl ToolCallEventStreamReceiver {
pub async fn expect_tool_authorization(&mut self) -> ToolCallAuthorization {
pub async fn expect_authorization(&mut self) -> ToolCallAuthorization {
let event = self.0.next().await;
if let Some(Ok(AgentResponseEvent::ToolCallAuthorization(auth))) = event {
auth
@ -1008,6 +1008,18 @@ impl ToolCallEventStreamReceiver {
panic!("Expected ToolCallAuthorization but got: {:?}", event);
}
}
pub async fn expect_terminal(&mut self) -> Entity<acp_thread::Terminal> {
let event = self.0.next().await;
if let Some(Ok(AgentResponseEvent::ToolCallUpdate(
acp_thread::ToolCallUpdate::UpdateTerminal(update),
))) = event
{
update.terminal
} else {
panic!("Expected terminal but got: {:?}", event);
}
}
}
#[cfg(test)]