claude_agent/types/
search.rs

1//! Search result content block for web search citations.
2
3use serde::{Deserialize, Serialize};
4
5use super::citations::CitationsConfig;
6use super::message::CacheControl;
7
8#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
9#[serde(tag = "type", rename_all = "snake_case")]
10pub enum SearchResultContentBlock {
11    Text { text: String },
12}
13
14impl SearchResultContentBlock {
15    pub fn text(content: impl Into<String>) -> Self {
16        Self::Text {
17            text: content.into(),
18        }
19    }
20}
21
22#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
23pub struct SearchResultBlock {
24    pub source: String,
25    pub title: String,
26    pub content: Vec<SearchResultContentBlock>,
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub citations: Option<CitationsConfig>,
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub cache_control: Option<CacheControl>,
31}
32
33impl SearchResultBlock {
34    pub fn new(
35        source: impl Into<String>,
36        title: impl Into<String>,
37        content: impl Into<String>,
38    ) -> Self {
39        Self {
40            source: source.into(),
41            title: title.into(),
42            content: vec![SearchResultContentBlock::Text {
43                text: content.into(),
44            }],
45            citations: None,
46            cache_control: None,
47        }
48    }
49
50    pub fn with_blocks(
51        source: impl Into<String>,
52        title: impl Into<String>,
53        blocks: Vec<SearchResultContentBlock>,
54    ) -> Self {
55        Self {
56            source: source.into(),
57            title: title.into(),
58            content: blocks,
59            citations: None,
60            cache_control: None,
61        }
62    }
63
64    pub fn add_text(mut self, text: impl Into<String>) -> Self {
65        self.content
66            .push(SearchResultContentBlock::Text { text: text.into() });
67        self
68    }
69
70    pub fn with_citations(mut self, enabled: bool) -> Self {
71        self.citations = Some(if enabled {
72            CitationsConfig::enabled()
73        } else {
74            CitationsConfig::disabled()
75        });
76        self
77    }
78
79    pub fn without_citations(mut self) -> Self {
80        self.citations = Some(CitationsConfig::disabled());
81        self
82    }
83
84    pub fn with_cache_control(mut self, cache_control: CacheControl) -> Self {
85        self.cache_control = Some(cache_control);
86        self
87    }
88
89    pub fn cached(mut self) -> Self {
90        self.cache_control = Some(CacheControl::ephemeral());
91        self
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    #[test]
100    fn test_search_result_new() {
101        let result = SearchResultBlock::new("https://example.com", "Example Page", "Some content");
102
103        assert_eq!(result.source, "https://example.com");
104        assert_eq!(result.title, "Example Page");
105        assert_eq!(result.content.len(), 1);
106        assert!(result.citations.is_none());
107    }
108
109    #[test]
110    fn test_search_result_with_citations() {
111        let result = SearchResultBlock::new("https://example.com", "Example Page", "Content")
112            .with_citations(true);
113
114        assert!(result.citations.is_some());
115        assert!(result.citations.unwrap().enabled);
116    }
117
118    #[test]
119    fn test_search_result_multiple_blocks() {
120        let result = SearchResultBlock::new("https://example.com", "Title", "First block")
121            .add_text("Second block")
122            .add_text("Third block");
123
124        assert_eq!(result.content.len(), 3);
125    }
126
127    #[test]
128    fn test_search_result_serialization() {
129        let result = SearchResultBlock::new("https://example.com", "Title", "content");
130
131        let json = serde_json::to_string(&result).unwrap();
132        assert!(json.contains("https://example.com"));
133        assert!(json.contains("content"));
134        assert!(json.contains("\"title\":\"Title\""));
135    }
136}