Skip to content

Commit 6815506

Browse files
committed
implement Claude's web-search tool; remove DuckDuckGo
1 parent 5179449 commit 6815506

9 files changed

Lines changed: 139 additions & 248 deletions

File tree

Cargo.lock

Lines changed: 0 additions & 23 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@ syntect = "5.2"
2727
# Utilities
2828
futures = "0.3"
2929
pathdiff = "0.2"
30-
urlencoding = "2.1"
31-
html-escape = "0.2"
3230

3331
# Logging (optional but helpful)
3432
tracing = "0.1"

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ A blazingly fast, interactive AI coding assistant powered by Claude, implemented
1212
- **File Operations** - Read, write, list, and create files/directories (sandboxed to current directory)
1313
- **Ultra-Fast Editing** - Optional Morph Apply integration (10,500+ tokens/sec, 96-98% accuracy)
1414
- **Code Search** - Fast regex-based code search using `ripgrep` (optional)
15-
- **Web Search** - Real-time information via DuckDuckGo
15+
- **Web Search** - Real-time web information via Claude's native search tool
1616
- **Bash Execution** - Run tests and build commands safely (read-only, sandboxed)
1717
- **Secure** - All operations restricted to workspace, prevents directory traversal
1818

@@ -97,7 +97,7 @@ Claude can automatically use these tools:
9797

9898
**Code & Search:**
9999
- `search_code` - Fast regex-based code search (requires `ripgrep`)
100-
- `web_search` - Search the internet
100+
- `web_search` - Search the web for current information (Claude's server-side tool)
101101
- `execute_bash` - Run tests and build commands (read-only, sandboxed)
102102

103103
## Security

src/api/types.rs

Lines changed: 70 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,24 @@ pub struct CreateMessageRequest {
5555
}
5656

5757
#[derive(Debug, Clone, Serialize, Deserialize)]
58-
pub struct Tool {
59-
pub name: String,
60-
pub description: String,
61-
pub input_schema: serde_json::Value,
58+
#[serde(untagged)]
59+
pub enum Tool {
60+
Regular {
61+
name: String,
62+
description: String,
63+
input_schema: serde_json::Value,
64+
},
65+
WebSearch {
66+
#[serde(rename = "type")]
67+
tool_type: String,
68+
name: String,
69+
#[serde(skip_serializing_if = "Option::is_none")]
70+
max_uses: Option<u32>,
71+
#[serde(skip_serializing_if = "Option::is_none")]
72+
allowed_domains: Option<Vec<String>>,
73+
#[serde(skip_serializing_if = "Option::is_none")]
74+
blocked_domains: Option<Vec<String>>,
75+
},
6276
}
6377

6478
#[derive(Debug, Deserialize)]
@@ -89,6 +103,27 @@ pub enum ContentBlock {
89103
name: String,
90104
input: serde_json::Value,
91105
},
106+
#[serde(rename = "server_tool_use")]
107+
ServerToolUse {
108+
id: String,
109+
name: String,
110+
input: serde_json::Value,
111+
},
112+
#[serde(rename = "web_search_tool_result")]
113+
WebSearchToolResult {
114+
tool_use_id: String,
115+
content: Vec<WebSearchResult>,
116+
},
117+
}
118+
119+
#[derive(Debug, Clone, Serialize, Deserialize)]
120+
pub struct WebSearchResult {
121+
#[serde(rename = "type")]
122+
pub result_type: String,
123+
pub url: String,
124+
pub title: String,
125+
pub encrypted_content: String,
126+
pub page_age: Option<String>,
92127
}
93128

94129
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -107,6 +142,17 @@ pub enum MessageContentBlock {
107142
tool_use_id: String,
108143
content: String,
109144
},
145+
#[serde(rename = "server_tool_use")]
146+
ServerToolUse {
147+
id: String,
148+
name: String,
149+
input: serde_json::Value,
150+
},
151+
#[serde(rename = "web_search_tool_result")]
152+
WebSearchToolResult {
153+
tool_use_id: String,
154+
content: Vec<WebSearchResult>,
155+
},
110156
}
111157

112158
impl MessageContentBlock {
@@ -118,10 +164,30 @@ impl MessageContentBlock {
118164
name: name.clone(),
119165
input: input.clone(),
120166
},
167+
ContentBlock::ServerToolUse { id, name, input } => MessageContentBlock::ServerToolUse {
168+
id: id.clone(),
169+
name: name.clone(),
170+
input: input.clone(),
171+
},
172+
ContentBlock::WebSearchToolResult { tool_use_id, content } => {
173+
MessageContentBlock::WebSearchToolResult {
174+
tool_use_id: tool_use_id.clone(),
175+
content: content.clone(),
176+
}
177+
}
121178
}
122179
}
123180
}
124181

