agent2: Add now, grep, and web search tools (#35974)
Release Notes: - N/A --------- Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de> Co-authored-by: Antonio Scandurra <me@as-cii.com>
This commit is contained in:
parent
ebcce8730d
commit
8dbded46d8
8 changed files with 1390 additions and 4 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -191,6 +191,7 @@ dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"assistant_tool",
|
"assistant_tool",
|
||||||
"assistant_tools",
|
"assistant_tools",
|
||||||
|
"chrono",
|
||||||
"client",
|
"client",
|
||||||
"clock",
|
"clock",
|
||||||
"cloud_llm_client",
|
"cloud_llm_client",
|
||||||
|
@ -227,10 +228,13 @@ dependencies = [
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"terminal",
|
"terminal",
|
||||||
"theme",
|
"theme",
|
||||||
|
"tree-sitter-rust",
|
||||||
"ui",
|
"ui",
|
||||||
|
"unindent",
|
||||||
"util",
|
"util",
|
||||||
"uuid",
|
"uuid",
|
||||||
"watch",
|
"watch",
|
||||||
|
"web_search",
|
||||||
"which 6.0.3",
|
"which 6.0.3",
|
||||||
"workspace-hack",
|
"workspace-hack",
|
||||||
"worktree",
|
"worktree",
|
||||||
|
|
|
@ -20,6 +20,7 @@ agent_settings.workspace = true
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
assistant_tool.workspace = true
|
assistant_tool.workspace = true
|
||||||
assistant_tools.workspace = true
|
assistant_tools.workspace = true
|
||||||
|
chrono.workspace = true
|
||||||
cloud_llm_client.workspace = true
|
cloud_llm_client.workspace = true
|
||||||
collections.workspace = true
|
collections.workspace = true
|
||||||
fs.workspace = true
|
fs.workspace = true
|
||||||
|
@ -49,6 +50,7 @@ ui.workspace = true
|
||||||
util.workspace = true
|
util.workspace = true
|
||||||
uuid.workspace = true
|
uuid.workspace = true
|
||||||
watch.workspace = true
|
watch.workspace = true
|
||||||
|
web_search.workspace = true
|
||||||
which.workspace = true
|
which.workspace = true
|
||||||
workspace-hack.workspace = true
|
workspace-hack.workspace = true
|
||||||
|
|
||||||
|
@ -71,5 +73,7 @@ settings = { workspace = true, "features" = ["test-support"] }
|
||||||
tempfile.workspace = true
|
tempfile.workspace = true
|
||||||
terminal = { workspace = true, "features" = ["test-support"] }
|
terminal = { workspace = true, "features" = ["test-support"] }
|
||||||
theme = { workspace = true, "features" = ["test-support"] }
|
theme = { workspace = true, "features" = ["test-support"] }
|
||||||
|
tree-sitter-rust.workspace = true
|
||||||
|
unindent = { workspace = true }
|
||||||
worktree = { workspace = true, "features" = ["test-support"] }
|
worktree = { workspace = true, "features" = ["test-support"] }
|
||||||
zlog.workspace = true
|
zlog.workspace = true
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
use crate::{AgentResponseEvent, Thread, templates::Templates};
|
use crate::{AgentResponseEvent, Thread, templates::Templates};
|
||||||
use crate::{
|
use crate::{
|
||||||
CopyPathTool, CreateDirectoryTool, EditFileTool, FindPathTool, ListDirectoryTool, MovePathTool,
|
CopyPathTool, CreateDirectoryTool, EditFileTool, FindPathTool, GrepTool, ListDirectoryTool,
|
||||||
OpenTool, ReadFileTool, TerminalTool, ThinkingTool, ToolCallAuthorization,
|
MovePathTool, NowTool, OpenTool, ReadFileTool, TerminalTool, ThinkingTool,
|
||||||
|
ToolCallAuthorization, WebSearchTool,
|
||||||
};
|
};
|
||||||
use acp_thread::ModelSelector;
|
use acp_thread::ModelSelector;
|
||||||
use agent_client_protocol as acp;
|
use agent_client_protocol as acp;
|
||||||
|
@ -424,9 +425,13 @@ impl acp_thread::AgentConnection for NativeAgentConnection {
|
||||||
thread.add_tool(OpenTool::new(project.clone()));
|
thread.add_tool(OpenTool::new(project.clone()));
|
||||||
thread.add_tool(ThinkingTool);
|
thread.add_tool(ThinkingTool);
|
||||||
thread.add_tool(FindPathTool::new(project.clone()));
|
thread.add_tool(FindPathTool::new(project.clone()));
|
||||||
|
thread.add_tool(GrepTool::new(project.clone()));
|
||||||
thread.add_tool(ReadFileTool::new(project.clone(), action_log));
|
thread.add_tool(ReadFileTool::new(project.clone(), action_log));
|
||||||
thread.add_tool(EditFileTool::new(cx.entity()));
|
thread.add_tool(EditFileTool::new(cx.entity()));
|
||||||
|
thread.add_tool(NowTool);
|
||||||
thread.add_tool(TerminalTool::new(project.clone(), cx));
|
thread.add_tool(TerminalTool::new(project.clone(), cx));
|
||||||
|
// TODO: Needs to be conditional based on zed model or not
|
||||||
|
thread.add_tool(WebSearchTool);
|
||||||
thread
|
thread
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -3,21 +3,27 @@ mod create_directory_tool;
|
||||||
mod delete_path_tool;
|
mod delete_path_tool;
|
||||||
mod edit_file_tool;
|
mod edit_file_tool;
|
||||||
mod find_path_tool;
|
mod find_path_tool;
|
||||||
|
mod grep_tool;
|
||||||
mod list_directory_tool;
|
mod list_directory_tool;
|
||||||
mod move_path_tool;
|
mod move_path_tool;
|
||||||
|
mod now_tool;
|
||||||
mod open_tool;
|
mod open_tool;
|
||||||
mod read_file_tool;
|
mod read_file_tool;
|
||||||
mod terminal_tool;
|
mod terminal_tool;
|
||||||
mod thinking_tool;
|
mod thinking_tool;
|
||||||
|
mod web_search_tool;
|
||||||
|
|
||||||
pub use copy_path_tool::*;
|
pub use copy_path_tool::*;
|
||||||
pub use create_directory_tool::*;
|
pub use create_directory_tool::*;
|
||||||
pub use delete_path_tool::*;
|
pub use delete_path_tool::*;
|
||||||
pub use edit_file_tool::*;
|
pub use edit_file_tool::*;
|
||||||
pub use find_path_tool::*;
|
pub use find_path_tool::*;
|
||||||
|
pub use grep_tool::*;
|
||||||
pub use list_directory_tool::*;
|
pub use list_directory_tool::*;
|
||||||
pub use move_path_tool::*;
|
pub use move_path_tool::*;
|
||||||
|
pub use now_tool::*;
|
||||||
pub use open_tool::*;
|
pub use open_tool::*;
|
||||||
pub use read_file_tool::*;
|
pub use read_file_tool::*;
|
||||||
pub use terminal_tool::*;
|
pub use terminal_tool::*;
|
||||||
pub use thinking_tool::*;
|
pub use thinking_tool::*;
|
||||||
|
pub use web_search_tool::*;
|
||||||
|
|
1196
crates/agent2/src/tools/grep_tool.rs
Normal file
1196
crates/agent2/src/tools/grep_tool.rs
Normal file
File diff suppressed because it is too large
Load diff
66
crates/agent2/src/tools/now_tool.rs
Normal file
66
crates/agent2/src/tools/now_tool.rs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use agent_client_protocol as acp;
|
||||||
|
use anyhow::Result;
|
||||||
|
use chrono::{Local, Utc};
|
||||||
|
use gpui::{App, SharedString, Task};
|
||||||
|
use schemars::JsonSchema;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{AgentTool, ToolCallEventStream};
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum Timezone {
|
||||||
|
/// Use UTC for the datetime.
|
||||||
|
Utc,
|
||||||
|
/// Use local time for the datetime.
|
||||||
|
Local,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the current datetime in RFC 3339 format.
|
||||||
|
/// Only use this tool when the user specifically asks for it or the current task would benefit from knowing the current datetime.
|
||||||
|
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||||
|
pub struct NowToolInput {
|
||||||
|
/// The timezone to use for the datetime.
|
||||||
|
timezone: Timezone,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct NowTool;
|
||||||
|
|
||||||
|
impl AgentTool for NowTool {
|
||||||
|
type Input = NowToolInput;
|
||||||
|
type Output = String;
|
||||||
|
|
||||||
|
fn name(&self) -> SharedString {
|
||||||
|
"now".into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn kind(&self) -> acp::ToolKind {
|
||||||
|
acp::ToolKind::Other
|
||||||
|
}
|
||||||
|
|
||||||
|
fn initial_title(&self, _input: Result<Self::Input, serde_json::Value>) -> SharedString {
|
||||||
|
"Get current time".into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
self: Arc<Self>,
|
||||||
|
input: Self::Input,
|
||||||
|
event_stream: ToolCallEventStream,
|
||||||
|
_cx: &mut App,
|
||||||
|
) -> Task<Result<String>> {
|
||||||
|
let now = match input.timezone {
|
||||||
|
Timezone::Utc => Utc::now().to_rfc3339(),
|
||||||
|
Timezone::Local => Local::now().to_rfc3339(),
|
||||||
|
};
|
||||||
|
let content = format!("The current datetime is {now}.");
|
||||||
|
|
||||||
|
event_stream.update_fields(acp::ToolCallUpdateFields {
|
||||||
|
content: Some(vec![content.clone().into()]),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
Task::ready(Ok(content))
|
||||||
|
}
|
||||||
|
}
|
105
crates/agent2/src/tools/web_search_tool.rs
Normal file
105
crates/agent2/src/tools/web_search_tool.rs
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::{AgentTool, ToolCallEventStream};
|
||||||
|
use agent_client_protocol as acp;
|
||||||
|
use anyhow::{Result, anyhow};
|
||||||
|
use cloud_llm_client::WebSearchResponse;
|
||||||
|
use gpui::{App, AppContext, Task};
|
||||||
|
use language_model::LanguageModelToolResultContent;
|
||||||
|
use schemars::JsonSchema;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use ui::prelude::*;
|
||||||
|
use web_search::WebSearchRegistry;
|
||||||
|
|
||||||
|
/// Search the web for information using your query.
|
||||||
|
/// Use this when you need real-time information, facts, or data that might not be in your training. \
|
||||||
|
/// Results will include snippets and links from relevant web pages.
|
||||||
|
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||||
|
pub struct WebSearchToolInput {
|
||||||
|
/// The search term or question to query on the web.
|
||||||
|
query: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(transparent)]
|
||||||
|
pub struct WebSearchToolOutput(WebSearchResponse);
|
||||||
|
|
||||||
|
impl From<WebSearchToolOutput> for LanguageModelToolResultContent {
|
||||||
|
fn from(value: WebSearchToolOutput) -> Self {
|
||||||
|
serde_json::to_string(&value.0)
|
||||||
|
.expect("Failed to serialize WebSearchResponse")
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct WebSearchTool;
|
||||||
|
|
||||||
|
impl AgentTool for WebSearchTool {
|
||||||
|
type Input = WebSearchToolInput;
|
||||||
|
type Output = WebSearchToolOutput;
|
||||||
|
|
||||||
|
fn name(&self) -> SharedString {
|
||||||
|
"web_search".into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn kind(&self) -> acp::ToolKind {
|
||||||
|
acp::ToolKind::Fetch
|
||||||
|
}
|
||||||
|
|
||||||
|
fn initial_title(&self, _input: Result<Self::Input, serde_json::Value>) -> SharedString {
|
||||||
|
"Searching the Web".into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
self: Arc<Self>,
|
||||||
|
input: Self::Input,
|
||||||
|
event_stream: ToolCallEventStream,
|
||||||
|
cx: &mut App,
|
||||||
|
) -> Task<Result<Self::Output>> {
|
||||||
|
let Some(provider) = WebSearchRegistry::read_global(cx).active_provider() else {
|
||||||
|
return Task::ready(Err(anyhow!("Web search is not available.")));
|
||||||
|
};
|
||||||
|
|
||||||
|
let search_task = provider.search(input.query, cx);
|
||||||
|
cx.background_spawn(async move {
|
||||||
|
let response = match search_task.await {
|
||||||
|
Ok(response) => response,
|
||||||
|
Err(err) => {
|
||||||
|
event_stream.update_fields(acp::ToolCallUpdateFields {
|
||||||
|
title: Some("Web Search Failed".to_string()),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let result_text = if response.results.len() == 1 {
|
||||||
|
"1 result".to_string()
|
||||||
|
} else {
|
||||||
|
format!("{} results", response.results.len())
|
||||||
|
};
|
||||||
|
event_stream.update_fields(acp::ToolCallUpdateFields {
|
||||||
|
title: Some(format!("Searched the web: {result_text}")),
|
||||||
|
content: Some(
|
||||||
|
response
|
||||||
|
.results
|
||||||
|
.iter()
|
||||||
|
.map(|result| acp::ToolCallContent::Content {
|
||||||
|
content: acp::ContentBlock::ResourceLink(acp::ResourceLink {
|
||||||
|
name: result.title.clone(),
|
||||||
|
uri: result.url.clone(),
|
||||||
|
title: Some(result.title.clone()),
|
||||||
|
description: Some(result.text.clone()),
|
||||||
|
mime_type: None,
|
||||||
|
annotations: None,
|
||||||
|
size: None,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
Ok(WebSearchToolOutput(response))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -263,12 +263,12 @@ pub struct WebSearchBody {
|
||||||
pub query: String,
|
pub query: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct WebSearchResponse {
|
pub struct WebSearchResponse {
|
||||||
pub results: Vec<WebSearchResult>,
|
pub results: Vec<WebSearchResult>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct WebSearchResult {
|
pub struct WebSearchResult {
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub url: String,
|
pub url: String,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue