fetch validates the URL, applies SSRF pre-checks, performs the GET, reads up to webFetchMaxBytes, extracts text from HTML payloads, and truncates the result to maxChars.
(ctx context.Context, rawURL string, maxChars int)
| 111 | return "", fmt.Errorf("web_fetch %s: url: %w", n.ID(), err) |
| 112 | } |
| 113 | text, err := n.webFetch(ctx, webFetchArgs{URL: urlStr}) |
| 114 | if err != nil { |
| 115 | return "", err |
| 116 | } |
| 117 | if err := engine.ApplyOutput(scope, n.ID(), webFetchOutID, n.binding, expr.StringVal(text)); err != nil { |
| 118 | return "", fmt.Errorf("web_fetch %s: applying output: %w", n.ID(), err) |
| 119 | } |
| 120 | return n.Next(engine.PortCtrl, scope) |
| 121 | } |
| 122 | |
| 123 | // webFetchArgs is the tool-call payload. A URL is always a string, so the |
| 124 | // schema is derivable from this type. |
| 125 | type webFetchArgs struct { |
| 126 | URL string `json:"url"` |
| 127 | } |
| 128 | |
| 129 | // Tools exposes this node as `web_fetch(url)`. maxChars is not a parameter — |
| 130 | // the author's cap applies to model-issued fetches too. |
| 131 | func (n *WebFetch) Tools() ([]llmproxy.FunctionTool, error) { |
| 132 | ft, err := llmproxy.NewFunctionTool("web_fetch", n.toolDescription, n.webFetch) |
| 133 | if err != nil { |
| 134 | return nil, fmt.Errorf("web_fetch %s: %w", n.ID(), err) |
| 135 | } |
| 136 | return []llmproxy.FunctionTool{ft}, nil |
| 137 | } |
| 138 | |
| 139 | // webFetch is the actual implementation of the tool call, unwrapped from the |
| 140 | // node execution signature. |
| 141 | func (n *WebFetch) webFetch(ctx context.Context, args webFetchArgs) (string, error) { |
| 142 | text, err := n.fetch(ctx, args.URL, n.maxChars) |
| 143 | if err != nil { |
| 144 | return "", fmt.Errorf("web_fetch %s: %w", n.ID(), err) |
| 145 | } |
| 146 | return text, nil |
| 147 | } |
| 148 | |
| 149 | // fetch validates the URL, applies SSRF pre-checks, performs the GET, reads up |
| 150 | // to webFetchMaxBytes, extracts text from HTML payloads, and truncates the |
| 151 | // result to maxChars. |
| 152 | func (n *WebFetch) fetch(ctx context.Context, rawURL string, maxChars int) (string, error) { |
| 153 | parsed, err := url.Parse(strings.TrimSpace(rawURL)) |
| 154 | if err != nil { |
| 155 | return "", fmt.Errorf("invalid url: %w", err) |
| 156 | } |
| 157 | if parsed.Scheme != "http" && parsed.Scheme != "https" { |
| 158 | return "", fmt.Errorf("only http/https urls allowed, got %q", parsed.Scheme) |
| 159 | } |
| 160 | if parsed.Host == "" { |
| 161 | return "", fmt.Errorf("missing host in url") |
| 162 | } |
| 163 | if isObviousPrivateHost(parsed.Hostname()) { |
| 164 | return "", fmt.Errorf("fetching private or local hosts is not allowed") |
| 165 | } |
no test coverage detected