@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 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 run the audit CLI directly without installing:
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
- Add AI agent detection to your Next.js middleware in three lines:
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
- matcher: ['/docs/:path*'],
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
- AI agents hitting `/docs/*` now receive markdown instead of HTML.
53
+ Agents hitting `/docs/*` get markdown instead of HTML.
36
54
 
37
- ## What It Does
55
+ ## How Detection Works
38
56
 
39
- Three-layer detection identifies AI agents from HTTP request headers:
57
+ Three layers, checked in order:
40
58
 
41
- 1. **Known UA patterns.** 40+ agents including ClaudeBot, GPTBot, Cursor, and Perplexity.
42
- 2. **Signature-Agent header.** Catches ChatGPT agent (RFC 9421).
43
- 3. **sec-fetch-mode heuristic.** Catches unknown bots that lack browser fingerprints.
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
- The detection optimizes for recall over precision. Serving markdown to a non-AI bot
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 a `headers.get()` method: `Request`, `NextRequest`,
62
- or a custom wrapper.
76
+ Accepts any object with `headers.get()` (`Request`, `NextRequest`, etc.)
63
77
 
64
- Returns a discriminated union:
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 agent detection and content negotiation into one call.
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
- Generates a markdown body for missing pages. Return this with a 200 status
98
- (not 404) for agent or `Accept: text/markdown` requests on the canonical URL
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 for consumers who need to extend or inspect them.
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
- Middleware wrapper that detects AI agents and rewrites matching requests
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
- The `onDetection` callback runs via `event.waitUntil()` and does not block
142
- the response.
143
-
144
- #### Composing with other middleware
148
+ `onDetection` runs via `event.waitUntil()` and does not block the response.
145
149
 
146
- Pass your existing middleware as the second argument:
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
- A pre-built matcher that excludes Next.js internals and static files.
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
- ## Audit CLI
184
+ ## SvelteKit Adapter
185
+
186
+ ### `handleAgentReadability(options)`
184
187
 
185
- Check your site against the Agent Readability Spec.
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
- Runs 16 weighted checks across three categories. Returns a score from 0 to 100.
192
- Failed checks include fix suggestions that can be copy-pasted into your coding agent.
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 Integration
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
- Exits with code 1 if the score is below the threshold.
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
- When the same URL serves HTML to browsers and markdown to AI agents,
213
- CDN caching must include the `Accept` header in the cache key.
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 `withAgentReadability` middleware handles detection and rewriting.
219
- Your markdown route handler is responsible for setting response headers
220
- including `Vary: Accept` and `Content-Type: text/markdown`.
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
- The core library and Next.js adapter use only Web APIs. They work in
225
- Vercel Edge Runtime, Cloudflare Workers, and any standard `Request`/`Response`
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
- The CLI runs on Node.js 20+.
288
+ CLI requires Node.js 20+.
229
289
 
230
290
  ## License
231
291