refactor(core): simplify WebFetch error narrowing (#45582)

Use the typed HTTP error reason instead of manual object probing and an unchecked response cast. Preserve the single challenge-only retry and cover ordinary 403 failures without retry.
This commit is contained in:
Kit Langton
2026-08-27 11:56:34 -04:00
committed by GitHub
parent 2b8a3e7ee0
commit 367cf59612
2 changed files with 20 additions and 13 deletions
+4 -13
View File
@@ -3,7 +3,7 @@ export * as WebFetchTool from "./webfetch.js"
import type { Context as PluginContext } from "@opencode-ai/plugin/effect/plugin"
import { ToolFailure } from "@opencode-ai/ai"
import { Duration, Effect, Schema } from "effect"
import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"
import { HttpClient, type HttpClientError, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"
import { Parser } from "htmlparser2"
import { Permission } from "../../permission.js"
import { convertHTMLToMarkdown, MAX_MARKDOWN_BYTES } from "../html-markdown.js"
@@ -58,18 +58,9 @@ const headers = (format: Format, userAgent: string) => ({
const openCodeUserAgent =
"Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; OpenCode-User/1.0; +https://opencode.ai"
const isCloudflareChallenge = (error: unknown) => {
if (!error || typeof error !== "object" || !("reason" in error)) return false
const reason = error.reason
if (
!reason ||
typeof reason !== "object" ||
!("_tag" in reason) ||
reason._tag !== "StatusCodeError" ||
!("response" in reason)
)
return false
const response = reason.response as HttpClientResponse.HttpClientResponse
const isCloudflareChallenge = (error: HttpClientError.HttpClientError) => {
if (error.reason._tag !== "StatusCodeError") return false
const response = error.reason.response
return response.status === 403 && response.headers["cf-mitigated"] === "challenge"
}
+16
View File
@@ -573,6 +573,22 @@ describe("WebFetchTool registration", () => {
}),
)
it.effect("does not retry ordinary 403 responses", () =>
Effect.gen(function* () {
reset()
respond = () => Effect.succeed(new Response("forbidden", { status: 403 }))
const registry = yield* Tool.Service
const url = "https://example.com/forbidden"
expect(yield* executeTool(registry, call({ url, format: "text" }))).toEqual({
status: "error",
error: { type: "unknown", message: `StatusCode: non 2xx status code (403 GET ${url})` },
})
expect(requests).toHaveLength(1)
expect(requests[0]?.headers["user-agent"]).toBe(webFetchUserAgent)
}),
)
it.effect("times out stalled requests", () =>
Effect.gen(function* () {
reset()