Skip to content

Commit f815c70

Browse files
committed
Add llms middleware and update Vite config to serve llms.txt for AI requests
- Introduced a new middleware in llms-middleware.ts to detect AI user agents and serve llms.txt content. - Updated vite.config.ts to include the new llmsTxtPlugin, which handles requests for llms.txt based on user agent detection.
1 parent 225e248 commit f815c70

2 files changed

Lines changed: 126 additions & 2 deletions

File tree

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/**
2+
* Middleware to detect AI/LLM requests and serve llms.txt content
3+
*
4+
* Usage with Express:
5+
* ```ts
6+
* import { llmsMiddleware } from './llms-middleware'
7+
* app.use(llmsMiddleware(llmsContent))
8+
* ```
9+
*
10+
* Usage with Hono:
11+
* ```ts
12+
* import { isAIUserAgent } from './llms-middleware'
13+
* app.use('/', async (c, next) => {
14+
* if (isAIUserAgent(c.req.header('user-agent'))) {
15+
* return c.text(llmsContent)
16+
* }
17+
* await next()
18+
* })
19+
* ```
20+
*/
21+
22+
const AI_USER_AGENTS = [
23+
'anthropic-ai',
24+
'claude',
25+
'openai',
26+
'gptbot',
27+
'chatgpt',
28+
'bingbot',
29+
'googlebot',
30+
'google-extended',
31+
'perplexitybot',
32+
'amazonbot',
33+
'meta-externalagent',
34+
'cohere-ai',
35+
'diffbot',
36+
'curl',
37+
'wget',
38+
'Claude-SearchBot',
39+
'Claude-User',
40+
'ClaudeBot',
41+
]
42+
43+
export function isAIUserAgent(userAgent: string | undefined): boolean {
44+
if (!userAgent) return false
45+
const ua = userAgent.toLowerCase()
46+
return AI_USER_AGENTS.some((agent) => ua.includes(agent))
47+
}
48+
49+
export type LlmsMiddleware = (req: any, res: any, next: () => void) => void
50+
51+
export function llmsMiddleware(llmsContent: string): LlmsMiddleware {
52+
return (req, res, next) => {
53+
const userAgent = req.headers['user-agent']
54+
55+
if (req.url === '/llms.txt') {
56+
res.setHeader('Content-Type', 'text/plain; charset=utf-8')
57+
res.end(llmsContent)
58+
return
59+
}
60+
61+
if (req.url === '/' && isAIUserAgent(userAgent)) {
62+
res.setHeader('Content-Type', 'text/plain; charset=utf-8')
63+
res.setHeader('X-Served-As', 'llms.txt')
64+
res.end(llmsContent)
65+
return
66+
}
67+
68+
next()
69+
}
70+
}

examples/web/vite.config.ts

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,65 @@
11
import vue from '@vitejs/plugin-vue'
2-
import { defineConfig } from 'vite'
2+
import { defineConfig, Plugin } from 'vite'
33
import path from 'path'
44
import fs from 'fs'
55

6+
const AI_USER_AGENTS = [
7+
'anthropic-ai',
8+
'claude',
9+
'openai',
10+
'gptbot',
11+
'chatgpt',
12+
'bingbot',
13+
'googlebot',
14+
'google-extended',
15+
'perplexitybot',
16+
'amazonbot',
17+
'meta-externalagent',
18+
'cohere-ai',
19+
'diffbot',
20+
'curl',
21+
]
22+
23+
function isAIRequest(userAgent: string | undefined): boolean {
24+
if (!userAgent) return false
25+
const ua = userAgent.toLowerCase()
26+
return AI_USER_AGENTS.some((agent) => ua.includes(agent))
27+
}
28+
29+
function llmsTxtPlugin(): Plugin {
30+
let llmsContent: string
31+
32+
return {
33+
name: 'llms-txt-plugin',
34+
configureServer(server) {
35+
const llmsPath = path.resolve(__dirname, '../../llms.txt')
36+
llmsContent = fs.readFileSync(llmsPath, 'utf-8')
37+
38+
server.middlewares.use((req, res, next) => {
39+
const userAgent = req.headers['user-agent']
40+
41+
if (req.url === '/llms.txt') {
42+
res.setHeader('Content-Type', 'text/plain; charset=utf-8')
43+
res.end(llmsContent)
44+
return
45+
}
46+
47+
if (req.url === '/' && isAIRequest(userAgent)) {
48+
res.setHeader('Content-Type', 'text/plain; charset=utf-8')
49+
res.setHeader('X-Served-As', 'llms.txt')
50+
res.end(llmsContent)
51+
return
52+
}
53+
54+
next()
55+
})
56+
},
57+
}
58+
}
59+
660
// https://vitejs.dev/config/
761
export default defineConfig({
8-
plugins: [vue()],
62+
plugins: [llmsTxtPlugin(), vue()],
963
server: {
1064
port: 5050,
1165
open: true,

0 commit comments

Comments
 (0)