| 1 | /** Explicit Markdown negotiation; ordinary browsers and wildcard clients keep HTML. */ |
| 2 | export function pageRepresentation(accept: string | null): "html" | "markdown" | "none" { |
| 3 | if (!accept?.trim()) return "html"; |
| 4 | const ranges = accept.toLowerCase().split(",").map((part) => { |
| 5 | const [type, ...parameters] = part.trim().split(";").map((value) => value.trim()); |
| 6 | const quality = parameters.find((value) => value.startsWith("q="))?.slice(2) ?? "1"; |
| 7 | const q = /^(?:0(?:\.\d{0,3})?|1(?:\.0{0,3})?)$/.test(quality) ? Number(quality) : 0; |
| 8 | return { type, q }; |
| 9 | }); |
| 10 | function weight(type: string) { |
| 11 | for (const [specificity, candidate] of [[2, type], [1, "text/*"], [0, "*/*"]] as const) { |
| 12 | const matches = ranges.filter((range) => range.type === candidate); |
| 13 | if (matches.length) return { q: Math.max(...matches.map((range) => range.q)), specificity }; |
| 14 | } |
| 15 | return { q: 0, specificity: -1 }; |
| 16 | } |
| 17 | const html = weight("text/html"); |
| 18 | const markdown = weight("text/markdown"); |
| 19 | if (html.q === 0 && markdown.q === 0) return "none"; |
| 20 | return markdown.q > html.q || (markdown.q === html.q && markdown.specificity > html.specificity) ? "markdown" : "html"; |
| 21 | } |
| 22 | |
| 23 | export function varyAccept(headers: Headers) { |
| 24 | const values = (headers.get("vary") ?? "").split(",").map((value) => value.trim()).filter(Boolean); |
| 25 | if (!values.some((value) => value === "*" || value.toLowerCase() === "accept")) values.push("Accept"); |
| 26 | headers.set("vary", values.join(", ")); |
| 27 | } |
| 28 | |
| 29 | export function developerResource(path: string, addresses: { api: string; mcp: string }): string | null { |
| 30 | const resources: Record<string, string> = { |
| 31 | "/developers": "https://docs.g1t.sh/reference/api/", |
| 32 | "/openapi.json": `${addresses.api}/openapi.json`, |
| 33 | "/mcp": addresses.mcp, |
| 34 | }; |
| 35 | return resources[path] ?? null; |
| 36 | } |
| 37 | |
| 38 | /** Called only for document responses, after routing and authorization decide the status. */ |
| 39 | export function agentPage(request: Request, status: number, headers: Headers): Response | null { |
| 40 | if (!["GET", "HEAD"].includes(request.method)) return null; |
| 41 | const url = new URL(request.url); |
| 42 | if (status !== 404 && !(status === 200 && url.pathname === "/")) return null; |
| 43 | varyAccept(headers); |
| 44 | const representation = pageRepresentation(request.headers.get("accept")); |
| 45 | if (representation === "html") return null; |
| 46 | headers.set("content-type", representation === "markdown" ? "text/markdown; charset=utf-8" : "text/plain; charset=utf-8"); |
| 47 | headers.delete("content-length"); |
| 48 | const body = representation === "none" ? "This page is available as text/html or text/markdown.\n" : status === 404 |
| 49 | ? `# Page not found\n\nThe requested page does not exist or is not available to you.\n\nSee [g1t agent instructions](${url.origin}/llms.txt) or the [sitemap](${url.origin}/sitemap.xml) to find a public resource.\n` |
| 50 | : `# g1t\n\ng1t is an open-source workspace for repositories, issues, pull requests, reviews, required checks, a merge queue, deployments, team chat and Artifacts documents. Bring the coding agents you already use through MCP, REST or the CLI.\n\n## When to use g1t\n\nUse g1t to inspect a project's code and issues, prepare and review pull requests, follow checks and deployment status, and coordinate agent work in a shared repository. Use the API or MCP for structured reads and changes; use git for cloning and pushing code.\n\n## Developer resources\n\n- [Agent instructions](${url.origin}/llms.txt)\n- [g1t documentation](https://docs.g1t.sh/)\n- [REST API and versioning](https://docs.g1t.sh/reference/api/)\n- [OpenAPI](https://api.g1t.sh/openapi.json)\n- [Authentication and scopes](https://docs.g1t.sh/guides/authentication/)\n- [MCP server](https://mcp.g1t.sh)\n- [CLI installation](https://docs.g1t.sh/guides/cli/)\n- [Pricing](${url.origin}/pricing)\n\nPublic repositories can be read without credentials. Writes and private data require authorization; request only the scopes your task needs. Never ask a user to paste their password.\n`; |
| 51 | return new Response(request.method === "HEAD" ? null : body, { status: representation === "none" ? 406 : status, headers }); |
| 52 | } |