- Add Agent mode toggle to web UI input row — routes through POST /orchestrate instead of /chat; polls for result with live tool-call count in thinking bubble - Add cortex/tools/system.py with claude_allow_dir tool; registers in tool registry - Fix web search: duckduckgo_search renamed to ddgs, update import + requirements.txt - Allow WebSearch and WebFetch in ~/.claude/settings.json for Claude CLI fallback - Add claude-allow-dir script docs and security note to CLAUDE.md Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
51 lines
1.6 KiB
Python
51 lines
1.6 KiB
Python
"""
|
|
Web search tool — DuckDuckGo backend.
|
|
|
|
Uses the duckduckgo-search library. Set DDG_API_KEY in .env for a paid account
|
|
(higher rate limits). The free unauthenticated tier works for moderate usage.
|
|
"""
|
|
|
|
import asyncio
|
|
import logging
|
|
from config import settings
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
async def search(query: str, max_results: int | None = None) -> str:
|
|
"""Search DuckDuckGo and return results as a formatted string.
|
|
|
|
Returns a markdown-formatted list of results: title, URL, and snippet.
|
|
The orchestrator includes this in the context it passes to Claude.
|
|
"""
|
|
n = min(max_results or settings.ddg_max_results, 10)
|
|
results = await asyncio.to_thread(_sync_search, query, n)
|
|
if not results:
|
|
return f"No results found for: {query}"
|
|
|
|
lines = [f"Search results for: **{query}**\n"]
|
|
for i, r in enumerate(results, 1):
|
|
lines.append(f"{i}. [{r['title']}]({r['href']})")
|
|
if r.get("body"):
|
|
lines.append(f" {r['body']}")
|
|
lines.append("")
|
|
|
|
return "\n".join(lines).strip()
|
|
|
|
|
|
def _sync_search(query: str, max_results: int) -> list[dict]:
|
|
"""Synchronous DuckDuckGo search — run via asyncio.to_thread."""
|
|
from ddgs import DDGS
|
|
|
|
kwargs = {}
|
|
if settings.ddg_api_key:
|
|
# Paid account — pass token for higher rate limits
|
|
kwargs["headers"] = {"Authorization": f"Bearer {settings.ddg_api_key}"}
|
|
|
|
try:
|
|
with DDGS(**kwargs) as ddgs:
|
|
return list(ddgs.text(query, max_results=max_results))
|
|
except Exception as e:
|
|
logger.warning("DuckDuckGo search error: %s", e)
|
|
return []
|