Wire up find_path tool in agent2 (#35799)

Release Notes:

- N/A

---------

Co-authored-by: Antonio Scandurra <me@as-cii.com>
This commit is contained in:
Ben Brandt
2025-08-07 22:21:26 +00:00
committed by GitHub
co-authored by Antonio Scandurra
parent 11efa32fa7
commit 90fa921756
18 changed files with 669 additions and 247 deletions
+130 -2
View File
@@ -270,14 +270,14 @@ async fn test_tool_authorization(cx: &mut TestAppContext) {
vec![
MessageContent::ToolResult(LanguageModelToolResult {
tool_use_id: tool_call_auth_1.tool_call.id.0.to_string().into(),
tool_name: tool_call_auth_1.tool_call.title.into(),
tool_name: ToolRequiringPermission.name().into(),
is_error: false,
content: "Allowed".into(),
output: None
}),
MessageContent::ToolResult(LanguageModelToolResult {
tool_use_id: tool_call_auth_2.tool_call.id.0.to_string().into(),
tool_name: tool_call_auth_2.tool_call.title.into(),
tool_name: ToolRequiringPermission.name().into(),
is_error: true,
content: "Permission to run tool denied by user".into(),
output: None
@@ -286,6 +286,63 @@ async fn test_tool_authorization(cx: &mut TestAppContext) {
);
}
#[gpui::test]
async fn test_tool_hallucination(cx: &mut TestAppContext) {
let ThreadTest { model, thread, .. } = setup(cx, TestModel::Fake).await;
let fake_model = model.as_fake();
let mut events = thread.update(cx, |thread, cx| thread.send(model.clone(), "abc", cx));
cx.run_until_parked();
fake_model.send_last_completion_stream_event(LanguageModelCompletionEvent::ToolUse(
LanguageModelToolUse {
id: "tool_id_1".into(),
name: "nonexistent_tool".into(),
raw_input: "{}".into(),
input: json!({}),
is_input_complete: true,
},
));
fake_model.end_last_completion_stream();
let tool_call = expect_tool_call(&mut events).await;
assert_eq!(tool_call.title, "nonexistent_tool");
assert_eq!(tool_call.status, acp::ToolCallStatus::Pending);
let update = expect_tool_call_update(&mut events).await;
assert_eq!(update.fields.status, Some(acp::ToolCallStatus::Failed));
}
async fn expect_tool_call(
events: &mut UnboundedReceiver<Result<AgentResponseEvent, LanguageModelCompletionError>>,
) -> acp::ToolCall {
let event = events
.next()
.await
.expect("no tool call authorization event received")
.unwrap();
match event {
AgentResponseEvent::ToolCall(tool_call) => return tool_call,
event => {
panic!("Unexpected event {event:?}");
}
}
}
async fn expect_tool_call_update(
events: &mut UnboundedReceiver<Result<AgentResponseEvent, LanguageModelCompletionError>>,
) -> acp::ToolCallUpdate {
let event = events
.next()
.await
.expect("no tool call authorization event received")
.unwrap();
match event {
AgentResponseEvent::ToolCallUpdate(tool_call_update) => return tool_call_update,
event => {
panic!("Unexpected event {event:?}");
}
}
}
async fn next_tool_call_authorization(
events: &mut UnboundedReceiver<Result<AgentResponseEvent, LanguageModelCompletionError>>,
) -> ToolCallAuthorization {
@@ -582,6 +639,77 @@ async fn test_agent_connection(cx: &mut TestAppContext) {
);
}
#[gpui::test]
async fn test_tool_updates_to_completion(cx: &mut TestAppContext) {
let ThreadTest { thread, model, .. } = setup(cx, TestModel::Fake).await;
thread.update(cx, |thread, _cx| thread.add_tool(ThinkingTool));
let fake_model = model.as_fake();
let mut events = thread.update(cx, |thread, cx| thread.send(model.clone(), "Think", cx));
cx.run_until_parked();
let input = json!({ "content": "Thinking hard!" });
fake_model.send_last_completion_stream_event(LanguageModelCompletionEvent::ToolUse(
LanguageModelToolUse {
id: "1".into(),
name: ThinkingTool.name().into(),
raw_input: input.to_string(),
input,
is_input_complete: true,
},
));
fake_model.end_last_completion_stream();
cx.run_until_parked();
let tool_call = expect_tool_call(&mut events).await;
assert_eq!(
tool_call,
acp::ToolCall {
id: acp::ToolCallId("1".into()),
title: "Thinking".into(),
kind: acp::ToolKind::Think,
status: acp::ToolCallStatus::Pending,
content: vec![],
locations: vec![],
raw_input: Some(json!({ "content": "Thinking hard!" })),
raw_output: None,
}
);
let update = expect_tool_call_update(&mut events).await;
assert_eq!(
update,
acp::ToolCallUpdate {
id: acp::ToolCallId("1".into()),
fields: acp::ToolCallUpdateFields {
status: Some(acp::ToolCallStatus::InProgress,),
..Default::default()
},
}
);
let update = expect_tool_call_update(&mut events).await;
assert_eq!(
update,
acp::ToolCallUpdate {
id: acp::ToolCallId("1".into()),
fields: acp::ToolCallUpdateFields {
content: Some(vec!["Thinking hard!".into()]),
..Default::default()
},
}
);
let update = expect_tool_call_update(&mut events).await;
assert_eq!(
update,
acp::ToolCallUpdate {
id: acp::ToolCallId("1".into()),
fields: acp::ToolCallUpdateFields {
status: Some(acp::ToolCallStatus::Completed),
..Default::default()
},
}
);
}
/// Filters out the stop events for asserting against in tests
fn stop_events(
result_events: Vec<Result<AgentResponseEvent, LanguageModelCompletionError>>,
+65 -17
View File
@@ -19,11 +19,20 @@ impl AgentTool for EchoTool {
"echo".into()
}
fn needs_authorization(&self, _input: Self::Input, _cx: &App) -> bool {
false
fn kind(&self) -> acp::ToolKind {
acp::ToolKind::Other
}
fn run(self: Arc<Self>, input: Self::Input, _cx: &mut App) -> Task<Result<String>> {
fn initial_title(&self, _: Self::Input) -> SharedString {
"Echo".into()
}
fn run(
self: Arc<Self>,
input: Self::Input,
_event_stream: ToolCallEventStream,
_cx: &mut App,
) -> Task<Result<String>> {
Task::ready(Ok(input.text))
}
}
@@ -44,11 +53,20 @@ impl AgentTool for DelayTool {
"delay".into()
}
fn needs_authorization(&self, _input: Self::Input, _cx: &App) -> bool {
false
fn initial_title(&self, input: Self::Input) -> SharedString {
format!("Delay {}ms", input.ms).into()
}
fn run(self: Arc<Self>, input: Self::Input, cx: &mut App) -> Task<Result<String>>
fn kind(&self) -> acp::ToolKind {
acp::ToolKind::Other
}
fn run(
self: Arc<Self>,
input: Self::Input,
_event_stream: ToolCallEventStream,
cx: &mut App,
) -> Task<Result<String>>
where
Self: Sized,
{
@@ -71,16 +89,28 @@ impl AgentTool for ToolRequiringPermission {
"tool_requiring_permission".into()
}
fn needs_authorization(&self, _input: Self::Input, _cx: &App) -> bool {
true
fn kind(&self) -> acp::ToolKind {
acp::ToolKind::Other
}
fn run(self: Arc<Self>, _input: Self::Input, cx: &mut App) -> Task<Result<String>>
fn initial_title(&self, _input: Self::Input) -> SharedString {
"This tool requires permission".into()
}
fn run(
self: Arc<Self>,
input: Self::Input,
event_stream: ToolCallEventStream,
cx: &mut App,
) -> Task<Result<String>>
where
Self: Sized,
{
cx.foreground_executor()
.spawn(async move { Ok("Allowed".to_string()) })
let auth_check = self.authorize(input, event_stream);
cx.foreground_executor().spawn(async move {
auth_check.await?;
Ok("Allowed".to_string())
})
}
}
@@ -96,11 +126,20 @@ impl AgentTool for InfiniteTool {
"infinite".into()
}
fn needs_authorization(&self, _input: Self::Input, _cx: &App) -> bool {
false
fn kind(&self) -> acp::ToolKind {
acp::ToolKind::Other
}
fn run(self: Arc<Self>, _input: Self::Input, cx: &mut App) -> Task<Result<String>> {
fn initial_title(&self, _input: Self::Input) -> SharedString {
"This is the tool that never ends... it just goes on and on my friends!".into()
}
fn run(
self: Arc<Self>,
_input: Self::Input,
_event_stream: ToolCallEventStream,
cx: &mut App,
) -> Task<Result<String>> {
cx.foreground_executor().spawn(async move {
future::pending::<()>().await;
unreachable!()
@@ -137,11 +176,20 @@ impl AgentTool for WordListTool {
"word_list".into()
}
fn needs_authorization(&self, _input: Self::Input, _cx: &App) -> bool {
false
fn initial_title(&self, _input: Self::Input) -> SharedString {
"List of random words".into()
}
fn run(self: Arc<Self>, _input: Self::Input, _cx: &mut App) -> Task<Result<String>> {
fn kind(&self) -> acp::ToolKind {
acp::ToolKind::Other
}
fn run(
self: Arc<Self>,
_input: Self::Input,
_event_stream: ToolCallEventStream,
_cx: &mut App,
) -> Task<Result<String>> {
Task::ready(Ok("ok".to_string()))
}
}