wip
Co-authored-by: Agus Zubiaga <agus@zed.dev>
This commit is contained in:
parent
edbcc71b87
commit
ebf94e20c9
3 changed files with 136 additions and 51 deletions
|
@ -221,8 +221,8 @@ impl ToolCall {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ToolCallStatus {
|
pub enum ToolCallStatus {
|
||||||
WaitingForConfirmation {
|
WaitingForConfirmation {
|
||||||
possible_grants: Vec<acp::Grant>,
|
options: Vec<acp::PermissionOption>,
|
||||||
respond_tx: oneshot::Sender<acp::GrantId>,
|
respond_tx: oneshot::Sender<acp::PermissionOutcome>,
|
||||||
},
|
},
|
||||||
Allowed {
|
Allowed {
|
||||||
status: acp::ToolCallStatus,
|
status: acp::ToolCallStatus,
|
||||||
|
@ -550,6 +550,7 @@ pub struct AcpThread {
|
||||||
send_task: Option<Task<()>>,
|
send_task: Option<Task<()>>,
|
||||||
connection: Arc<dyn AgentConnection>,
|
connection: Arc<dyn AgentConnection>,
|
||||||
child_status: Option<Task<Result<()>>>,
|
child_status: Option<Task<Result<()>>>,
|
||||||
|
session_id: acp::SessionId,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum AcpThreadEvent {
|
pub enum AcpThreadEvent {
|
||||||
|
@ -595,6 +596,7 @@ impl AcpThread {
|
||||||
title: SharedString,
|
title: SharedString,
|
||||||
child_status: Option<Task<Result<()>>>,
|
child_status: Option<Task<Result<()>>>,
|
||||||
project: Entity<Project>,
|
project: Entity<Project>,
|
||||||
|
session_id: acp::SessionId,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let action_log = cx.new(|_| ActionLog::new(project.clone()));
|
let action_log = cx.new(|_| ActionLog::new(project.clone()));
|
||||||
|
@ -609,19 +611,7 @@ impl AcpThread {
|
||||||
send_task: None,
|
send_task: None,
|
||||||
connection: Arc::new(connection),
|
connection: Arc::new(connection),
|
||||||
child_status,
|
child_status,
|
||||||
}
|
session_id,
|
||||||
}
|
|
||||||
|
|
||||||
/// Send a request to the agent and wait for a response.
|
|
||||||
pub fn request<R: acp_old::AgentRequest + 'static>(
|
|
||||||
&self,
|
|
||||||
params: R,
|
|
||||||
) -> impl use<R> + Future<Output = Result<R::Response>> {
|
|
||||||
let params = params.into_any();
|
|
||||||
let result = self.connection.request_any(params);
|
|
||||||
async move {
|
|
||||||
let result = result.await?;
|
|
||||||
Ok(R::response_from_any(result)?)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -778,13 +768,13 @@ impl AcpThread {
|
||||||
pub fn request_tool_call_permission(
|
pub fn request_tool_call_permission(
|
||||||
&mut self,
|
&mut self,
|
||||||
tool_call: acp::ToolCall,
|
tool_call: acp::ToolCall,
|
||||||
possible_grants: Vec<acp::Grant>,
|
options: Vec<acp::PermissionOption>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> oneshot::Receiver<acp::GrantId> {
|
) -> oneshot::Receiver<acp::PermissionOutcome> {
|
||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
|
|
||||||
let status = ToolCallStatus::WaitingForConfirmation {
|
let status = ToolCallStatus::WaitingForConfirmation {
|
||||||
possible_grants,
|
options,
|
||||||
respond_tx: tx,
|
respond_tx: tx,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -812,25 +802,32 @@ impl AcpThread {
|
||||||
pub fn authorize_tool_call(
|
pub fn authorize_tool_call(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: acp::ToolCallId,
|
id: acp::ToolCallId,
|
||||||
grant: acp::Grant,
|
option: acp::PermissionOption,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
let Some((ix, call)) = self.tool_call_mut(&id) else {
|
let Some((ix, call)) = self.tool_call_mut(&id) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let new_status = if grant.is_allowed {
|
let new_status = match option.kind {
|
||||||
|
acp::PermissionOptionKind::RejectOnce | acp::PermissionOptionKind::RejectAlways => {
|
||||||
|
ToolCallStatus::Rejected
|
||||||
|
}
|
||||||
|
acp::PermissionOptionKind::AllowOnce | acp::PermissionOptionKind::AllowAlways => {
|
||||||
ToolCallStatus::Allowed {
|
ToolCallStatus::Allowed {
|
||||||
status: acp::ToolCallStatus::InProgress,
|
status: acp::ToolCallStatus::InProgress,
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
ToolCallStatus::Rejected
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let curr_status = mem::replace(&mut call.status, new_status);
|
let curr_status = mem::replace(&mut call.status, new_status);
|
||||||
|
|
||||||
if let ToolCallStatus::WaitingForConfirmation { respond_tx, .. } = curr_status {
|
if let ToolCallStatus::WaitingForConfirmation { respond_tx, .. } = curr_status {
|
||||||
respond_tx.send(grant.id).log_err();
|
respond_tx
|
||||||
|
.send(acp::PermissionOutcome::Selected {
|
||||||
|
option_id: option.id,
|
||||||
|
})
|
||||||
|
.log_err();
|
||||||
} else if cfg!(debug_assertions) {
|
} else if cfg!(debug_assertions) {
|
||||||
panic!("tried to authorize an already authorized tool call");
|
panic!("tried to authorize an already authorized tool call");
|
||||||
}
|
}
|
||||||
|
@ -941,8 +938,11 @@ impl AcpThread {
|
||||||
message: Vec<acp::ContentBlock>,
|
message: Vec<acp::ContentBlock>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> BoxFuture<'static, Result<(), acp_old::Error>> {
|
) -> BoxFuture<'static, Result<(), acp_old::Error>> {
|
||||||
let block =
|
let block = ContentBlock::new_combined(
|
||||||
ContentBlock::new_combined(message.clone(), self.project.read(cx).languages(), cx);
|
message.clone(),
|
||||||
|
self.project.read(cx).languages().clone(),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
self.push_entry(
|
self.push_entry(
|
||||||
AgentThreadEntry::UserMessage(UserMessage { content: block }),
|
AgentThreadEntry::UserMessage(UserMessage { content: block }),
|
||||||
cx,
|
cx,
|
||||||
|
@ -955,7 +955,14 @@ impl AcpThread {
|
||||||
async {
|
async {
|
||||||
cancel.await.log_err();
|
cancel.await.log_err();
|
||||||
|
|
||||||
let result = this.update(cx, |this, _| this.request(message))?.await;
|
let result = this
|
||||||
|
.update(cx, |this, _| {
|
||||||
|
this.connection.prompt(acp::PromptToolArguments {
|
||||||
|
prompt: message,
|
||||||
|
session_id: this.session_id.clone(),
|
||||||
|
})
|
||||||
|
})?
|
||||||
|
.await;
|
||||||
tx.send(result).log_err();
|
tx.send(result).log_err();
|
||||||
this.update(cx, |this, _cx| this.send_task.take())?;
|
this.update(cx, |this, _cx| this.send_task.take())?;
|
||||||
anyhow::Ok(())
|
anyhow::Ok(())
|
||||||
|
@ -975,7 +982,7 @@ impl AcpThread {
|
||||||
|
|
||||||
pub fn cancel(&mut self, cx: &mut Context<Self>) -> Task<Result<(), acp_old::Error>> {
|
pub fn cancel(&mut self, cx: &mut Context<Self>) -> Task<Result<(), acp_old::Error>> {
|
||||||
if self.send_task.take().is_some() {
|
if self.send_task.take().is_some() {
|
||||||
let request = self.request(acp_old::CancelSendMessageParams);
|
let request = self.connection.cancel();
|
||||||
cx.spawn(async move |this, cx| {
|
cx.spawn(async move |this, cx| {
|
||||||
request.await?;
|
request.await?;
|
||||||
this.update(cx, |this, _cx| {
|
this.update(cx, |this, _cx| {
|
||||||
|
@ -994,17 +1001,10 @@ impl AcpThread {
|
||||||
mem::replace(&mut call.status, ToolCallStatus::Canceled);
|
mem::replace(&mut call.status, ToolCallStatus::Canceled);
|
||||||
|
|
||||||
if let ToolCallStatus::WaitingForConfirmation {
|
if let ToolCallStatus::WaitingForConfirmation {
|
||||||
respond_tx,
|
respond_tx, ..
|
||||||
possible_grants,
|
|
||||||
} = curr_status
|
} = curr_status
|
||||||
{
|
{
|
||||||
if let Some(grant_id) = possible_grants
|
respond_tx.send(acp::PermissionOutcome::Canceled).ok();
|
||||||
.iter()
|
|
||||||
.find_map(|g| (!g.is_allowed).then(|| g.id.clone()))
|
|
||||||
{
|
|
||||||
// todo! do we need a way to cancel rather than reject?
|
|
||||||
respond_tx.send(grant_id).ok();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1171,12 +1171,17 @@ impl AcpThread {
|
||||||
pub struct OldAcpClientDelegate {
|
pub struct OldAcpClientDelegate {
|
||||||
thread: WeakEntity<AcpThread>,
|
thread: WeakEntity<AcpThread>,
|
||||||
cx: AsyncApp,
|
cx: AsyncApp,
|
||||||
|
next_tool_call_id: Rc<RefCell<u64>>,
|
||||||
// sent_buffer_versions: HashMap<Entity<Buffer>, HashMap<u64, BufferSnapshot>>,
|
// sent_buffer_versions: HashMap<Entity<Buffer>, HashMap<u64, BufferSnapshot>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OldAcpClientDelegate {
|
impl OldAcpClientDelegate {
|
||||||
pub fn new(thread: WeakEntity<AcpThread>, cx: AsyncApp) -> Self {
|
pub fn new(thread: WeakEntity<AcpThread>, cx: AsyncApp) -> Self {
|
||||||
Self { thread, cx }
|
Self {
|
||||||
|
thread,
|
||||||
|
cx,
|
||||||
|
next_tool_call_id: Rc::new(RefCell::new(0)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn clear_completed_plan_entries(&self) -> Result<()> {
|
pub async fn clear_completed_plan_entries(&self) -> Result<()> {
|
||||||
|
@ -1196,10 +1201,11 @@ impl OldAcpClientDelegate {
|
||||||
confirmation: acp_old::ToolCallConfirmation,
|
confirmation: acp_old::ToolCallConfirmation,
|
||||||
) -> Result<acp_old::ToolCallConfirmationOutcome> {
|
) -> Result<acp_old::ToolCallConfirmationOutcome> {
|
||||||
let cx = &mut self.cx.clone();
|
let cx = &mut self.cx.clone();
|
||||||
|
|
||||||
let ToolCallRequest { outcome, .. } = cx
|
let ToolCallRequest { outcome, .. } = cx
|
||||||
.update(|cx| {
|
.update(|cx| {
|
||||||
self.thread.update(cx, |thread, cx| {
|
self.thread.update(cx, |thread, cx| {
|
||||||
thread.request_tool_call_confirmation(tool_call_id, confirmation, cx)
|
thread.request_tool_call_permission(acp_new_tool_call, confirmation, cx)
|
||||||
})
|
})
|
||||||
})?
|
})?
|
||||||
.context("Failed to update thread")??;
|
.context("Failed to update thread")??;
|
||||||
|
@ -1264,10 +1270,26 @@ impl acp_old::Client for OldAcpClientDelegate {
|
||||||
request: acp_old::PushToolCallParams,
|
request: acp_old::PushToolCallParams,
|
||||||
) -> Result<acp_old::PushToolCallResponse, acp_old::Error> {
|
) -> Result<acp_old::PushToolCallResponse, acp_old::Error> {
|
||||||
let cx = &mut self.cx.clone();
|
let cx = &mut self.cx.clone();
|
||||||
|
|
||||||
|
let new_id = acp::ToolCallId(
|
||||||
|
util::post_inc(self.next_tool_call_id.borrow_mut())
|
||||||
|
.to_string()
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let new_tool_call = acp::ToolCall {
|
||||||
|
id: new_id,
|
||||||
|
label: request.label,
|
||||||
|
kind: acp_kind_from_icon(request.icon),
|
||||||
|
status: acp::ToolCallStatus::InProgress,
|
||||||
|
content: into_new_acp_content(request.content),
|
||||||
|
locations: request.locations.into_iter().map(into_new_acp_location).collect(),
|
||||||
|
};
|
||||||
|
|
||||||
let id = cx
|
let id = cx
|
||||||
.update(|cx| {
|
.update(|cx| {
|
||||||
self.thread
|
self.thread
|
||||||
.update(cx, |thread, cx| thread.push_tool_call(request, cx))
|
.update(cx, |thread, cx| thread.update_tool_call(request, cx))
|
||||||
})?
|
})?
|
||||||
.context("Failed to update thread")?;
|
.context("Failed to update thread")?;
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,83 @@
|
||||||
use agentic_coding_protocol as acp_old;
|
use agent_client_protocol as acp;
|
||||||
|
use agentic_coding_protocol::{self as acp_old, AgentRequest};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use futures::future::{FutureExt as _, LocalBoxFuture};
|
use futures::future::{FutureExt as _, LocalBoxFuture};
|
||||||
|
|
||||||
pub trait AgentConnection {
|
pub trait AgentConnection {
|
||||||
fn request_any(
|
fn new_session(
|
||||||
&self,
|
&self,
|
||||||
params: acp_old::AnyAgentRequest,
|
params: acp::NewSessionToolArguments,
|
||||||
) -> LocalBoxFuture<'static, Result<acp_old::AnyAgentResult>>;
|
) -> LocalBoxFuture<'static, Result<acp::SessionId>>;
|
||||||
|
|
||||||
|
fn authenticate(&self) -> LocalBoxFuture<'static, Result<()>>;
|
||||||
|
|
||||||
|
fn prompt(&self, params: acp::PromptToolArguments) -> LocalBoxFuture<'static, Result<()>>;
|
||||||
|
|
||||||
|
fn cancel(&self) -> LocalBoxFuture<'static, Result<()>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AgentConnection for acp_old::AgentConnection {
|
impl AgentConnection for acp_old::AgentConnection {
|
||||||
fn request_any(
|
fn new_session(
|
||||||
&self,
|
&self,
|
||||||
params: acp_old::AnyAgentRequest,
|
_params: acp::NewSessionToolArguments,
|
||||||
) -> LocalBoxFuture<'static, Result<acp_old::AnyAgentResult>> {
|
) -> LocalBoxFuture<'static, Result<acp::SessionId>> {
|
||||||
let task = self.request_any(params);
|
let task = self.request_any(
|
||||||
async move { Ok(task.await?) }.boxed_local()
|
acp_old::InitializeParams {
|
||||||
|
protocol_version: acp_old::ProtocolVersion::latest(),
|
||||||
|
}
|
||||||
|
.into_any(),
|
||||||
|
);
|
||||||
|
async move {
|
||||||
|
let result = task.await?;
|
||||||
|
let result = acp_old::InitializeParams::response_from_any(result)?;
|
||||||
|
|
||||||
|
if !result.is_authenticated {
|
||||||
|
anyhow::bail!("Not authenticated");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(acp::SessionId("acp-old-no-id".into()))
|
||||||
|
}
|
||||||
|
.boxed_local()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn authenticate(&self) -> LocalBoxFuture<'static, Result<()>> {
|
||||||
|
let task = self.request_any(acp_old::AuthenticateParams.into_any());
|
||||||
|
async move {
|
||||||
|
task.await?;
|
||||||
|
anyhow::Ok(())
|
||||||
|
}
|
||||||
|
.boxed_local()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prompt(&self, params: acp::PromptToolArguments) -> LocalBoxFuture<'static, Result<()>> {
|
||||||
|
let chunks = params
|
||||||
|
.prompt
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|block| match block {
|
||||||
|
acp::ContentBlock::Text(text) => {
|
||||||
|
Some(acp_old::UserMessageChunk::Text { text: text.text })
|
||||||
|
}
|
||||||
|
acp::ContentBlock::ResourceLink(link) => Some(acp_old::UserMessageChunk::Path {
|
||||||
|
path: link.uri.into(),
|
||||||
|
}),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let task = self.request_any(acp_old::SendUserMessageParams { chunks }.into_any());
|
||||||
|
async move {
|
||||||
|
task.await?;
|
||||||
|
anyhow::Ok(())
|
||||||
|
}
|
||||||
|
.boxed_local()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cancel(&self) -> LocalBoxFuture<'static, Result<()>> {
|
||||||
|
let task = self.request_any(acp_old::CancelSendMessageParams.into_any());
|
||||||
|
async move {
|
||||||
|
task.await?;
|
||||||
|
anyhow::Ok(())
|
||||||
|
}
|
||||||
|
.boxed_local()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -911,7 +911,7 @@ impl AcpThreadView {
|
||||||
let content = if is_open {
|
let content = if is_open {
|
||||||
match &tool_call.status {
|
match &tool_call.status {
|
||||||
ToolCallStatus::WaitingForConfirmation {
|
ToolCallStatus::WaitingForConfirmation {
|
||||||
possible_grants,
|
options,
|
||||||
respond_tx,
|
respond_tx,
|
||||||
} => {
|
} => {
|
||||||
// Some(self.render_tool_call_confirmation(
|
// Some(self.render_tool_call_confirmation(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue