| 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 |
| 5 | .toLowerCase() |
| 6 | .split(",") |
| 7 | .map((part) => { |
| 8 | const [type, ...parameters] = part |
| 9 | .trim() |
| 10 | .split(";") |
| 11 | .map((value) => value.trim()); |
| 12 | const quality = parameters.find((value) => value.startsWith("q="))?.slice(2) ?? "1"; |
| 13 | const q = /^(?:0(?:\.\d{0,3})?|1(?:\.0{0,3})?)$/.test(quality) ? Number(quality) : 0; |
| 14 | return { type, q }; |
| 15 | }); |
| 16 | function weight(type: string) { |
| 17 | for (const [specificity, candidate] of [ |
| 18 | [2, type], |
| 19 | [1, "text/*"], |
| 20 | [0, "*/*"], |
| 21 | ] as const) { |
| 22 | const matches = ranges.filter((range) => range.type === candidate); |
| 23 | if (matches.length) return { q: Math.max(...matches.map((range) => range.q)), specificity }; |
| 24 | } |
| 25 | return { q: 0, specificity: -1 }; |
| 26 | } |
| 27 | const html = weight("text/html"); |
| 28 | const markdown = weight("text/markdown"); |
| 29 | if (html.q === 0 && markdown.q === 0) return "none"; |
| 30 | return markdown.q > html.q || (markdown.q === html.q && markdown.specificity > html.specificity) ? "markdown" : "html"; |
| 31 | } |
| 32 | |
| 33 | export function varyAccept(headers: Headers) { |
| 34 | const values = (headers.get("vary") ?? "") |
| 35 | .split(",") |
| 36 | .map((value) => value.trim()) |
| 37 | .filter(Boolean); |
| 38 | if (!values.some((value) => value === "*" || value.toLowerCase() === "accept")) values.push("Accept"); |
| 39 | headers.set("vary", values.join(", ")); |
| 40 | } |
| 41 | |
| 42 | export function developerResource(path: string, addresses: { api: string; mcp: string }): string | null { |
| 43 | const resources: Record<string, string> = { |
| 44 | "/developers": "https://docs.g1t.sh/reference/developers/", |
| 45 | "/openapi.json": `${addresses.api}/openapi.json`, |
| 46 | "/mcp": addresses.mcp, |
| 47 | }; |
| 48 | return resources[path] ?? null; |
| 49 | } |
| 50 | |
| 51 | /** Called only for document responses, after routing and authorization decide the status. */ |
| 52 | export function agentPage(request: Request, status: number, headers: Headers): Response | null { |
| 53 | if (!["GET", "HEAD"].includes(request.method)) return null; |
| 54 | const url = new URL(request.url); |
| 55 | if (status !== 404 && !(status === 200 && url.pathname === "/")) return null; |
| 56 | varyAccept(headers); |
| 57 | const representation = pageRepresentation(request.headers.get("accept")); |
| 58 | if (representation === "html") return null; |
| 59 | headers.set("content-type", representation === "markdown" ? "text/markdown; charset=utf-8" : "text/plain; charset=utf-8"); |
| 60 | headers.delete("content-length"); |
| 61 | const body = |
| 62 | representation === "none" |
| 63 | ? "This page is available as text/html or text/markdown.\n" |
| 64 | : status === 404 |
| 65 | ? `# 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` |
| 66 | : `# g1t\n\ng1t is an open-source workspace for repositories, work items, 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`; |
| 67 | return new Response(request.method === "HEAD" ? null : body, { status: representation === "none" ? 406 : status, headers }); |
| 68 | } |