Files
shockbot/docs/websearch.md
T
Colin McDonnell 5fa8c3603d Add writeups
2026-01-09 16:03:25 -08:00

436 lines
13 KiB
Markdown

# Web Search Functionality by Agent
This document describes how each supported agent implements web search functionality.
## Summary
| Agent | Tool Name | Search Provider | API/Method |
|-------|-----------|-----------------|------------|
| Claude Code | `WebSearch` | Anthropic internal | Claude Code SDK |
| Gemini CLI | `google_web_search` | Google Search via Gemini API | `generateContent` with `model: 'web-search'` |
| OpenCode | `websearch` | Exa AI | MCP protocol to `https://mcp.exa.ai/mcp` |
All three agents also support a separate **web fetch** tool for directly retrieving and parsing web page content.
---
## Claude Code
### Tools
- `WebSearch` - Search the web for information
- `WebFetch` - Fetch and process web content
### Implementation
Native functionality through `@anthropic-ai/claude-agent-sdk` (closed source). The actual search provider is internal to Anthropic's infrastructure.
### Configuration in Pullfrog
Web search can be disabled via the `disallowedTools` option:
```typescript
// In sandbox mode, web tools are disabled
disallowedTools: ["Bash", "WebSearch", "WebFetch", "Write"]
```
---
## Gemini CLI
### Tools
- `google_web_search` - Perform web searches using Google Search
- `web_fetch` - Fetch and process content from URLs
### Implementation
Source: [`packages/core/src/tools/web-search.ts`](https://github.com/google-gemini/gemini-cli/blob/main/packages/core/src/tools/web-search.ts)
**How it works:**
1. Sends query to Gemini API using `generateContent` with `model: 'web-search'`
2. Google performs the search and returns results with grounding metadata
3. Response includes inline citations, source URLs, and titles
```typescript
const response = await geminiClient.generateContent(
{ model: 'web-search' },
[{ role: 'user', parts: [{ text: this.params.query }] }],
signal,
);
```
### Features
- Returns processed summary (not raw search results)
- Inline citations with grounding metadata
- Sources list with titles and URIs
- UTF-8 byte position handling for accurate citation insertion
### Parameters
- `query` (string, required): The search query
### Web Fetch
The `web_fetch` tool processes content from URLs:
- Uses Gemini API's `urlContext` feature
- Fallback to direct HTTP fetch with `html-to-text` conversion
- Supports up to 20 URLs per request
- Converts GitHub blob URLs to raw URLs automatically
---
## OpenCode
### Tools
- `websearch` - Search the web using Exa AI
- `webfetch` - Fetch and read web pages
### Implementation
Source: [`packages/opencode/src/tool/websearch.ts`](https://github.com/sst/opencode/blob/main/packages/opencode/src/tool/websearch.ts)
**How it works:**
1. Calls Exa AI's MCP endpoint at `https://mcp.exa.ai/mcp`
2. Uses JSON-RPC protocol to invoke the `web_search_exa` tool
3. Parses SSE response for search results
```typescript
const searchRequest: McpSearchRequest = {
jsonrpc: "2.0",
id: 1,
method: "tools/call",
params: {
name: "web_search_exa",
arguments: {
query: params.query,
type: params.type || "auto",
numResults: params.numResults || 8,
livecrawl: params.livecrawl || "fallback",
contextMaxCharacters: params.contextMaxCharacters,
},
},
}
```
### Features
- Real-time web searches with content scraping
- Configurable result count (default: 8)
- Live crawl modes: `fallback` (backup if cached unavailable) or `preferred` (prioritize live crawling)
- Search types: `auto` (balanced), `fast` (quick results), `deep` (comprehensive)
- Context max characters for LLM optimization
### Parameters
- `query` (string, required): The search query
- `numResults` (number, optional): Number of results to return (default: 8)
- `livecrawl` (enum, optional): `"fallback"` | `"preferred"`
- `type` (enum, optional): `"auto"` | `"fast"` | `"deep"`
- `contextMaxCharacters` (number, optional): Maximum characters for context
### Configuration in Pullfrog
Web tools are configured via the permission config in `opencode.json`:
```typescript
// In sandbox mode
permission: {
webfetch: "deny",
// ...
}
// In normal mode
permission: {
webfetch: "allow",
// ...
}
```
### Environment Variables
- `OPENCODE_ENABLE_EXA` - Enable Exa web search tools (required for "zen" users)
### Web Fetch
The `webfetch` tool directly fetches URLs:
- Direct HTTP fetch with browser-like User-Agent
- HTML to Markdown conversion using Turndown
- Configurable timeout (max 120 seconds)
- 5MB response size limit
---
## Comparison
| Feature | Claude Code | Gemini CLI | OpenCode |
|---------|-------------|------------|----------|
| Search Provider | Anthropic | Google | Exa AI |
| Result Format | Summary | Summary + Citations | Raw content |
| URL Fetching | Yes (`WebFetch`) | Yes (`web_fetch`) | Yes (`webfetch`) |
| Grounding/Citations | Unknown | Yes | No |
| Configurable Results | No | No | Yes (numResults) |
| Search Depth Options | No | No | Yes (auto/fast/deep) |
| Live Crawling | Unknown | Fallback only | Configurable |
---
## Security Considerations
In Pullfrog's sandbox mode:
- **Claude Code**: `WebSearch` and `WebFetch` are explicitly disabled via `disallowedTools`
- **Gemini CLI**: No explicit disable mechanism in the wrapper (relies on default behavior)
- **OpenCode**: `webfetch` permission set to `"deny"` in sandbox mode
For public repositories, consider the implications of web search/fetch:
- Fetched content could potentially be used to inject prompts
- Search queries might leak information about the codebase context
---
## Proposed Implementation Plan
### Option 1: Use Native Agent Web Search (Current State)
Each agent uses its own built-in web search:
- **Pros**: No additional implementation, leverages each provider's strengths
- **Cons**: Inconsistent behavior across agents, no unified control
**Current gaps:**
- Gemini CLI has no explicit disable mechanism for web search in sandbox mode
- No unified way to configure web search across all agents
### Option 2: Unified MCP Web Search Tool
Add a `web_search` tool to the Pullfrog MCP server (`mcp/`) that all agents can use:
```
mcp/
├── bash.ts
├── webSearch.ts # New unified web search tool
└── ...
```
**Implementation approach:**
1. **Create `mcp/webSearch.ts`** with a provider-agnostic interface:
```typescript
export const webSearchTool = {
name: "web_search",
description: "Search the web for information",
inputSchema: {
type: "object",
properties: {
query: { type: "string", description: "Search query" },
numResults: { type: "number", description: "Number of results (default: 5)" },
},
required: ["query"],
},
};
```
2. **Choose a search provider** (options):
- **Exa AI** - Already used by OpenCode, good LLM-optimized results
- **Tavily** - Popular for AI agents, provides search + content extraction
- **SerpAPI** - Google results via API
- **Brave Search API** - Privacy-focused alternative
3. **Add to MCP server** in `mcp/server.ts`:
```typescript
import { webSearchTool, handleWebSearch } from "./webSearch.ts";
// Register tool...
```
4. **Disable native web search** for each agent:
- Claude: Add `"WebSearch"` to `disallowedTools`
- Gemini: Add `"google_web_search"` to `excludeTools` in settings.json
- OpenCode: Set `websearch: "deny"` in permission config
**Pros:**
- Consistent behavior across all agents
- Centralized control for security/sandbox modes
- Can filter/sanitize results before returning to agent
- Single API key management
**Cons:**
- Additional API costs (search provider)
- Loses provider-specific features (e.g., Gemini's grounding metadata)
### Option 3: Hybrid Approach
Allow native web search for private repos, use MCP tool for public repos:
```typescript
// In agent configuration
const useNativeWebSearch = !repo.isPublic;
// Claude
disallowedTools: repo.isPublic ? ["WebSearch", "WebFetch"] : [];
// Gemini
excludeTools: repo.isPublic ? ["google_web_search", "web_fetch"] : [];
// OpenCode
permission: {
websearch: repo.isPublic ? "deny" : "allow",
}
```
Then for public repos, agents would use the MCP `web_search` tool which:
- Filters sensitive queries
- Sanitizes returned content
- Logs all searches for audit
### Recommended Approach
**Short-term**: Implement Option 3 (Hybrid) with these steps:
1. [ ] Add `excludeTools: ["google_web_search"]` for Gemini in public repo mode
2. [ ] Ensure OpenCode `websearch` permission is properly set for sandbox mode
3. [ ] Document the current native web search behavior for each agent
**Medium-term**: Implement Option 2 (Unified MCP) for public repos:
1. [ ] Create `mcp/webSearch.ts` using Exa AI (consistent with OpenCode)
2. [ ] Add `EXA_API_KEY` to secrets handling
3. [ ] Register web search in MCP server
4. [ ] Disable native web search for all agents when MCP tool is available
5. [ ] Add result sanitization to prevent prompt injection
### API Key Requirements
| Provider | Environment Variable | Notes |
|----------|---------------------|-------|
| Exa AI | `EXA_API_KEY` | Already used by OpenCode |
| Tavily | `TAVILY_API_KEY` | Popular alternative |
| Brave | `BRAVE_API_KEY` | Privacy-focused |
For the unified MCP approach, only one search provider API key would be needed.
---
## Proposed Implementation Plan
### Option A: Unified MCP Web Search Tool
Create a custom MCP tool that provides consistent web search across all agents.
**Pros:**
- Consistent behavior and results across agents
- Full control over search provider and rate limiting
- Can implement caching and deduplication
- Single point for security filtering
**Cons:**
- Additional infrastructure (need a search API key)
- Latency from proxying through MCP server
**Implementation:**
1. Add `websearch` tool to `mcp/` directory
2. Integrate with a search provider (options: Exa AI, SerpAPI, Brave Search, Tavily)
3. Configure each agent to use MCP tool instead of native:
- Claude: Add to `disallowedTools` and provide via MCP
- Gemini: Use `excludeTools` in settings.json for `google_web_search`
- OpenCode: Disable native via permission config
```typescript
// mcp/websearch.ts
export const websearchTool = {
name: "websearch",
description: "Search the web for current information",
inputSchema: {
type: "object",
properties: {
query: { type: "string", description: "Search query" },
numResults: { type: "number", description: "Number of results (1-10)" },
},
required: ["query"],
},
handler: async ({ query, numResults = 5 }) => {
// Use Exa, Brave, or other search API
const results = await searchProvider.search(query, numResults);
return formatResults(results);
},
};
```
### Option B: Native Tools with Configuration
Keep using each agent's native web search but add consistent configuration.
**Pros:**
- No additional infrastructure
- Agents can use optimized native implementations
- Less latency
**Cons:**
- Inconsistent results across agents
- Different capabilities per agent
- Harder to control/audit searches
**Implementation:**
1. Add `websearch_enabled` option to payload/config
2. Update each agent wrapper:
- Claude: Toggle `WebSearch` in `disallowedTools`
- Gemini: Add `google_web_search` to `excludeTools` in settings.json
- OpenCode: Set `websearch` permission in config
```typescript
// agents/claude.ts
const disallowedTools = payload.websearchEnabled
? ["Bash"]
: ["Bash", "WebSearch", "WebFetch"];
// agents/gemini.ts
if (!payload.websearchEnabled) {
newSettings.excludeTools = [...(newSettings.excludeTools || []), "google_web_search"];
}
// agents/opencode.ts
permission: {
websearch: payload.websearchEnabled ? "allow" : "deny",
// ...
}
```
### Option C: Hybrid Approach (Recommended)
Use native tools when available, with MCP fallback for consistency.
**Implementation:**
1. Define a `websearch` MCP tool as fallback
2. For agents with good native search (Claude, Gemini): use native
3. For agents without (or with unreliable) search: use MCP tool
4. Add configuration to force MCP-only mode if needed
```typescript
// Per-agent configuration
const agentWebSearchConfig = {
claude: { useNative: true, mcpFallback: false },
gemini: { useNative: true, mcpFallback: false },
opencode: { useNative: false, mcpFallback: true }, // Exa requires API key
};
```
### Required Changes by Option
| Change | Option A | Option B | Option C |
|--------|----------|----------|----------|
| New MCP tool | Yes | No | Yes |
| Search API key | Yes | No | Optional |
| Agent wrapper changes | Yes | Yes | Yes |
| Action input changes | No | Yes | Yes |
| External dependencies | Yes | No | Optional |
### Recommended Next Steps
1. **Decide on search provider** - If going with MCP approach:
- Exa AI: Already used by OpenCode, good for code-related searches
- Brave Search: Privacy-focused, good general search
- Tavily: Designed for AI agents, includes content extraction
2. **Add configuration** - New action inputs:
```yaml
websearch:
description: 'Enable web search functionality'
required: false
default: 'false'
```
3. **Implement per-agent** - Start with Option B (simplest), upgrade to C if needed
4. **Add security controls** - Query filtering, domain allowlists, rate limiting