@vercel/agent-readability 0.3.0 → 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 +127 -62
- package/dist/cli/index.cjs +26 -26
- package/dist/index.cjs +3 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -2
- package/dist/index.d.ts +10 -2
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -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 +47 -9
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,8 +104,8 @@ const { serve, reason } = shouldServeMarkdown(request)
|
|
|
94
104
|
|
|
95
105
|
### `generateNotFoundMarkdown(path, options?)`
|
|
96
106
|
|
|
97
|
-
|
|
98
|
-
(
|
|
107
|
+
Markdown body for missing pages. Return with 200 on the canonical URL
|
|
108
|
+
(agents discard 404 bodies).
|
|
99
109
|
|
|
100
110
|
```ts
|
|
101
111
|
import { generateNotFoundMarkdown } from '@vercel/agent-readability'
|
|
@@ -103,23 +113,25 @@ import { generateNotFoundMarkdown } from '@vercel/agent-readability'
|
|
|
103
113
|
const md = generateNotFoundMarkdown('/docs/missing', {
|
|
104
114
|
baseUrl: 'https://example.com',
|
|
105
115
|
})
|
|
106
|
-
// Return as 200 so agents read the body
|
|
107
116
|
return new Response(md, {
|
|
108
|
-
headers: { 'Content-Type': 'text/markdown' },
|
|
117
|
+
headers: { 'Content-Type': 'text/markdown', 'Vary': 'Accept' },
|
|
109
118
|
})
|
|
110
119
|
```
|
|
111
120
|
|
|
121
|
+
Keep suggested page links canonical (`/docs/page`) and negotiate markdown with
|
|
122
|
+
`Accept: text/markdown` rather than exposing `.md` page URLs in not-found
|
|
123
|
+
responses.
|
|
124
|
+
|
|
112
125
|
### Pattern Exports
|
|
113
126
|
|
|
114
127
|
`AI_AGENT_UA_PATTERNS`, `TRADITIONAL_BOT_PATTERNS`, `SIGNATURE_AGENT_DOMAINS`,
|
|
115
|
-
and `BOT_LIKE_REGEX` are exported
|
|
128
|
+
and `BOT_LIKE_REGEX` are all exported.
|
|
116
129
|
|
|
117
130
|
## Next.js Adapter
|
|
118
131
|
|
|
119
132
|
### `withAgentReadability(options, handler?)`
|
|
120
133
|
|
|
121
|
-
|
|
122
|
-
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).
|
|
123
135
|
|
|
124
136
|
```ts
|
|
125
137
|
import { withAgentReadability } from '@vercel/agent-readability/next'
|
|
@@ -133,18 +145,13 @@ export default withAgentReadability({
|
|
|
133
145
|
})
|
|
134
146
|
```
|
|
135
147
|
|
|
136
|
-
|
|
137
|
-
the response.
|
|
148
|
+
`onDetection` runs via `event.waitUntil()` and does not block the response.
|
|
138
149
|
|
|
139
|
-
#### Composing with
|
|
140
|
-
|
|
141
|
-
Pass your existing middleware as the second argument:
|
|
150
|
+
#### Composing with existing middleware
|
|
142
151
|
|
|
143
152
|
```ts
|
|
144
153
|
export default withAgentReadability(
|
|
145
|
-
{
|
|
146
|
-
rewrite: (p) => `/md${p}`,
|
|
147
|
-
},
|
|
154
|
+
{ rewrite: (p) => `/md${p}` },
|
|
148
155
|
(req, event) => i18nMiddleware(req, event),
|
|
149
156
|
)
|
|
150
157
|
```
|
|
@@ -159,8 +166,7 @@ export default withAgentReadability(
|
|
|
159
166
|
|
|
160
167
|
#### `agentReadabilityMatcher`
|
|
161
168
|
|
|
162
|
-
|
|
163
|
-
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:
|
|
164
170
|
|
|
165
171
|
```ts
|
|
166
172
|
import { withAgentReadability, agentReadabilityMatcher } from '@vercel/agent-readability/next'
|
|
@@ -175,27 +181,90 @@ export const config = {
|
|
|
175
181
|
}
|
|
176
182
|
```
|
|
177
183
|
|
|
178
|
-
##
|
|
184
|
+
## SvelteKit Adapter
|
|
185
|
+
|
|
186
|
+
### `handleAgentReadability(options)`
|
|
187
|
+
|
|
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
|
+
```
|
|
179
206
|
|
|
180
|
-
|
|
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
|
|
181
252
|
|
|
182
253
|
```bash
|
|
183
254
|
npx @vercel/agent-readability audit https://sdk.vercel.ai
|
|
184
255
|
```
|
|
185
256
|
|
|
186
|
-
|
|
187
|
-
|
|
257
|
+
25 weighted checks across 4 categories. Score 0-100. Failed checks include
|
|
258
|
+
fix suggestions you can paste into your coding agent.
|
|
188
259
|
|
|
189
|
-
### CI
|
|
260
|
+
### CI
|
|
190
261
|
|
|
191
262
|
```yaml
|
|
192
263
|
- name: Audit agent readability
|
|
193
264
|
run: npx @vercel/agent-readability audit ${{ env.SITE_URL }} --min-score 70 --json
|
|
194
265
|
```
|
|
195
266
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
### CLI Options
|
|
267
|
+
Exit code 1 if score is below threshold.
|
|
199
268
|
|
|
200
269
|
| Flag | Description |
|
|
201
270
|
|------|-------------|
|
|
@@ -204,23 +273,19 @@ Exits with code 1 if the score is below the threshold.
|
|
|
204
273
|
|
|
205
274
|
## Caching
|
|
206
275
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
Set `Vary: Accept` on all markdown responses. Without it, CDNs may serve
|
|
211
|
-
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).
|
|
212
278
|
|
|
213
|
-
The
|
|
214
|
-
|
|
215
|
-
|
|
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`.
|
|
216
282
|
|
|
217
283
|
## Edge Runtime
|
|
218
284
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
environment.
|
|
285
|
+
Core library and all adapters use Web APIs only. Works in Vercel Edge
|
|
286
|
+
Runtime, Cloudflare Workers, and any `Request`/`Response` environment.
|
|
222
287
|
|
|
223
|
-
|
|
288
|
+
CLI requires Node.js 20+.
|
|
224
289
|
|
|
225
290
|
## License
|
|
226
291
|
|