@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 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,8 +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) because agents discard 404 response bodies.
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 for consumers who need to extend or inspect them.
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
- Middleware wrapper that detects AI agents and rewrites matching requests
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
- The `onDetection` callback runs via `event.waitUntil()` and does not block
137
- the response.
148
+ `onDetection` runs via `event.waitUntil()` and does not block the response.
138
149
 
139
- #### Composing with other middleware
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
- A pre-built matcher that excludes Next.js internals and static files.
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
- ## Audit CLI
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
- Check your site against the Agent Readability Spec.
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
- Runs 16 weighted checks across three categories. Returns a score from 0 to 100.
187
- 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.
188
259
 
189
- ### CI Integration
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
- Exits with code 1 if the score is below the threshold.
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
- When the same URL serves HTML to browsers and markdown to AI agents,
208
- CDN caching must include the `Accept` header in the cache key.
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 `withAgentReadability` middleware handles detection and rewriting.
214
- Your markdown route handler is responsible for setting response headers
215
- 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`.
216
282
 
217
283
  ## Edge Runtime
218
284
 
219
- The core library and Next.js adapter use only Web APIs. They work in
220
- Vercel Edge Runtime, Cloudflare Workers, and any standard `Request`/`Response`
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
- The CLI runs on Node.js 20+.
288
+ CLI requires Node.js 20+.
224
289
 
225
290
  ## License
226
291