agent: Switch to new web search provider (#29951)

Bennet Bo Fenner created

Release Notes:

- N/A

Change summary

Cargo.lock                                    |   4 
Cargo.toml                                    |   2 
crates/assistant_tools/src/web_search_tool.rs | 120 ++++++++++----------
crates/web_search_providers/src/cloud.rs      |   5 
4 files changed, 66 insertions(+), 65 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -18879,9 +18879,9 @@ dependencies = [
 
 [[package]]
 name = "zed_llm_client"
-version = "0.7.4"
+version = "0.7.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2adf9bc80def4ec93c190f06eb78111865edc2576019a9753eaef6fd7bc3b72c"
+checksum = "6fe0d60001c02d0d21a4114a13bee3a905fbb9e146ada80a90435c05fda18852"
 dependencies = [
  "anyhow",
  "serde",

Cargo.toml 🔗

@@ -610,7 +610,7 @@ wasmtime-wasi = "29"
 which = "6.0.0"
 wit-component = "0.221"
 workspace-hack = "0.1.0"
-zed_llm_client = "0.7.4"
+zed_llm_client = "0.7.5"
 zstd = "0.11"
 
 [workspace.dependencies.async-stripe]

crates/assistant_tools/src/web_search_tool.rs 🔗

@@ -15,7 +15,7 @@ use serde::{Deserialize, Serialize};
 use ui::{IconName, Tooltip, prelude::*};
 use web_search::WebSearchRegistry;
 use workspace::Workspace;
-use zed_llm_client::{WebSearchCitation, WebSearchResponse};
+use zed_llm_client::{WebSearchResponse, WebSearchResult};
 
 #[derive(Debug, Serialize, Deserialize, JsonSchema)]
 pub struct WebSearchToolInput {
@@ -120,10 +120,10 @@ impl ToolCard for WebSearchToolCard {
     ) -> impl IntoElement {
         let header = match self.response.as_ref() {
             Some(Ok(response)) => {
-                let text: SharedString = if response.citations.len() == 1 {
+                let text: SharedString = if response.results.len() == 1 {
                     "1 result".into()
                 } else {
-                    format!("{} results", response.citations.len()).into()
+                    format!("{} results", response.results.len()).into()
                 };
                 ToolCallCardHeader::new(IconName::Globe, "Searched the Web")
                     .with_secondary_text(text)
@@ -134,52 +134,47 @@ impl ToolCard for WebSearchToolCard {
             None => ToolCallCardHeader::new(IconName::Globe, "Searching the Web").loading(),
         };
 
-        let content =
-            self.response.as_ref().and_then(|response| match response {
-                Ok(response) => {
-                    Some(
-                        v_flex()
-                            .overflow_hidden()
-                            .ml_1p5()
-                            .pl(px(5.))
-                            .border_l_1()
-                            .border_color(cx.theme().colors().border_variant)
-                            .gap_1()
-                            .children(response.citations.iter().enumerate().map(
-                                |(index, citation)| {
-                                    let title = citation.title.clone();
-                                    let url = citation.url.clone();
+        let content = self.response.as_ref().and_then(|response| match response {
+            Ok(response) => Some(
+                v_flex()
+                    .overflow_hidden()
+                    .ml_1p5()
+                    .pl(px(5.))
+                    .border_l_1()
+                    .border_color(cx.theme().colors().border_variant)
+                    .gap_1()
+                    .children(response.results.iter().enumerate().map(|(index, result)| {
+                        let title = result.title.clone();
+                        let url = result.url.clone();
 
-                                    Button::new(("citation", index), title)
-                                        .label_size(LabelSize::Small)
-                                        .color(Color::Muted)
-                                        .icon(IconName::ArrowUpRight)
-                                        .icon_size(IconSize::XSmall)
-                                        .icon_position(IconPosition::End)
-                                        .truncate(true)
-                                        .tooltip({
-                                            let url = url.clone();
-                                            move |window, cx| {
-                                                Tooltip::with_meta(
-                                                    "Citation Link",
-                                                    None,
-                                                    url.clone(),
-                                                    window,
-                                                    cx,
-                                                )
-                                            }
-                                        })
-                                        .on_click({
-                                            let url = url.clone();
-                                            move |_, _, cx| cx.open_url(&url)
-                                        })
-                                },
-                            ))
-                            .into_any(),
-                    )
-                }
-                Err(_) => None,
-            });
+                        Button::new(("result", index), title)
+                            .label_size(LabelSize::Small)
+                            .color(Color::Muted)
+                            .icon(IconName::ArrowUpRight)
+                            .icon_size(IconSize::XSmall)
+                            .icon_position(IconPosition::End)
+                            .truncate(true)
+                            .tooltip({
+                                let url = url.clone();
+                                move |window, cx| {
+                                    Tooltip::with_meta(
+                                        "Web Search Result",
+                                        None,
+                                        url.clone(),
+                                        window,
+                                        cx,
+                                    )
+                                }
+                            })
+                            .on_click({
+                                let url = url.clone();
+                                move |_, _, cx| cx.open_url(&url)
+                            })
+                    }))
+                    .into_any(),
+            ),
+            Err(_) => None,
+        });
 
         v_flex().mb_3().gap_1().child(header).children(content)
     }
@@ -269,36 +264,39 @@ impl Component for WebSearchToolCard {
 
 fn example_search_response() -> WebSearchResponse {
     WebSearchResponse {
-        summary: r#"Toronto boasts a vibrant culinary scene with a diverse array of..."#
-            .to_string(),
-        citations: vec![
-            WebSearchCitation {
+        results: vec![
+            WebSearchResult {
+                title: "Alo".to_string(),
+                url: "https://www.google.com/maps/search/Alo%2C+Toronto%2C+Canada".to_string(),
+                text: "Alo is a popular restaurant in Toronto.".to_string(),
+            },
+            WebSearchResult {
                 title: "Alo".to_string(),
                 url: "https://www.google.com/maps/search/Alo%2C+Toronto%2C+Canada".to_string(),
-                range: Some(147..213),
+                text: "Information about Alo restaurant in Toronto.".to_string(),
             },
-            WebSearchCitation {
+            WebSearchResult {
                 title: "Edulis".to_string(),
                 url: "https://www.google.com/maps/search/Edulis%2C+Toronto%2C+Canada".to_string(),
-                range: Some(447..519),
+                text: "Details about Edulis restaurant in Toronto.".to_string(),
             },
-            WebSearchCitation {
+            WebSearchResult {
                 title: "Sushi Masaki Saito".to_string(),
                 url: "https://www.google.com/maps/search/Sushi+Masaki+Saito%2C+Toronto%2C+Canada"
                     .to_string(),
-                range: Some(776..872),
+                text: "Information about Sushi Masaki Saito in Toronto.".to_string(),
             },
-            WebSearchCitation {
+            WebSearchResult {
                 title: "Shoushin".to_string(),
                 url: "https://www.google.com/maps/search/Shoushin%2C+Toronto%2C+Canada".to_string(),
-                range: Some(1072..1148),
+                text: "Details about Shoushin restaurant in Toronto.".to_string(),
             },
-            WebSearchCitation {
+            WebSearchResult {
                 title: "Restaurant 20 Victoria".to_string(),
                 url:
                     "https://www.google.com/maps/search/Restaurant+20+Victoria%2C+Toronto%2C+Canada"
                         .to_string(),
-                range: Some(1291..1395),
+                text: "Information about Restaurant 20 Victoria in Toronto.".to_string(),
             },
         ],
     }

crates/web_search_providers/src/cloud.rs 🔗

@@ -7,7 +7,9 @@ use gpui::{App, AppContext, Context, Entity, Subscription, Task};
 use http_client::{HttpClient, Method};
 use language_model::{LlmApiToken, RefreshLlmTokenListener};
 use web_search::{WebSearchProvider, WebSearchProviderId};
-use zed_llm_client::{WebSearchBody, WebSearchResponse};
+use zed_llm_client::{
+    CLIENT_SUPPORTS_EXA_WEB_SEARCH_PROVIDER_HEADER_NAME, WebSearchBody, WebSearchResponse,
+};
 
 pub struct CloudWebSearchProvider {
     state: Entity<State>,
@@ -84,6 +86,7 @@ async fn perform_web_search(
     let request = request_builder
         .header("Content-Type", "application/json")
         .header("Authorization", format!("Bearer {token}"))
+        .header(CLIENT_SUPPORTS_EXA_WEB_SEARCH_PROVIDER_HEADER_NAME, "true")
         .body(serde_json::to_string(&body)?.into())?;
     let mut response = http_client
         .send(request)