web_fetch.go 2.1 KB

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