182+
#[derive(Debug, Clone, Serialize, Deserialize)]
183+
#[serde(tag = "type")]
184+
pub enum _ContentDelta {
185+
#[serde(rename = "text_delta")]
186+
TextDelta { text: String },
187+
#[serde(rename = "input_json_delta")]
188+
InputJsonDelta { partial_json: String },
189+
}
190+
125191
#[derive(Debug, Deserialize)]
126192
pub struct Usage {
127193
#[serde(rename = "input_tokens")]
@@ -160,12 +226,3 @@ pub enum _StreamEventType {
160226
#[serde(rename = "ping")]
161227
Ping,
162228
}
163-
164-
#[derive(Debug, Clone, Deserialize)]
165-
#[serde(tag = "type")]
166-
pub enum _ContentDelta {
167-
#[serde(rename = "text_delta")]
168-
TextDelta { text: String },
169-
#[serde(rename = "input_json_delta")]
170-
InputJsonDelta { partial_json: String },
171-
}

src/error.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,6 @@ pub enum SofosError {
2323
#[error("File not found: {0}")]
2424
FileNotFound(String),
2525

26-
#[error("Search error: {0}")]
27-
Search(String),
28-
2926
#[error("Configuration error: {0}")]
3027
Config(String),
3128

src/repl.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,17 @@ impl Repl {
181181
ContentBlock::ToolUse { id, name, input } => {
182182
tool_uses.push((id.clone(), name.clone(), input.clone()));
183183
}
184+
ContentBlock::ServerToolUse { name, input, .. } => {
185+
// Server-side tools (like web_search) are executed by Claude API
186+
if std::env::var("SOFOS_DEBUG").is_ok() {
187+
eprintln!("Server tool use: {} with input: {:?}", name, input);
188+
}
189+
}
190+
ContentBlock::WebSearchToolResult { content, .. } => {
191+
if !content.is_empty() {
192+
text_output.push(format!("\n[Web search returned {} results]", content.len()));
193+
}
194+
}
184195
}
185196
}
186197

@@ -419,6 +430,12 @@ impl Repl {
419430
ContentBlock::ToolUse { name, .. } => {
420431
eprintln!(" Block {}: ToolUse({})", i, name)
421432
}
433+
ContentBlock::ServerToolUse { name, .. } => {
434+
eprintln!(" Block {}: ServerToolUse({})", i, name)
435+
}
436+
ContentBlock::WebSearchToolResult { content, .. } => {
437+
eprintln!(" Block {}: WebSearchToolResult({} results)", i, content.len())
438+
}
422439
}
423440
}
424441
}

src/tools/mod.rs

Lines changed: 1 addition & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
pub mod bashexec;
22
pub mod codesearch;
33
pub mod filesystem;
4-
pub mod search;
54
pub mod types;
65

76
use crate::api::MorphClient;
87
use crate::error::{Result, SofosError};
98
use bashexec::BashExecutor;
109
use codesearch::CodeSearchTool;
1110
use filesystem::FileSystemTool;
12-
use search::WebSearchTool;
1311
use serde_json::Value;
1412
use std::io::{self, Write};
1513

@@ -29,7 +27,6 @@ fn confirm_action(prompt: &str) -> Result<bool> {
2927
/// ToolExecutor handles execution of tool calls from Claude
3028
pub struct ToolExecutor {
3129
fs_tool: FileSystemTool,
32-
search_tool: WebSearchTool,
3330
code_search_tool: Option<CodeSearchTool>,
3431
bash_executor: BashExecutor,
3532
morph_client: Option<MorphClient>,
@@ -47,7 +44,6 @@ impl ToolExecutor {
4744

4845
Ok(Self {
4946
fs_tool: FileSystemTool::new(workspace.clone())?,
50-
search_tool: WebSearchTool::new()?,
5147
code_search_tool,
5248
bash_executor: BashExecutor::new(workspace)?,
5349
morph_client,
@@ -114,34 +110,6 @@ impl ToolExecutor {
114110
self.fs_tool.create_directory(path)?;
115111
Ok(format!("Successfully created directory '{}'", path))
116112
}
117-
"web_search" => {
118-
let query = input["query"].as_str().ok_or_else(|| {
119-
SofosError::ToolExecution("Missing 'query' parameter".to_string())
120-
})?;
121-
let max_results = input["max_results"].as_u64().unwrap_or(5) as usize;
122-
123-
let results = self.search_tool.search(query, max_results).await?;
124-
125-
if results.is_empty() {
126-
Ok(format!("No search results found for '{}'", query))
127-
} else {
128-
let formatted = results
129-
.iter()
130-
.enumerate()
131-
.map(|(i, r)| {
132-
format!(
133-
"{}. {}\n URL: {}\n {}",
134-
i + 1,
135-
r.title,
136-
r.url,
137-
r.snippet
138-
)
139-
})
140-
.collect::<Vec<_>>()
141-
.join("\n\n");
142-
Ok(format!("Search results for '{}':\n\n{}", query, formatted))
143-
}
144-
}
145113
"search_code" => {
146114
let code_search = self.code_search_tool.as_ref()
147115
.ok_or_else(|| SofosError::ToolExecution(
@@ -258,6 +226,7 @@ impl ToolExecutor {
258226
let result = self.bash_executor.execute(command)?;
259227
Ok(result)
260228
}
229+
// web_search is now handled server-side by Claude API, not by ToolExecutor
261230
_ => Err(SofosError::ToolExecution(format!(
262231
"Unknown tool: {}",
263232
tool_name

0 commit comments

Comments
 (0)