@vercel/agent-readability 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +122 -62
- package/dist/cli/index.cjs +26 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -1
- package/dist/index.d.ts +9 -1
- package/dist/next/index.cjs +23 -11
- package/dist/next/index.cjs.map +1 -1
- package/dist/next/index.d.cts +9 -5
- package/dist/next/index.d.ts +9 -5
- package/dist/next/index.js +23 -11
- package/dist/next/index.js.map +1 -1
- package/dist/nuxt/index.cjs +170 -0
- package/dist/nuxt/index.cjs.map +1 -0
- package/dist/nuxt/index.d.cts +39 -0
- package/dist/nuxt/index.d.ts +39 -0
- package/dist/nuxt/index.js +145 -0
- package/dist/nuxt/index.js.map +1 -0
- package/dist/sveltekit/index.cjs +178 -0
- package/dist/sveltekit/index.cjs.map +1 -0
- package/dist/sveltekit/index.d.cts +37 -0
- package/dist/sveltekit/index.d.ts +37 -0
- package/dist/sveltekit/index.js +151 -0
- package/dist/sveltekit/index.js.map +1 -0
- package/package.json +34 -1
- package/skill/SKILL.md +44 -7
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ Detect AI agents. Serve them markdown. Audit your site against the
|
|
|
9
9
|
npm install @vercel/agent-readability
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
Or
|
|
12
|
+
Or audit without installing:
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
15
|
npx @vercel/agent-readability audit https://vercel.com/docs
|
|
@@ -17,40 +17,55 @@ npx @vercel/agent-readability audit https://vercel.com/docs
|
|
|
17
17
|
|
|
18
18
|
## Quick Start
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
**Next.js** `middleware.ts`:
|
|
22
21
|
```ts
|
|
23
|
-
// middleware.ts
|
|
24
22
|
import { withAgentReadability } from '@vercel/agent-readability/next'
|
|
25
23
|
|
|
26
24
|
export default withAgentReadability({
|
|
27
25
|
rewrite: (pathname) => `/api/docs-md${pathname}`,
|
|
28
26
|
})
|
|
29
27
|
|
|
30
|
-
export const config = {
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
export const config = { matcher: ['/docs/:path*'] }
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**SvelteKit** `hooks.server.ts`:
|
|
32
|
+
```ts
|
|
33
|
+
import { handleAgentReadability } from '@vercel/agent-readability/sveltekit'
|
|
34
|
+
import { sequence } from '@sveltejs/kit/hooks'
|
|
35
|
+
|
|
36
|
+
export const handle = sequence(
|
|
37
|
+
handleAgentReadability({ rewrite: (p) => `/api/docs-md${p}` }),
|
|
38
|
+
)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Nuxt** `server/middleware/agent.ts`:
|
|
42
|
+
```ts
|
|
43
|
+
import { defineAgentMiddleware } from '@vercel/agent-readability/nuxt'
|
|
44
|
+
|
|
45
|
+
export default defineAgentMiddleware({
|
|
46
|
+
getMarkdown: async (pathname) => {
|
|
47
|
+
const doc = await fetchDoc(pathname)
|
|
48
|
+
return doc.markdown
|
|
49
|
+
},
|
|
50
|
+
})
|
|
33
51
|
```
|
|
34
52
|
|
|
35
|
-
|
|
53
|
+
Agents hitting `/docs/*` get markdown instead of HTML.
|
|
36
54
|
|
|
37
|
-
##
|
|
55
|
+
## How Detection Works
|
|
38
56
|
|
|
39
|
-
Three
|
|
57
|
+
Three layers, checked in order:
|
|
40
58
|
|
|
41
|
-
1. **Known UA patterns.**
|
|
42
|
-
2. **Signature-Agent header.**
|
|
43
|
-
3. **sec-fetch-mode heuristic.**
|
|
59
|
+
1. **Known UA patterns.** 30+ agents (ClaudeBot, GPTBot, Cursor, Perplexity, etc.)
|
|
60
|
+
2. **Signature-Agent header.** ChatGPT agent via RFC 9421.
|
|
61
|
+
3. **sec-fetch-mode heuristic.** Unknown bots lacking browser fingerprints.
|
|
44
62
|
|
|
45
|
-
|
|
46
|
-
is low cost. Missing an AI agent means a worse experience.
|
|
63
|
+
Optimizes for recall over precision. Serving markdown to a non-AI bot is cheap. Missing an AI agent is not.
|
|
47
64
|
|
|
48
65
|
## Core API
|
|
49
66
|
|
|
50
67
|
### `isAIAgent(request)`
|
|
51
68
|
|
|
52
|
-
Detect AI agents from request headers.
|
|
53
|
-
|
|
54
69
|
```ts
|
|
55
70
|
import { isAIAgent } from '@vercel/agent-readability'
|
|
56
71
|
|
|
@@ -58,19 +73,14 @@ const result = isAIAgent(request)
|
|
|
58
73
|
// { detected: true, method: 'ua-match' }
|
|
59
74
|
```
|
|
60
75
|
|
|
61
|
-
Accepts any object with
|
|
62
|
-
or a custom wrapper.
|
|
76
|
+
Accepts any object with `headers.get()` (`Request`, `NextRequest`, etc.)
|
|
63
77
|
|
|
64
|
-
Returns
|
|
78
|
+
Returns:
|
|
65
79
|
- `{ detected: true, method: 'ua-match' | 'signature-agent' | 'heuristic' }`
|
|
66
80
|
- `{ detected: false, method: null }`
|
|
67
81
|
|
|
68
|
-
When `detected` is `true`, `method` is always non-null. TypeScript narrows automatically.
|
|
69
|
-
|
|
70
82
|
### `acceptsMarkdown(request)`
|
|
71
83
|
|
|
72
|
-
Check if the request prefers markdown via the `Accept` header.
|
|
73
|
-
|
|
74
84
|
```ts
|
|
75
85
|
import { acceptsMarkdown } from '@vercel/agent-readability'
|
|
76
86
|
|
|
@@ -83,7 +93,7 @@ if (acceptsMarkdown(request)) {
|
|
|
83
93
|
|
|
84
94
|
### `shouldServeMarkdown(request)`
|
|
85
95
|
|
|
86
|
-
Combines
|
|
96
|
+
Combines detection + content negotiation.
|
|
87
97
|
|
|
88
98
|
```ts
|
|
89
99
|
import { shouldServeMarkdown } from '@vercel/agent-readability'
|
|
@@ -94,9 +104,8 @@ const { serve, reason } = shouldServeMarkdown(request)
|
|
|
94
104
|
|
|
95
105
|
### `generateNotFoundMarkdown(path, options?)`
|
|
96
106
|
|
|
97
|
-
|
|
98
|
-
(
|
|
99
|
-
because agents discard 404 response bodies.
|
|
107
|
+
Markdown body for missing pages. Return with 200 on the canonical URL
|
|
108
|
+
(agents discard 404 bodies).
|
|
100
109
|
|
|
101
110
|
```ts
|
|
102
111
|
import { generateNotFoundMarkdown } from '@vercel/agent-readability'
|
|
@@ -104,7 +113,6 @@ import { generateNotFoundMarkdown } from '@vercel/agent-readability'
|
|
|
104
113
|
const md = generateNotFoundMarkdown('/docs/missing', {
|
|
105
114
|
baseUrl: 'https://example.com',
|
|
106
115
|
})
|
|
107
|
-
// Return as 200 so agents read the body
|
|
108
116
|
return new Response(md, {
|
|
109
117
|
headers: { 'Content-Type': 'text/markdown', 'Vary': 'Accept' },
|
|
110
118
|
})
|
|
@@ -117,14 +125,13 @@ responses.
|
|
|
117
125
|
### Pattern Exports
|
|
118
126
|
|
|
119
127
|
`AI_AGENT_UA_PATTERNS`, `TRADITIONAL_BOT_PATTERNS`, `SIGNATURE_AGENT_DOMAINS`,
|
|
120
|
-
and `BOT_LIKE_REGEX` are exported
|
|
128
|
+
and `BOT_LIKE_REGEX` are all exported.
|
|
121
129
|
|
|
122
130
|
## Next.js Adapter
|
|
123
131
|
|
|
124
132
|
### `withAgentReadability(options, handler?)`
|
|
125
133
|
|
|
126
|
-
|
|
127
|
-
to markdown routes. Compatible with Next.js 14 and 15 (Pages and App Router).
|
|
134
|
+
Works with Next.js 14 and 15 (Pages and App Router).
|
|
128
135
|
|
|
129
136
|
```ts
|
|
130
137
|
import { withAgentReadability } from '@vercel/agent-readability/next'
|
|
@@ -138,18 +145,13 @@ export default withAgentReadability({
|
|
|
138
145
|
})
|
|
139
146
|
```
|
|
140
147
|
|
|
141
|
-
|
|
142
|
-
the response.
|
|
143
|
-
|
|
144
|
-
#### Composing with other middleware
|
|
148
|
+
`onDetection` runs via `event.waitUntil()` and does not block the response.
|
|
145
149
|
|
|
146
|
-
|
|
150
|
+
#### Composing with existing middleware
|
|
147
151
|
|
|
148
152
|
```ts
|
|
149
153
|
export default withAgentReadability(
|
|
150
|
-
{
|
|
151
|
-
rewrite: (p) => `/md${p}`,
|
|
152
|
-
},
|
|
154
|
+
{ rewrite: (p) => `/md${p}` },
|
|
153
155
|
(req, event) => i18nMiddleware(req, event),
|
|
154
156
|
)
|
|
155
157
|
```
|
|
@@ -164,8 +166,7 @@ export default withAgentReadability(
|
|
|
164
166
|
|
|
165
167
|
#### `agentReadabilityMatcher`
|
|
166
168
|
|
|
167
|
-
|
|
168
|
-
Use this for site-wide agent detection instead of scoping to a prefix:
|
|
169
|
+
Excludes Next.js internals and static files. Use for site-wide detection:
|
|
169
170
|
|
|
170
171
|
```ts
|
|
171
172
|
import { withAgentReadability, agentReadabilityMatcher } from '@vercel/agent-readability/next'
|
|
@@ -180,27 +181,90 @@ export const config = {
|
|
|
180
181
|
}
|
|
181
182
|
```
|
|
182
183
|
|
|
183
|
-
##
|
|
184
|
+
## SvelteKit Adapter
|
|
185
|
+
|
|
186
|
+
### `handleAgentReadability(options)`
|
|
184
187
|
|
|
185
|
-
|
|
188
|
+
Returns a `Handle` function. Requires SvelteKit 2+. Uses `event.fetch()`
|
|
189
|
+
for zero-cost internal routing to your `+server.ts` markdown routes.
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
// hooks.server.ts
|
|
193
|
+
import { handleAgentReadability } from '@vercel/agent-readability/sveltekit'
|
|
194
|
+
import { sequence } from '@sveltejs/kit/hooks'
|
|
195
|
+
|
|
196
|
+
export const handle = sequence(
|
|
197
|
+
handleAgentReadability({
|
|
198
|
+
docsPrefix: '/docs',
|
|
199
|
+
rewrite: (pathname) => `/api/docs-md${pathname}`,
|
|
200
|
+
onDetection: ({ path, method }) => {
|
|
201
|
+
console.log(`Agent detected: ${method} on ${path}`)
|
|
202
|
+
},
|
|
203
|
+
}),
|
|
204
|
+
)
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Automatically guards against infinite loops (`isSubRequest`), skips client
|
|
208
|
+
navigation requests (`isDataRequest`), and falls through if the rewrite
|
|
209
|
+
target returns non-OK.
|
|
210
|
+
|
|
211
|
+
#### Options
|
|
212
|
+
|
|
213
|
+
| Option | Type | Default | Description |
|
|
214
|
+
|--------|------|---------|-------------|
|
|
215
|
+
| `docsPrefix` | `string` | `'/docs'` | URL prefix to intercept |
|
|
216
|
+
| `rewrite` | `(pathname: string) => string` | required | Maps request path to `+server.ts` route |
|
|
217
|
+
| `onDetection` | `(info) => void \| Promise<void>` | - | Fire-and-forget analytics callback |
|
|
218
|
+
|
|
219
|
+
## Nuxt Adapter
|
|
220
|
+
|
|
221
|
+
### `defineAgentMiddleware(options)`
|
|
222
|
+
|
|
223
|
+
Wraps `defineEventHandler`. Requires h3 1.8+ (ships with Nuxt 3/4).
|
|
224
|
+
Uses a `getMarkdown` callback instead of rewrite since Nuxt has no
|
|
225
|
+
zero-cost internal fetch.
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
// server/middleware/agent.ts
|
|
229
|
+
import { defineAgentMiddleware } from '@vercel/agent-readability/nuxt'
|
|
230
|
+
|
|
231
|
+
export default defineAgentMiddleware({
|
|
232
|
+
docsPrefix: '/docs',
|
|
233
|
+
getMarkdown: async (pathname, event) => {
|
|
234
|
+
const doc = await queryContent(pathname).findOne()
|
|
235
|
+
return doc.body
|
|
236
|
+
},
|
|
237
|
+
})
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
`getMarkdown` can return a `string` (auto-wrapped with `text/markdown` headers)
|
|
241
|
+
or a `Response` for full control.
|
|
242
|
+
|
|
243
|
+
#### Options
|
|
244
|
+
|
|
245
|
+
| Option | Type | Default | Description |
|
|
246
|
+
|--------|------|---------|-------------|
|
|
247
|
+
| `docsPrefix` | `string` | `'/docs'` | URL prefix to intercept |
|
|
248
|
+
| `getMarkdown` | `(pathname, event) => string \| Response \| Promise<...>` | required | Returns markdown content |
|
|
249
|
+
| `onDetection` | `(info) => void \| Promise<void>` | - | Fire-and-forget analytics callback |
|
|
250
|
+
|
|
251
|
+
## Audit CLI
|
|
186
252
|
|
|
187
253
|
```bash
|
|
188
254
|
npx @vercel/agent-readability audit https://sdk.vercel.ai
|
|
189
255
|
```
|
|
190
256
|
|
|
191
|
-
|
|
192
|
-
|
|
257
|
+
25 weighted checks across 4 categories. Score 0-100. Failed checks include
|
|
258
|
+
fix suggestions you can paste into your coding agent.
|
|
193
259
|
|
|
194
|
-
### CI
|
|
260
|
+
### CI
|
|
195
261
|
|
|
196
262
|
```yaml
|
|
197
263
|
- name: Audit agent readability
|
|
198
264
|
run: npx @vercel/agent-readability audit ${{ env.SITE_URL }} --min-score 70 --json
|
|
199
265
|
```
|
|
200
266
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
### CLI Options
|
|
267
|
+
Exit code 1 if score is below threshold.
|
|
204
268
|
|
|
205
269
|
| Flag | Description |
|
|
206
270
|
|------|-------------|
|
|
@@ -209,23 +273,19 @@ Exits with code 1 if the score is below the threshold.
|
|
|
209
273
|
|
|
210
274
|
## Caching
|
|
211
275
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
Set `Vary: Accept` on all markdown responses. Without it, CDNs may serve
|
|
216
|
-
cached HTML to agents or cached markdown to browsers.
|
|
276
|
+
Set `Vary: Accept` on markdown responses so CDNs don't serve cached
|
|
277
|
+
HTML to agents (or cached markdown to browsers).
|
|
217
278
|
|
|
218
|
-
The
|
|
219
|
-
|
|
220
|
-
|
|
279
|
+
The SvelteKit and Nuxt adapters set `Vary: Accept` automatically. The
|
|
280
|
+
Next.js adapter rewrites the URL, so your markdown route handler must
|
|
281
|
+
set `Vary: Accept` and `Content-Type: text/markdown`.
|
|
221
282
|
|
|
222
283
|
## Edge Runtime
|
|
223
284
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
environment.
|
|
285
|
+
Core library and all adapters use Web APIs only. Works in Vercel Edge
|
|
286
|
+
Runtime, Cloudflare Workers, and any `Request`/`Response` environment.
|
|
227
287
|
|
|
228
|
-
|
|
288
|
+
CLI requires Node.js 20+.
|
|
229
289
|
|
|
230
290
|
## License
|
|
231
291
|
|