package tools import ( "encoding/json" "fmt" "io" "net/http" "net/url" "strings" ) // SearchResult represents a single search result type SearchResult struct { Title string `json:"title"` URL string `json:"url"` Content string `json:"content"` } // SearchResponse represents the SearXNG API response type SearchResponse struct { Results []SearchResult `json:"results"` } // WebSearch performs a web search using SearXNG func WebSearch(searxngURL, query string) (string, error) { // Build the search URL searchURL := fmt.Sprintf("%s/search", strings.TrimSuffix(searxngURL, "/")) // Create URL with query parameters params := url.Values{} params.Add("q", query) params.Add("format", "json") params.Add("language", "en") fullURL := fmt.Sprintf("%s?%s", searchURL, params.Encode()) // Make the request resp, err := http.Get(fullURL) if err != nil { return "", fmt.Errorf("failed to perform search: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return "", fmt.Errorf("search request failed with status %d: %s", resp.StatusCode, string(body)) } // Parse the response body, err := io.ReadAll(resp.Body) if err != nil { return "", fmt.Errorf("failed to read response: %w", err) } var searchResp SearchResponse if err := json.Unmarshal(body, &searchResp); err != nil { return "", fmt.Errorf("failed to parse search results: %w", err) } // Format results as text if len(searchResp.Results) == 0 { return "No results found.", nil } var results strings.Builder results.WriteString(fmt.Sprintf("Found %d results:\n\n", len(searchResp.Results))) for i, result := range searchResp.Results { if i >= 10 { // Limit to top 10 results break } results.WriteString(fmt.Sprintf("%d. %s\n", i+1, result.Title)) results.WriteString(fmt.Sprintf(" URL: %s\n", result.URL)) if result.Content != "" { results.WriteString(fmt.Sprintf(" %s\n", result.Content)) } results.WriteString("\n") } return results.String(), nil }