1package tools
2
3import (
4 "context"
5 _ "embed"
6 "fmt"
7 "net/http"
8 "os"
9 "strings"
10 "time"
11
12 "charm.land/fantasy"
13)
14
15//go:embed web_fetch.md
16var webFetchToolDescription []byte
17
18// NewWebFetchTool creates a simple web fetch tool for sub-agents (no permissions needed).
19func NewWebFetchTool(workingDir string, client *http.Client) fantasy.AgentTool {
20 if client == nil {
21 transport := http.DefaultTransport.(*http.Transport).Clone()
22 transport.MaxIdleConns = 100
23 transport.MaxIdleConnsPerHost = 10
24 transport.IdleConnTimeout = 90 * time.Second
25
26 client = &http.Client{
27 Timeout: 30 * time.Second,
28 Transport: transport,
29 }
30 }
31
32 return fantasy.NewParallelAgentTool(
33 WebFetchToolName,
34 string(webFetchToolDescription),
35 func(ctx context.Context, params WebFetchParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
36 if params.URL == "" {
37 return fantasy.NewTextErrorResponse("url is required"), nil
38 }
39
40 content, err := FetchURLAndConvert(ctx, client, params.URL)
41 if err != nil {
42 return fantasy.NewTextErrorResponse(fmt.Sprintf("Failed to fetch URL: %s", err)), nil
43 }
44
45 hasLargeContent := len(content) > LargeContentThreshold
46 var result strings.Builder
47
48 if hasLargeContent {
49 tempFile, err := os.CreateTemp(workingDir, "page-*.md")
50 if err != nil {
51 return fantasy.NewTextErrorResponse(fmt.Sprintf("Failed to create temporary file: %s", err)), nil
52 }
53 tempFilePath := tempFile.Name()
54
55 if _, err := tempFile.WriteString(content); err != nil {
56 _ = tempFile.Close() // Best effort close
57 return fantasy.NewTextErrorResponse(fmt.Sprintf("Failed to write content to file: %s", err)), nil
58 }
59 if err := tempFile.Close(); err != nil {
60 return fantasy.NewTextErrorResponse(fmt.Sprintf("Failed to close temporary file: %s", err)), nil
61 }
62
63 result.WriteString(fmt.Sprintf("Fetched content from %s (large page)\n\n", params.URL))
64 result.WriteString(fmt.Sprintf("Content saved to: %s\n\n", tempFilePath))
65 result.WriteString("Use the view and grep tools to analyze this file.")
66 } else {
67 result.WriteString(fmt.Sprintf("Fetched content from %s:\n\n", params.URL))
68 result.WriteString(content)
69 }
70
71 return fantasy.NewTextResponse(result.String()), nil
72 })
73}