| 229 | |
| 230 | impl WebFetchTool { |
| 231 | async fn fetch_with_retry( |
| 232 | &self, |
| 233 | url: &str, |
| 234 | accept_header: &str, |
| 235 | _timeout_secs: u64, |
| 236 | ) -> Result<reqwest::Response, ToolError> { |
| 237 | let response = self |
| 238 | .client |
| 239 | .get(url) |
| 240 | .header("Accept", accept_header) |
| 241 | .header("Accept-Language", "en-US,en;q=0.9") |
| 242 | .send() |
| 243 | .await |
| 244 | .map_err(|e| ToolError::ExecutionError(format!("Failed to fetch URL: {}", e)))?; |
| 245 | |
| 246 | if response.status() == 403 { |
| 247 | let cf_mitigated = response |
| 248 | .headers() |
| 249 | .get("cf-mitigated") |
| 250 | .and_then(|v| v.to_str().ok()); |
| 251 | |
| 252 | if cf_mitigated == Some("challenge") { |
| 253 | return self |
| 254 | .client |
| 255 | .get(url) |
| 256 | .header("Accept", accept_header) |
| 257 | .header("User-Agent", "opencode") |
| 258 | .send() |
| 259 | .await |
| 260 | .map_err(|e| ToolError::ExecutionError(format!("Failed to fetch URL: {}", e))); |
| 261 | } |
| 262 | } |
| 263 | |
| 264 | if !response.status().is_success() { |
| 265 | return Err(ToolError::ExecutionError(format!( |
| 266 | "Request failed with status code: {}", |
| 267 | response.status() |
| 268 | ))); |
| 269 | } |
| 270 | |
| 271 | Ok(response) |
| 272 | } |
| 273 | } |
| 274 | |
| 275 | fn convert_html_to_markdown(html: &str) -> String { |