| 74 | } |
| 75 | |
| 76 | func (t *WebFetchTool) Execute(ctx context.Context, input map[string]any, tc *tools.ToolContext) (any, error) { |
| 77 | url, ok := input["url"].(string) |
| 78 | if !ok || url == "" { |
| 79 | return nil, errors.New("url must be a non-empty string") |
| 80 | } |
| 81 | |
| 82 | method := "GET" |
| 83 | if m, ok := input["method"].(string); ok { |
| 84 | method = m |
| 85 | } |
| 86 | |
| 87 | var reqBody io.Reader |
| 88 | if bodyStr, ok := input["body"].(string); ok && bodyStr != "" { |
| 89 | reqBody = bytes.NewBufferString(bodyStr) |
| 90 | } |
| 91 | |
| 92 | req, err := http.NewRequestWithContext(ctx, method, url, reqBody) |
| 93 | if err != nil { |
| 94 | return map[string]any{ |
| 95 | "success": false, |
| 96 | "error": fmt.Sprintf("failed to create request: %v", err), |
| 97 | }, nil |
| 98 | } |
| 99 | |
| 100 | if headers, ok := input["headers"].(map[string]any); ok { |
| 101 | for key, value := range headers { |
| 102 | if valueStr, ok := value.(string); ok { |
| 103 | req.Header.Set(key, valueStr) |
| 104 | } |
| 105 | } |
| 106 | } |
| 107 | |
| 108 | if req.Header.Get("User-Agent") == "" { |
| 109 | req.Header.Set("User-Agent", "Aster-Agent/1.0") |
| 110 | } |
| 111 | |
| 112 | client := t.client |
| 113 | if timeoutSec, ok := input["timeout"].(float64); ok && timeoutSec > 0 { |
| 114 | client = &http.Client{ |
| 115 | Timeout: time.Duration(timeoutSec) * time.Second, |
| 116 | } |
| 117 | } |
| 118 | |
| 119 | resp, err := client.Do(req) |
| 120 | if err != nil { |
| 121 | var netErr net.Error |
| 122 | if ctx.Err() == context.DeadlineExceeded || (errors.As(err, &netErr) && netErr.Timeout()) { |
| 123 | return map[string]any{ |
| 124 | "success": false, |
| 125 | "error": fmt.Sprintf("request timeout after %v", client.Timeout), |
| 126 | "url": url, |
| 127 | }, nil |
| 128 | } |
| 129 | |
| 130 | return map[string]any{ |
| 131 | "success": false, |
| 132 | "error": fmt.Sprintf("request failed: %v", err), |
| 133 | "url": url, |