-
-
Notifications
You must be signed in to change notification settings - Fork 427
Expand file tree
/
Copy pathllm-docs.ts
More file actions
135 lines (118 loc) · 4.68 KB
/
llm-docs.ts
File metadata and controls
135 lines (118 loc) · 4.68 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
import * as v from 'valibot'
import { PackageRouteParamsSchema } from '#shared/schemas/package'
import { handleApiError } from '#server/utils/error-handler'
import {
handleLlmsTxt,
handleOrgLlmsTxt,
generateRootLlmsTxt,
handlePackageMd,
} from '#server/utils/llm-docs'
const CACHE_HEADER = 's-maxage=3600, stale-while-revalidate=86400'
/**
* Middleware to handle ALL llms.txt / llms-full.txt / .md routes.
*
* All llms.txt handling lives here rather than in file-based routes because
* Vercel's ISR route rules with glob patterns (e.g. `/package/ ** /llms.txt`)
* create catch-all serverless functions that interfere with Nitro's file-based
* route resolution — scoped packages and versioned paths fail to match.
*
* Handles:
* - /llms.txt (root discovery page)
* - /package/:name.md (unscoped, latest, raw README)
* - /package/@:org/:name.md (scoped, latest, raw README)
* - /package/@:org/llms.txt (org package listing)
* - /package/:name/llms.txt (unscoped, latest)
* - /package/:name/llms-full.txt (unscoped, latest, full)
* - /package/@:org/:name/llms.txt (scoped, latest)
* - /package/@:org/:name/llms-full.txt (scoped, latest, full)
* - /package/:name/v/:version/llms.txt (unscoped, versioned)
* - /package/:name/v/:version/llms-full.txt (unscoped, versioned, full)
* - /package/@:org/:name/v/:version/llms.txt (scoped, versioned)
* - /package/@:org/:name/v/:version/llms-full.txt (scoped, versioned, full)
*/
export default defineEventHandler(async event => {
const path = event.path.split('?')[0] ?? '/'
// Handle .md routes — raw README markdown (latest version only)
if (path.startsWith('/package/') && path.endsWith('.md') && !path.includes('/v/')) {
const rawPackageName = path.slice('/package/'.length, -'.md'.length)
if (!rawPackageName) return
try {
const { packageName } = v.parse(PackageRouteParamsSchema, {
packageName: rawPackageName,
})
const content = await handlePackageMd(packageName)
setHeader(event, 'Content-Type', 'text/markdown; charset=utf-8')
setHeader(event, 'Cache-Control', CACHE_HEADER)
return content
} catch (error: unknown) {
handleApiError(error, {
statusCode: 502,
message: 'Failed to generate package markdown.',
})
}
}
if (!path.endsWith('/llms.txt') && !path.endsWith('/llms-full.txt')) return
const full = path.endsWith('/llms-full.txt')
const suffix = full ? '/llms-full.txt' : '/llms.txt'
// Root /llms.txt
if (path === '/llms.txt') {
const url = getRequestURL(event)
const baseUrl = `${url.protocol}//${url.host}`
setHeader(event, 'Content-Type', 'text/markdown; charset=utf-8')
setHeader(event, 'Cache-Control', CACHE_HEADER)
return generateRootLlmsTxt(baseUrl)
}
if (!path.startsWith('/package/')) return
// Strip /package/ prefix and /llms[_full].txt suffix
const inner = path.slice('/package/'.length, -suffix.length)
// Org-level: /package/@org/llms.txt (inner = "@org")
if (!full && inner.startsWith('@') && !inner.includes('/')) {
const orgName = inner.slice(1)
try {
const url = getRequestURL(event)
const baseUrl = `${url.protocol}//${url.host}`
const content = await handleOrgLlmsTxt(orgName, baseUrl)
setHeader(event, 'Content-Type', 'text/markdown; charset=utf-8')
setHeader(event, 'Cache-Control', CACHE_HEADER)
return content
} catch (error: unknown) {
handleApiError(error, { statusCode: 502, message: 'Failed to generate org llms.txt.' })
}
}
// Parse package name and optional version from inner path
let rawPackageName: string
let rawVersion: string | undefined
if (inner.includes('/v/')) {
// Versioned path
if (inner.startsWith('@')) {
const match = inner.match(/^(@[^/]+\/[^/]+)\/v\/(.+)$/)
if (!match?.[1] || !match[2]) return
rawPackageName = match[1]
rawVersion = match[2]
} else {
const match = inner.match(/^([^/]+)\/v\/(.+)$/)
if (!match?.[1] || !match[2]) return
rawPackageName = match[1]
rawVersion = match[2]
}
} else {
// Latest version — inner is just the package name
rawPackageName = inner
}
if (!rawPackageName) return
try {
const { packageName, version } = v.parse(PackageRouteParamsSchema, {
packageName: rawPackageName,
version: rawVersion,
})
const content = await handleLlmsTxt(packageName, version, { includeAgentFiles: full })
setHeader(event, 'Content-Type', 'text/markdown; charset=utf-8')
setHeader(event, 'Cache-Control', CACHE_HEADER)
return content
} catch (error: unknown) {
handleApiError(error, {
statusCode: 502,
message: `Failed to generate ${full ? 'llms-full.txt' : 'llms.txt'}.`,
})
}
})