diff --git a/apps/skills/src/app/route.ts b/apps/skills/src/app/route.ts index 0ae26a2a60..6da8fff408 100644 --- a/apps/skills/src/app/route.ts +++ b/apps/skills/src/app/route.ts @@ -211,7 +211,7 @@ For the full, current flag list and any commands added after this skill was gene const COMMON_HEADERS = { "Cache-Control": "public, max-age=3600, s-maxage=3600", // CDN must cache markdown (curl/agents) and HTML (browser navigate) separately. - "Vary": "Sec-Fetch-Mode, Sec-Fetch-Dest", + "Vary": "Accept", "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, HEAD, OPTIONS", "Access-Control-Allow-Headers": "*", @@ -431,12 +431,18 @@ function renderHtml(): string { `; } +const MARKDOWN_PREFERRING_TYPES = new Set(["*/*", "text/plain", "text/markdown", "text/x-markdown"]); + function wantsHtml(req: Request): boolean { - // Browsers navigating to a top-level URL send Sec-Fetch-Mode: navigate. - // curl, fetch(), and agent fetchers do not, so they keep getting markdown. - if (req.headers.get("sec-fetch-mode") === "navigate") return true; - if (req.headers.get("sec-fetch-dest") === "document") return true; - return false; + // Browsers send `Accept: text/html,...` before `*/*`; curl/fetch/agents send + // `*/*` (or omit Accept). Serve HTML only when text/html appears AND is + // listed before any markdown-preferring type that would otherwise win. + const accept = req.headers.get("accept") ?? ""; + const types = accept.split(",").map((part) => part.trim().split(";")[0].trim().toLowerCase()); + const htmlIndex = types.indexOf("text/html"); + if (htmlIndex === -1) return false; + const competitorIndex = types.findIndex((t) => MARKDOWN_PREFERRING_TYPES.has(t)); + return competitorIndex === -1 || htmlIndex < competitorIndex; } export function GET(req: Request) {