@apideck/agent-analytics 0.15.0 → 0.17.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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/payments.ts","../src/bots.ts","../src/policy.ts","../src/gateway.ts","../src/entitlement.ts"],"names":["X402_CHALLENGE","X402_SIGNATURE","X402_SETTLEMENT","MPP_CHALLENGE","MPP_CREDENTIAL","quoted","v","b64","json","text","bytes","bin","b","paymentRequired","opts","headers","c","params","k","paymentPayload","req","x402","auth","m","hasPaymentPayload","withSettlement","res","settlement","respondToDecision","decision","AI_BOT_PATTERN","HTTP_CLIENT_PATTERN","isAiBot","userAgent","isHttpClient","parseBotName","s","detectHeadless","signals","ua","secChUa","accept","score","classifyAgent","label","aiBot","httpClient","kind","classifyRequest","base","headless","RETRIEVAL","TRAINING","SEARCH","PREVIEW","agentIntent","agentPolicy","intent","entry","resolved","verification","mppxGateway","handler","out","x402Gateway","settle","receipt","challenge","sig","paymentGate","gateway","onDecision","verify","meter","policyOpts","identity","path","method","offerDescription","offer","days","window","entitlementGateway","store","exposeRemaining","submitted","ent","now","left","h","memoryEntitlementStore","seed","map","credential"],"mappings":"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","file":"pay.cjs","sourcesContent":["/**\n * Charge for training crawls. **EXPERIMENTAL.**\n *\n * The protocols this speaks are weeks old and moving. x402 and MPP are both\n * live but their specs are unstable, MPP's settlement-confirmation header was\n * not pinned publicly at the time of writing, and no agent in our production\n * traffic has yet presented a payment credential. Expect this API to change\n * without a major version while that settles — everything else in the package\n * is stable, this is not.\n *\n * Today the industry's answer to bulk AI crawling is `Disallow` — over 2.5\n * million sites block AI training in robots.txt. That leaves money on the\n * table and depends on the crawler's goodwill to work at all.\n *\n * The alternative is to let them train and price it. That only works if you\n * can tell training from retrieval, because they have opposite economics: a\n * `GPTBot` fetch is corpus collection you get nothing back for, while a\n * `ChatGPT-User` fetch is a person asking about you — charging for the second\n * is charging for your own distribution. {@link agentPolicy} draws that line;\n * this module turns a `'charge'` decision into the HTTP challenge.\n *\n * Two protocols, one status code. Both settle at the HTTP layer and both use\n * 402, but the framing differs:\n *\n * x402 PAYMENT-REQUIRED: <base64 JSON> -> PAYMENT-SIGNATURE\n * MPP WWW-Authenticate: Payment id=\"…\" -> Authorization: Payment …\n *\n * MPP reuses standard HTTP authentication framing; x402 defines its own\n * headers. They do not collide, so a single 402 can advertise both and let the\n * agent pick — which is what {@link paymentRequired} does when given both.\n *\n * Scope: this emits the 402 and reads the client's payment header. It does not\n * settle anything. Settlement belongs to an x402 facilitator or Stripe's MPP —\n * a library that held money would inherit PCI scope and stop being something\n * you can drop into middleware.\n */\n\nimport type { AgentDecision } from './policy.js'\n\n/**\n * One way a client may pay. Field names follow x402's `PaymentRequirements`;\n * values are yours — the library never invents an amount, network or asset.\n */\nexport interface PaymentRequirements {\n scheme: string\n network: string\n maxAmountRequired: string\n resource: string\n description?: string\n mimeType?: string\n payTo: string\n maxTimeoutSeconds?: number\n asset: string\n extra?: Record<string, unknown>\n}\n\n/** Which settlement protocol a challenge speaks. */\nexport type PaymentProtocol = 'x402' | 'mpp'\n\n/** x402: base64 JSON in a `PAYMENT-REQUIRED` header. */\nexport interface X402Challenge {\n protocol: 'x402'\n /** Accepted payment methods, in preference order. At least one. */\n accepts: readonly PaymentRequirements[]\n /** Protocol version. Defaults to 1. */\n x402Version?: number\n}\n\n/**\n * MPP: an RFC 9110 `WWW-Authenticate: Payment` challenge.\n *\n * Field values are yours. `request` carries the encoded challenge payload your\n * MPP provider generates — the library does not construct or price it.\n */\nexport interface MppChallenge {\n protocol: 'mpp'\n /** Challenge identifier. */\n id: string\n /** Authentication realm. */\n realm: string\n /** Payment method, e.g. `'tempo'`. */\n method: string\n /** Transaction intent, e.g. `'charge'`. */\n intent?: string\n /** Encoded challenge data from your provider. */\n request?: string\n}\n\nexport type PaymentChallenge = X402Challenge | MppChallenge\n\nexport interface PaymentChallengeOptions {\n /**\n * Challenges to advertise. Supplying both an x402 and an MPP challenge is\n * valid and usually correct: they use non-colliding headers, so one 402 can\n * offer both and the agent takes whichever it speaks.\n */\n challenges: readonly PaymentChallenge[]\n /**\n * `Content-Signal` to send with the challenge. Defaults to\n * `search=yes, ai-input=yes, ai-train=paid` — the whole point being that\n * training is available rather than forbidden.\n */\n contentSignal?: string\n /** Extra response headers. */\n headers?: Record<string, string>\n /** Human-readable body. Agents read the header; people read logs. */\n body?: string\n}\n\nconst X402_CHALLENGE = 'PAYMENT-REQUIRED'\nconst X402_SIGNATURE = 'PAYMENT-SIGNATURE'\nconst X402_SETTLEMENT = 'PAYMENT-RESPONSE'\nconst MPP_CHALLENGE = 'WWW-Authenticate'\nconst MPP_CREDENTIAL = 'Authorization'\n\n/** Quote and escape a WWW-Authenticate auth-param value per RFC 9110. */\nfunction quoted(v: string): string {\n return `\"${v.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"')}\"`\n}\n\nfunction b64(json: unknown): string {\n const text = JSON.stringify(json)\n // btoa is Latin-1 only; encode first so non-ASCII descriptions survive.\n const bytes = new TextEncoder().encode(text)\n let bin = ''\n for (const b of bytes) bin += String.fromCharCode(b)\n return btoa(bin)\n}\n\n/**\n * Build a 402 challenge.\n *\n * @example\n * ```ts\n * const decision = agentPolicy(req, { onTraining: 'charge' })\n * if (decision.action === 'charge') {\n * return paymentRequired({\n * challenges: [\n * {\n * protocol: 'x402',\n * accepts: [{\n * scheme: 'exact',\n * network: 'base',\n * maxAmountRequired: '1000', // your price, your units\n * resource: req.url,\n * payTo: process.env.WALLET!,\n * asset: process.env.USDC!\n * }]\n * },\n * { protocol: 'mpp', id: challengeId, realm: 'example.com', method: 'tempo', intent: 'charge' }\n * ]\n * })\n * }\n * ```\n */\nexport function paymentRequired(opts: PaymentChallengeOptions): Response {\n if (!opts.challenges.length) {\n throw new Error('paymentRequired needs at least one challenge')\n }\n\n const headers = new Headers({\n 'content-type': 'text/plain; charset=utf-8',\n // Says the quiet part out loud: training is for sale, not forbidden.\n 'content-signal': opts.contentSignal ?? 'search=yes, ai-input=yes, ai-train=paid'\n })\n\n for (const c of opts.challenges) {\n if (c.protocol === 'x402') {\n if (!c.accepts.length) {\n throw new Error('an x402 challenge needs at least one entry in `accepts`')\n }\n headers.set(X402_CHALLENGE, b64({ x402Version: c.x402Version ?? 1, accepts: c.accepts }))\n } else {\n const params = [\n `id=${quoted(c.id)}`,\n `realm=${quoted(c.realm)}`,\n `method=${quoted(c.method)}`,\n ...(c.intent ? [`intent=${quoted(c.intent)}`] : []),\n ...(c.request ? [`request=${quoted(c.request)}`] : [])\n ]\n // `append`, not `set`: WWW-Authenticate legitimately carries multiple\n // challenges, and a caller may already have added one.\n headers.append(MPP_CHALLENGE, `Payment ${params.join(', ')}`)\n }\n }\n\n for (const [k, v] of Object.entries(opts.headers ?? {})) headers.set(k, v)\n\n return new Response(opts.body ?? 'Payment required for training access.\\n', {\n status: 402,\n headers\n })\n}\n\n/** A payment credential the client sent back, and which protocol it speaks. */\nexport interface SubmittedPayment {\n protocol: PaymentProtocol\n /** Raw header value, for handing to a facilitator. */\n value: string\n}\n\n/**\n * Read the client's payment credential, whichever protocol it used.\n *\n * x402 sends `PAYMENT-SIGNATURE`; MPP sends `Authorization: Payment …`. The\n * `Payment` scheme check matters — a site behind normal auth will also have a\n * Bearer or Basic `Authorization` header, and mistaking that for a payment\n * would be a security-relevant confusion.\n */\nexport function paymentPayload(req: Request): SubmittedPayment | null {\n const x402 = req.headers.get(X402_SIGNATURE)\n if (x402) return { protocol: 'x402', value: x402 }\n\n const auth = req.headers.get(MPP_CREDENTIAL)\n if (auth) {\n const m = auth.match(/^Payment\\s+(.*)$/i)\n if (m?.[1]) return { protocol: 'mpp', value: m[1] }\n }\n return null\n}\n\n/**\n * True when the client attached a payment credential — i.e. this is the retry\n * after a 402, not a fresh unpaid request.\n *\n * Presence is not proof. Hand the value to your facilitator to verify and\n * settle; only then serve the resource.\n */\nexport function hasPaymentPayload(req: Request): boolean {\n return paymentPayload(req) !== null\n}\n\n/**\n * Attach a facilitator's settlement result to a successful response.\n *\n * x402 defines `PAYMENT-RESPONSE` for this. MPP's public spec did not pin a\n * settlement-confirmation header at the time of writing, so pass `header` to\n * name whatever your provider expects rather than have the library guess.\n */\nexport function withSettlement(\n res: Response,\n settlement: unknown,\n opts: { header?: string } = {}\n): Response {\n const headers = new Headers(res.headers)\n headers.set(opts.header ?? X402_SETTLEMENT, b64(settlement))\n return new Response(res.body, { status: res.status, statusText: res.statusText, headers })\n}\n\n/**\n * Convenience: turn an {@link AgentDecision} straight into a response, or\n * `null` when the request should simply be served.\n *\n * Returns 403 for `'block'`, a 402 challenge for `'charge'`, and `null` for\n * `'allow'` and `'meter'` — metering is an accounting concern, not a gate, so\n * the request still gets served while `trackVisit` records it.\n */\nexport function respondToDecision(\n decision: AgentDecision,\n opts: PaymentChallengeOptions\n): Response | null {\n if (decision.action === 'block') {\n return new Response('Forbidden: agent identity could not be verified.\\n', { status: 403 })\n }\n if (decision.action === 'charge') {\n return paymentRequired({\n body: `Payment required: ${decision.label} — ${decision.reason}.\\n`,\n ...opts\n })\n }\n return null\n}\n","/**\n * User-agent substrings that identify **publicly declared** AI crawlers — the\n * branded bots that identify themselves by name (OpenAI's GPTBot, Anthropic's\n * ClaudeBot, Perplexity-User, Google-Extended, etc.). High-confidence: when\n * this matches, the request almost certainly comes from that vendor's crawler\n * fleet.\n *\n * Does NOT include **coding-agent traffic** (Claude Code, Cline, Cursor,\n * Windsurf, Aider, OpenCode, VS Code). Those tools use generic HTTP library\n * UAs (axios, curl, got, colly, Electron) or spoof full browser UAs — they\n * can't be distinguished from non-AI traffic by UA alone. See\n * {@link HTTP_CLIENT_PATTERN} for the loose heuristic layer.\n *\n * Sources consulted when updating: darkvisitors.com, vendor docs from OpenAI,\n * Anthropic, Google, Perplexity, Cohere, Apple, Bytedance.\n */\nexport const AI_BOT_PATTERN =\n /ClaudeBot|Claude-User|Claude-SearchBot|Claude-Web|Anthropic|GPTBot|ChatGPT-User|OAI-SearchBot|PerplexityBot|Perplexity-User|Google-Extended|Google-CloudVertexBot|Google-Agent|GoogleAgent-Mariner|Gemini-Deep-Research|Applebot|cohere|Bytespider|CCBot|Amazonbot|Amzn-SearchBot|NovaAct|AzureAI-SearchBot|Meta-ExternalAgent|meta-externalfetcher|meta-webindexer|FacebookBot|DuckAssistBot|MistralAI-User|YouBot|AI2Bot|Diffbot|DeepSeek|PanguBot|Webzio-Extended|omgili|Timpibot|Grok|Manus-User|quillbot|MyCentralAIScraperBot|Cursor|Windsurf/i\n\n/**\n * HTTP library / runtime signatures frequently used by coding agents. Matching\n * any of these is a **loose** signal — legitimate curl scripts, CI jobs, and\n * server-to-server traffic use the same libraries. Use this for the wider\n * net (`coding_agent_hint: true`) and pair with other signals (request\n * shape, JA4 fingerprint, path patterns) for higher confidence.\n *\n * Based on behavioural signatures observed by Addy Osmani:\n * Claude Code → axios/1.8.4\n * Cline, Junie → curl/8.4.0\n * Cursor → got (sindresorhus/got)\n * Windsurf → colly\n * VS Code → Electron / Chromium\n *\n * Aider and OpenCode use Playwright-driven full Mozilla/Safari UAs and are\n * indistinguishable from real browsers at the UA layer.\n */\nexport const HTTP_CLIENT_PATTERN =\n /axios\\/|curl\\/|(?:^|[\\s(])got(?:\\/|[\\s(])|\\bcolly\\b|Electron\\/|node-fetch\\/|python-requests\\/|Go-http-client\\/|okhttp\\/|aiohttp\\/|Deno\\//i\n\nexport function isAiBot(userAgent: string | null | undefined): boolean {\n if (!userAgent) return false\n return AI_BOT_PATTERN.test(userAgent)\n}\n\nexport function isHttpClient(userAgent: string | null | undefined): boolean {\n if (!userAgent) return false\n return HTTP_CLIENT_PATTERN.test(userAgent)\n}\n\n/**\n * Map a user-agent string to a coarse, human-readable label. Returns one of:\n *\n * - A branded-crawler name (`'Claude'`, `'ChatGPT'`, …) — pair with\n * {@link isAiBot} for `is_ai_bot: true` segmentation.\n * - An HTTP-library name (`'curl'`, `'axios'`, `'got'`, `'colly'`,\n * `'Electron'`, …) — hint of a coding agent or automation; not\n * conclusive. Pair with {@link isHttpClient}.\n * - `'Browser'` for typical desktop browsers (possibly spoofed by\n * Playwright-based agents like Aider/OpenCode — this label alone can't\n * tell you).\n * - `'Other'` for anything unrecognised or empty input.\n */\nexport function parseBotName(userAgent: string | null | undefined): string {\n if (!userAgent || typeof userAgent !== 'string') return 'Other'\n const s = userAgent.toLowerCase()\n\n // Publicly declared AI crawlers (high confidence).\n if (s.includes('chatgpt-user') || s.includes('gptbot') || s.includes('oai-searchbot') || s.includes('openai'))\n return 'ChatGPT'\n if (\n s.includes('claudebot') ||\n s.includes('claude-user') ||\n s.includes('claude-searchbot') ||\n s.includes('claude-web') ||\n s.includes('anthropic')\n )\n return 'Claude'\n if (s.includes('perplexitybot') || s.includes('perplexity-user')) return 'Perplexity'\n if (s.includes('ccbot')) return 'Common Crawl'\n if (\n s.includes('google-extended') ||\n s.includes('googlebot') ||\n s.includes('google-cloudvertexbot') ||\n s.includes('google-agent') ||\n s.includes('googleagent-mariner') ||\n s.includes('gemini-deep-research')\n )\n return 'Google'\n if (s.includes('applebot')) return 'Apple'\n if (s.includes('bingbot')) return 'Bing'\n if (s.includes('bytespider')) return 'Bytespider'\n if (s.includes('amazonbot') || s.includes('amzn-searchbot') || s.includes('novaact')) return 'Amazon'\n if (\n s.includes('meta-externalagent') ||\n s.includes('meta-externalfetcher') ||\n s.includes('meta-webindexer') ||\n s.includes('facebookbot')\n )\n return 'Meta'\n if (s.includes('mistralai-user')) return 'Mistral'\n if (s.includes('duckassistbot')) return 'DuckDuckGo'\n if (s.includes('youbot')) return 'You.com'\n if (s.includes('diffbot')) return 'Diffbot'\n if (s.includes('ai2bot')) return 'AI2'\n if (s.includes('cohere')) return 'Cohere'\n if (s.includes('cursor')) return 'Cursor'\n if (s.includes('windsurf')) return 'Windsurf'\n if (s.includes('deepseek')) return 'DeepSeek'\n if (s.includes('pangubot')) return 'Huawei'\n if (s.includes('webzio') || s.includes('omgili')) return 'Webz.io'\n if (s.includes('timpibot')) return 'Timpi'\n if (s.includes('grok') || s.includes('xai-')) return 'xAI'\n if (s.includes('manus-user')) return 'Manus'\n if (s.includes('quillbot')) return 'QuillBot'\n if (s.includes('azureai-searchbot')) return 'Microsoft'\n if (s.includes('mycentralaiscraperbot')) return 'MyCentralAI'\n if (s.includes('petalbot')) return 'PetalBot'\n\n // SEO crawlers and monitoring bots.\n if (s.includes('ahrefsbot')) return 'Ahrefs'\n if (s.includes('semrushbot')) return 'Semrush'\n if (s.includes('mj12bot')) return 'Majestic'\n if (s.includes('dotbot')) return 'Moz'\n if (s.includes('rogerbot')) return 'Moz'\n if (s.includes('screaming frog')) return 'Screaming Frog'\n if (s.includes('sitebulb')) return 'Sitebulb'\n if (s.includes('linkfluence')) return 'Linkfluence'\n if (s.includes('dataforseo')) return 'DataForSEO'\n if (s.includes('serpstatbot')) return 'Serpstat'\n\n // Monitoring and feed bots.\n if (s.includes('uptimerobot')) return 'UptimeRobot'\n if (s.includes('pingdom')) return 'Pingdom'\n if (s.includes('statuscake')) return 'StatusCake'\n if (s.includes('newrelicpinger')) return 'New Relic'\n if (s.includes('datadogagent') || s.includes('datadog')) return 'Datadog'\n if (s.includes('slackbot')) return 'Slack'\n if (s.includes('twitterbot')) return 'Twitter'\n if (s.includes('linkedinbot')) return 'LinkedIn'\n if (s.includes('discordbot')) return 'Discord'\n if (s.includes('telegrambot')) return 'Telegram'\n if (s.includes('whatsapp')) return 'WhatsApp'\n\n // AI search and indexing bots.\n if (s.includes('linkupbot')) return 'Linkup'\n if (s.includes('sogou')) return 'Sogou'\n if (s.includes('yandexbot')) return 'Yandex'\n if (s.includes('baiduspider')) return 'Baidu'\n\n // Link preview fetchers.\n if (s.includes('facebookexternalhit')) return 'Facebook'\n if (s.includes('com.apple.webkit')) return 'Apple URL Preview'\n\n // Uptime and monitoring.\n if (s.includes('ohdear')) return 'Oh Dear'\n\n // Generic scrapers.\n if (s.includes('scrapy')) return 'Scrapy'\n if (s.includes('headlesschrome')) return 'Headless Chrome'\n if (s.includes('phantomjs')) return 'PhantomJS'\n if (s.includes('wget')) return 'wget'\n if (s.includes('httpie')) return 'HTTPie'\n if (s.includes('guzzlehttp')) return 'Guzzle'\n\n // HTTP library / runtime signatures (loose — coding agent or automation).\n // Check Electron before Browser since Electron UAs contain Chrome/Safari.\n if (s.includes('electron/')) return 'Electron'\n if (/curl\\//.test(s)) return 'curl'\n if (/axios\\//.test(s)) return 'axios'\n if (/(?:^|[\\s(])got(?:\\/|[\\s(])/.test(s)) return 'got'\n if (/\\bcolly\\b/.test(s)) return 'colly'\n if (/node-fetch\\//.test(s)) return 'node-fetch'\n if (/python-requests\\//.test(s)) return 'python-requests'\n if (/go-http-client\\//.test(s)) return 'Go http client'\n if (/okhttp\\//.test(s)) return 'OkHttp'\n if (/aiohttp\\//.test(s)) return 'aiohttp'\n if (/deno\\//.test(s)) return 'Deno'\n\n // Real browsers (or UAs spoofed to look like them — see Aider/OpenCode note).\n if (s.includes('mozilla') || s.includes('chrome') || s.includes('safari') || s.includes('firefox'))\n return 'Browser'\n\n return 'Other'\n}\n\n/**\n * Return the first product token from a UA header, useful for segmenting by\n * client without hard-coding every bot name. Falls back to `'Other'` for empty\n * input.\n */\nexport function firstUserAgentProduct(userAgent: string | null | undefined): string {\n if (!userAgent || typeof userAgent !== 'string') return 'Other'\n const compatibleMatch = userAgent.match(/compatible;\\s*([^/;\\s]+)(?:\\/[^\\s;]*)?/i)\n if (compatibleMatch && compatibleMatch[1]) return compatibleMatch[1].trim()\n const first = userAgent.trim().split('/')[0]?.trim().split(/\\s+/)[0]?.trim()\n return first || 'Other'\n}\n\n/**\n * Detect likely headless/automated browsers by checking for missing headers\n * that real browsers always send. Playwright, Puppeteer, and similar tools\n * spoof the UA but often omit standard browser headers.\n *\n * Signals checked (each scores 1 point):\n * - Missing `Accept-Language` — every real browser sends this\n * - Missing `Sec-Fetch-Mode` — sent by all modern browsers\n * - Missing `Sec-CH-UA` — Client Hints, Chromium 89+\n * - `Sec-CH-UA` contains \"HeadlessChrome\"\n * - Missing or bare Accept header — browsers send detailed accept lists\n * - `Connection: close` with browser UA — browsers use keep-alive\n *\n * Returns a score (0-6), the signals that fired, and a boolean `likely`\n * flag (score >= 2 with a browser-like UA).\n */\nexport function detectHeadless(req: Request): HeadlessDetection {\n const signals: string[] = []\n const ua = (req.headers.get('user-agent') || '').toLowerCase()\n const isBrowserUA =\n ua.includes('mozilla') || ua.includes('chrome') || ua.includes('safari') || ua.includes('firefox')\n\n if (!isBrowserUA) return { score: 0, signals: [], likely: false }\n\n if (!req.headers.get('accept-language')) {\n signals.push('missing-accept-language')\n }\n if (!req.headers.get('sec-fetch-mode')) {\n signals.push('missing-sec-fetch-mode')\n }\n const secChUa = req.headers.get('sec-ch-ua')\n if (!secChUa) {\n signals.push('missing-sec-ch-ua')\n } else if (secChUa.toLowerCase().includes('headlesschrome')) {\n signals.push('headless-chrome-hint')\n }\n const accept = req.headers.get('accept') || ''\n if (!accept || accept === '*/*') {\n signals.push('missing-or-bare-accept')\n }\n if ((req.headers.get('connection') || '').toLowerCase() === 'close') {\n signals.push('connection-close')\n }\n\n const score = signals.length\n return { score, signals, likely: score >= 2 }\n}\n\nexport interface HeadlessDetection {\n /** Number of suspicious signals found (0-6). */\n score: number\n /** Names of the specific signals that fired. */\n signals: string[]\n /** True when score >= 2 — strong headless indication. */\n likely: boolean\n}\n\nexport type AgentKind =\n | 'declared-crawler'\n | 'coding-agent-hint'\n | 'headless-likely'\n | 'browser'\n | 'other'\n\nexport interface AgentClassification {\n /**\n * Categorical tag for the request:\n *\n * - `'declared-crawler'` — {@link AI_BOT_PATTERN} matched. High confidence.\n * - `'coding-agent-hint'` — {@link HTTP_CLIENT_PATTERN} matched. Loose\n * signal; could be a coding agent, a curl script, or any automation.\n * - `'headless-likely'` — Browser-like UA but missing standard headers.\n * Strong signal of Playwright/Puppeteer automation (Aider, OpenCode, etc.).\n * - `'browser'` — Looks like a real browser with expected headers present.\n * - `'other'` — Unrecognised or empty.\n */\n kind: AgentKind\n /** Human-readable label, same string {@link parseBotName} returns. */\n label: string\n /** Strict: `true` only when the UA matches a branded AI crawler. */\n isAiBot: boolean\n /** Loose: `true` for known HTTP-library / automation UAs. */\n codingAgentHint: boolean\n /** Headless browser detection result. Only populated when `req` is passed. */\n headless?: HeadlessDetection\n}\n\n/**\n * UA-only classification. Use {@link classifyRequest} for full detection\n * including headless browser heuristics.\n */\nexport function classifyAgent(userAgent: string | null | undefined): AgentClassification {\n const label = parseBotName(userAgent)\n const aiBot = isAiBot(userAgent)\n const httpClient = isHttpClient(userAgent)\n\n let kind: AgentKind\n if (aiBot) kind = 'declared-crawler'\n else if (httpClient) kind = 'coding-agent-hint'\n else if (label === 'Browser') kind = 'browser'\n else kind = 'other'\n\n return { kind, label, isAiBot: aiBot, codingAgentHint: httpClient }\n}\n\n/**\n * Full request classification — combines UA parsing with header-based\n * headless detection. When a browser-like UA is missing standard headers,\n * the kind is promoted from `'browser'` to `'headless-likely'`.\n */\nexport function classifyRequest(req: Request): AgentClassification {\n const userAgent = req.headers.get('user-agent') || ''\n const base = classifyAgent(userAgent)\n const headless = detectHeadless(req)\n\n let kind = base.kind\n let label = base.label\n if (kind === 'browser' && headless.likely) {\n kind = 'headless-likely'\n // Relabel too. Leaving it as 'Browser' meant automation with a spoofed\n // browser UA — 79% of one production site's agent traffic — was\n // indistinguishable from a human in any `bot_name` breakdown, and was\n // silently excluded by the obvious `bot_name != 'Browser'` filter.\n label = 'Headless'\n }\n\n return { ...base, kind, label, headless }\n}\n","import { classifyRequest, isHttpClient } from './bots.js'\nimport type { BotVerificationLike } from './types.js'\n\n/**\n * What to do with an agent request.\n *\n * - `'allow'` — serve it, free. Humans, search crawlers, and the retrieval\n * agents you *want* reading your site.\n * - `'meter'` — serve it, but count it as billable. Bulk corpus collection.\n * - `'charge'` — don't serve it until it pays (HTTP 402).\n * - `'block'` — refuse. Failed identity verification, mostly.\n */\nexport type AgentAction = 'allow' | 'meter' | 'charge' | 'block'\n\n/**\n * Why an agent fetched the page. This is the distinction the whole module\n * exists for, and no other signal on the request carries it.\n *\n * - `'retrieval'` — a person asked a question and the assistant went to read\n * the page for them. This is *demand*: the agent is a distribution channel,\n * and charging for it is charging for your own marketing.\n * - `'training'` — bulk corpus collection for model training. You get nothing\n * back per fetch, which is where a price makes sense.\n * - `'search'` — index crawlers, traditional and AI-native. Blocking these\n * costs you organic traffic or citations in an assistant's answer.\n * - `'preview'` — link unfurlers. Someone pasted your URL into Slack, iMessage\n * or a tweet and the platform fetched it to render a card. No model involved,\n * but blocking it means your links look broken wherever they get shared.\n * - `'tooling'` — coding agents and HTTP clients. Usually developers using\n * your docs; treat like retrieval unless you see abuse.\n * - `'unknown'` — everything else, including real browsers.\n */\nexport type AgentIntent = 'retrieval' | 'training' | 'search' | 'preview' | 'tooling' | 'unknown'\n\n/**\n * User agents where a human is waiting on the answer. Deliberately explicit\n * rather than pattern-guessed: `-User` is not a reliable marker (OpenAI's\n * ChatGPT-User fetches server-side; Claude Code's Claude-User runs on a\n * laptop), and getting this wrong means charging your own demand channel.\n */\nconst RETRIEVAL = /ChatGPT-User|OAI-SearchBot|Claude-User|Claude-SearchBot|Perplexity-User|claude-code|DuckAssistBot|MistralAI-User|Gemini-Deep-Research|Manus-User|YouBot/i\n\n/** Bulk crawlers that collect corpora. No human is waiting on these. */\nconst TRAINING = /GPTBot|ClaudeBot|Claude-Web|CCBot|Bytespider|Amazonbot|Amzn-SearchBot|Meta-ExternalAgent|meta-externalfetcher|meta-webindexer|FacebookBot|Google-Extended|Applebot-Extended|AI2Bot|Diffbot|omgili|Webzio-Extended|Timpibot|PanguBot|cohere|DeepSeek|Grok|quillbot|MyCentralAIScraperBot|NovaAct|AzureAI-SearchBot|Google-CloudVertexBot/i\n\n/**\n * Index crawlers — blocking these costs you organic traffic.\n *\n * `PerplexityBot` sits here rather than in TRAINING despite the `Bot` suffix:\n * Perplexity documents it as the crawler behind their *search results* and\n * states it does not feed foundation-model training. Blocking it costs you\n * citations, which is the same shape of loss as blocking Googlebot. It was\n * previously in no list at all, so it classified as `unknown` and fell through\n * both the protective bypass and the training rate limit — a live gap found in\n * production traffic, not in review.\n */\nconst SEARCH = /bingbot|Googlebot|DuckDuckBot|YandexBot|Baiduspider|PetalBot|Sogou|PerplexityBot|Bravebot|Applebot(?!-Extended)/i\n\n/**\n * Link unfurlers. A human shared the URL and a platform fetched it to build a\n * preview card — one request, no crawl, and the payoff is a rendered link in a\n * conversation. They get their own intent rather than being folded into\n * `retrieval` because retrieval is the library's demand signal: counting\n * Slackbot as \"an assistant went to read this for someone\" would inflate the\n * one number the split exists to measure.\n *\n * These tokens are trivially spoofable — `facebookexternalhit` is among the\n * most-forged strings on the web. Treat this as a routing hint, never as\n * identity, and note that {@link recommendFirewallRules} proposes them as a\n * separate, higher-risk rule for exactly that reason.\n */\nconst PREVIEW = /facebookexternalhit|Twitterbot|LinkedInBot|Slackbot|Discordbot|TelegramBot|WhatsApp|redditbot|Pinterest|SkypeUriPreview|Iframely|Embedly|vkShare|Mastodon|Bluesky/i\n\nexport interface AgentDecision {\n action: AgentAction\n intent: AgentIntent\n /** Vendor label, same string `parseBotName` returns. */\n label: string\n /** Identity verdict, when a verifier was supplied. */\n verification?: string\n /** Short human-readable justification — log it, don't parse it. */\n reason: string\n}\n\nexport interface AgentPolicyOptions {\n /**\n * Identity verifier. Import `verifyRequest` from\n * `@apideck/agent-analytics/verify` and pass it here to have a `spoofed`\n * verdict produce `'block'`.\n *\n * Injected rather than imported so the published IP range tables only reach\n * bundles that use them. Only meaningful when your edge controls\n * `x-forwarded-for`: behind a proxy that forwards a client-supplied header,\n * an attacker picks their own verdict.\n */\n verify?: (req: Request) => BotVerificationLike\n /**\n * A verification already computed elsewhere. Use this when your verifier is\n * async — {@link verifyWebBotAuth} fetches a key directory, so the natural\n * verifier from `@apideck/agent-analytics/verify` returns a promise and\n * cannot be passed to `verify` on this synchronous function.\n *\n * {@link paymentGate} does this for you: it awaits the verifier and forwards\n * the result here.\n */\n verification?: BotVerificationLike\n /** What to do with bulk training crawlers. Defaults to `'meter'`. */\n onTraining?: AgentAction\n /** What to do with retrieval agents. Defaults to `'allow'` — see AgentIntent. */\n onRetrieval?: AgentAction\n /** What to do with search indexers. Defaults to `'allow'`. */\n onSearch?: AgentAction\n /**\n * What to do with link unfurlers. Defaults to `'allow'` — gating these does\n * not earn you anything, it just makes your links render as bare URLs.\n */\n onPreview?: AgentAction\n /** What to do with coding agents and HTTP clients. Defaults to `'allow'`. */\n onTooling?: AgentAction\n /** Vendor labels or UA substrings always allowed, whatever the intent. */\n allowList?: readonly string[]\n}\n\n/**\n * Classify why an agent is here, from its user agent alone.\n *\n * This must return exactly what {@link agentPolicy} reports for the same UA.\n * It previously did not: the `tooling` promotion for HTTP-library UAs lived\n * only inside `agentPolicy`, so `agentIntent('curl/8.4.0')` said `'unknown'`\n * while the policy said `'tooling'` — two exported functions disagreeing on\n * every HTTP client, with no way for a caller to know which was right. The\n * invariant is pinned by a test.\n */\nexport function agentIntent(userAgent: string | null | undefined): AgentIntent {\n const ua = userAgent ?? ''\n if (!ua) return 'unknown'\n // Retrieval is checked first: several vendors ship both a bulk crawler and a\n // user-facing fetcher whose tokens overlap (ClaudeBot vs Claude-User).\n if (RETRIEVAL.test(ua)) return 'retrieval'\n if (TRAINING.test(ua)) return 'training'\n if (SEARCH.test(ua)) return 'search'\n // After SEARCH so Applebot stays a search crawler rather than an iMessage\n // unfurler — Apple uses the same token for both.\n if (PREVIEW.test(ua)) return 'preview'\n // An HTTP-library UA that matched no vendor is a coding agent or a script.\n if (isHttpClient(ua)) return 'tooling'\n return 'unknown'\n}\n\n/**\n * Decide what to do with a request. Pure classification plus policy — no\n * payment rails, no network calls, nothing to configure beyond the four\n * intent knobs.\n *\n * @example\n * ```ts\n * const decision = agentPolicy(req, { verify: true, onTraining: 'charge' })\n * if (decision.action === 'block') return new Response(null, { status: 403 })\n * if (decision.action === 'charge') return paymentRequired(decision)\n * ```\n */\nexport function agentPolicy(req: Request, opts: AgentPolicyOptions = {}): AgentDecision {\n const ua = req.headers.get('user-agent') || ''\n const classification = classifyRequest(req)\n const label = classification.label\n\n // Single source of truth — the promotion that used to live here now lives in\n // agentIntent, so the two can no longer drift apart.\n const intent = agentIntent(ua)\n\n const allowed = opts.allowList?.some(\n (entry) => entry === label || ua.toLowerCase().includes(entry.toLowerCase())\n )\n if (allowed) {\n return { action: 'allow', intent, label, reason: 'on allowList' }\n }\n\n // A pre-resolved verification wins: it is the only way an async verifier can\n // reach this synchronous function.\n const resolved = opts.verification ?? (opts.verify ? opts.verify(req) : undefined)\n let verification: string | undefined\n if (resolved) {\n verification = resolved.verdict\n // Only 'spoofed' is actionable. 'unverifiable' means we couldn't check —\n // blocking on it would refuse every vendor without a published feed and\n // every coding agent running on someone's own machine.\n if (verification === 'spoofed') {\n return {\n action: 'block',\n intent,\n label,\n verification,\n reason: `${label} claimed but client IP is outside its published ranges`\n }\n }\n }\n\n const action: AgentAction =\n intent === 'training'\n ? (opts.onTraining ?? 'meter')\n : intent === 'retrieval'\n ? (opts.onRetrieval ?? 'allow')\n : intent === 'search'\n ? (opts.onSearch ?? 'allow')\n : intent === 'preview'\n ? (opts.onPreview ?? 'allow')\n : intent === 'tooling'\n ? (opts.onTooling ?? 'allow')\n : 'allow'\n\n const REASONS: Record<AgentIntent, string> = {\n retrieval: 'a person is waiting on this answer',\n training: 'bulk corpus collection',\n search: 'search index crawler',\n preview: 'link unfurler building a preview card',\n tooling: 'coding agent or HTTP client',\n unknown: 'not a recognised agent'\n }\n\n return {\n action,\n intent,\n label,\n // Spread rather than assign: `exactOptionalPropertyTypes` distinguishes an\n // absent key from one explicitly set to undefined.\n ...(verification ? { verification } : {}),\n reason: REASONS[intent]\n }\n}\n","/**\n * The paid-access gate: policy decides *whether* to charge, a gateway decides\n * *how*. **EXPERIMENTAL** — see `payments.ts`. The classification and policy\n * layers underneath are stable; the payment surface is not.\n *\n * The split matters. We own classification — telling a training crawl from a\n * retrieval fetch, which is the part nobody else does and the part that makes\n * charging sane. Settlement is somebody else's job: Stripe's MPP SDK, an x402\n * facilitator, whatever comes next. A library that held money would inherit PCI\n * scope and stop being something you drop into middleware.\n *\n * So gateways are injected, exactly like analytics adapters, and this module\n * takes no dependency on Stripe or any chain.\n */\n\nimport { agentPolicy, type AgentDecision, type AgentPolicyOptions } from './policy.js'\nimport type { BotVerificationLike } from './types.js'\nimport { paymentRequired, type PaymentChallengeOptions } from './payments.js'\n\n/**\n * Outcome of handing a request to a payment gateway.\n *\n * - `challenge` — respond with this. The client has not paid.\n * - `paid` — settled; serve the resource. `receipt` decorates the response with\n * whatever proof the protocol expects.\n */\nexport type GatewayResult =\n | { status: 'challenge'; response: Response }\n | { status: 'paid'; receipt?: (res: Response) => Response }\n\nexport interface PaymentGateway {\n handle(req: Request): Promise<GatewayResult>\n}\n\n/**\n * Wrap Stripe's MPP SDK.\n *\n * `Mppx.compose(...)` returns a handler that either yields a 402 with a\n * `.challenge` response, or a settled result with `.withReceipt(res)`. This\n * adapts that shape without importing it — pass the composed handler in.\n *\n * @example\n * ```ts\n * const mppx = Mppx.create({ methods: [...], secretKey })\n * const handler = Mppx.compose(\n * mppx.tempo.charge({ amount: '0.01', recipient }),\n * mppx.stripe.charge({ amount: '0.50', currency: 'usd' })\n * )\n * const gateway = mppxGateway(handler)\n * ```\n */\nexport function mppxGateway(\n handler: (req: Request) => Promise<MppxResponse> | MppxResponse\n): PaymentGateway {\n return {\n async handle(req: Request): Promise<GatewayResult> {\n const out = await handler(req)\n if (out.status === 402) {\n return { status: 'challenge', response: out.challenge }\n }\n return {\n status: 'paid',\n ...(out.withReceipt ? { receipt: (res: Response) => out.withReceipt!(res) } : {})\n }\n }\n }\n}\n\n/** The subset of Stripe's MPP response we rely on. Structural, not imported. */\nexport interface MppxResponse {\n status: number\n challenge: Response\n withReceipt?: (res: Response) => Response\n}\n\nexport interface X402GatewayOptions extends PaymentChallengeOptions {\n /**\n * Verify and settle a `PAYMENT-SIGNATURE` payload with your facilitator.\n * Resolve truthy to serve the resource, falsy to re-challenge.\n */\n settle: (payload: string, req: Request) => Promise<boolean> | boolean\n /** Attach the facilitator's settlement result to the served response. */\n receipt?: (res: Response) => Response\n}\n\n/**\n * Gateway using this library's own challenge builder plus a facilitator you\n * supply. For x402, or for MPP if you are not using Stripe's SDK.\n */\nexport function x402Gateway(opts: X402GatewayOptions): PaymentGateway {\n const { settle, receipt, ...challenge } = opts\n return {\n async handle(req: Request): Promise<GatewayResult> {\n const sig = req.headers.get('PAYMENT-SIGNATURE')\n if (sig && (await settle(sig, req))) {\n return { status: 'paid', ...(receipt ? { receipt } : {}) }\n }\n return { status: 'challenge', response: paymentRequired(challenge) }\n }\n }\n}\n\n/** One unit of billable agent traffic. */\nexport interface MeterRecord {\n decision: AgentDecision\n /** Units consumed. One request is one unit unless you price by bytes or tokens. */\n units: number\n path: string\n method: string\n}\n\n/**\n * Where billable usage goes.\n *\n * Metering is the model to ship first: it needs no crawler cooperation, works\n * today, and produces the number you would negotiate a licence with. Charging\n * per request is what the protocols define but not what a training sweep can\n * actually do — no crawler in the wild retries a 402.\n */\nexport interface Meter {\n record(entry: MeterRecord): Promise<void> | void\n}\n\nexport interface PaymentGateOptions extends Omit<AgentPolicyOptions, 'verify'> {\n gateway: PaymentGateway\n /**\n * Sink for billable traffic. Called for every `'meter'` decision — serve the\n * request, count it, bill out of band.\n *\n * Errors are swallowed: a metering failure must not turn into a failed\n * response, for the same reason analytics failures do not.\n */\n meter?: Meter\n /**\n * Identity verifier, sync or async. Unlike {@link agentPolicy}'s option this\n * accepts a promise, because `paymentGate` is already async and can await it.\n * That matters: `combinedVerifier()` and `webBotAuthVerifier()` are async by\n * necessity — Web Bot Auth fetches the signer's key directory — so without\n * this they could not be used with policy or payments at all.\n */\n verify?: (req: Request) => BotVerificationLike | Promise<BotVerificationLike>\n /**\n * Called for every decision, paid or not — wire it to your metering so\n * `'meter'` traffic is actually counted rather than merely allowed.\n */\n onDecision?: (decision: AgentDecision) => void\n}\n\n/**\n * Full gate: classify, decide, and either let the request through or return the\n * response it should get instead.\n *\n * Returns `null` when the request should be served normally. That covers\n * `'allow'`, `'meter'` (accounting, not a gate) and any request that has already\n * paid — in which case `receipt` is handed back so you can decorate the response\n * you were going to send anyway.\n *\n * @example\n * ```ts\n * const gate = await paymentGate(req, {\n * onTraining: 'charge',\n * verify: combinedVerifier(),\n * gateway: mppxGateway(handler),\n * onDecision: (d) => void trackVisit(req, { analytics, properties: { action: d.action } })\n * })\n * if (gate.response) return gate.response\n * return gate.decorate(await serve(req))\n * ```\n */\nexport async function paymentGate(\n req: Request,\n opts: PaymentGateOptions\n): Promise<{\n decision: AgentDecision\n /** Respond with this instead of serving, when set. */\n response: Response | null\n /** Wrap the response you were going to send. Identity when nothing to add. */\n decorate: (res: Response) => Response\n}> {\n const { gateway, onDecision, verify, meter, ...policyOpts } = opts\n // Await here so an async verifier works. Passing the function straight into\n // agentPolicy would hand it a promise to read `.verdict` off — undefined at\n // runtime, and a type error at compile time.\n const verification = verify ? await verify(req) : undefined\n const decision = agentPolicy(req, {\n ...policyOpts,\n ...(verification ? { verification } : {})\n })\n onDecision?.(decision)\n\n const identity = (res: Response) => res\n\n if (decision.action === 'block') {\n return {\n decision,\n response: new Response('Forbidden: agent identity could not be verified.\\n', {\n status: 403\n }),\n decorate: identity\n }\n }\n\n if (decision.action === 'meter') {\n // Count it and serve it. This is the path most sites should be on.\n try {\n let path = req.url\n let method = req.method\n try {\n path = new URL(req.url).pathname\n } catch {\n /* relative URL from some runtimes — keep the raw string */\n }\n await meter?.record({ decision, units: 1, path, method })\n } catch {\n // Metering must never turn into a failed response.\n }\n return { decision, response: null, decorate: identity }\n }\n\n if (decision.action !== 'charge') {\n return { decision, response: null, decorate: identity }\n }\n\n const out = await gateway.handle(req)\n if (out.status === 'challenge') {\n return { decision, response: out.response, decorate: identity }\n }\n return { decision, response: null, decorate: out.receipt ?? identity }\n}\n","/**\n * Quota and entitlements — the model that actually works for training crawls.\n * **EXPERIMENTAL**, like the rest of the payment surface.\n *\n * Per-request 402 is what x402 and MPP define, and it is the wrong shape for a\n * training sweep. On one production site training traffic is ~199,000 requests a\n * month. Charging each one means three times the traffic (402, pay, retry),\n * 199,000 settlements whose per-transaction cost exceeds any sane per-page\n * price, and — decisively — no crawler in the wild implements the retry, so a\n * per-request 402 is just blocking with extra steps.\n *\n * Two workable shapes instead, both supported here:\n *\n * METER Serve the request, count it, bill out of band. Needs no crawler\n * cooperation and works today. This is the one to ship.\n *\n * ENTITLEMENT Challenge once with a bulk offer, take payment, issue a\n * credential. Every later request presents it and is served\n * directly, decrementing quota. One settlement per licence rather\n * than per page.\n *\n * MPP's reusable `Authorization: Payment` credential fits entitlements better\n * than x402's per-resource signature, which proves payment for one URL.\n */\n\nimport { paymentRequired, paymentPayload, type PaymentChallengeOptions } from './payments.js'\nimport type { GatewayResult, PaymentGateway } from './gateway.js'\n\n/** What a buyer holds after paying. */\nexport interface Entitlement {\n /** Opaque licence id, for your own accounting. */\n id: string\n /**\n * Units left. Omit for an unmetered licence — `consume` is still called, so\n * you can count without capping.\n */\n remaining?: number\n /** Expiry as epoch seconds. Omit for no expiry. */\n expiresAt?: number\n}\n\n/**\n * Where entitlements live. A KV namespace, Redis, your database — anything\n * reachable from the edge. The library deliberately ships no storage: quota\n * state is yours, and so is the money it represents.\n */\nexport interface EntitlementStore {\n /** Resolve the credential a client presented. Return null to challenge. */\n lookup(credential: string, req: Request): Promise<Entitlement | null> | Entitlement | null\n /**\n * Record consumption after a request is admitted. Called for every served\n * request, including unmetered licences, so this doubles as your meter.\n */\n consume?(entitlement: Entitlement, req: Request): Promise<void> | void\n}\n\n/**\n * What is for sale. Folded into the challenge so an agent sees a bulk product\n * rather than a price for the single page it happened to ask for.\n */\nexport interface BulkOffer {\n /** e.g. 1_000_000 */\n units: number\n /** e.g. `'pages'` */\n unit: string\n /** Licence lifetime in seconds. */\n validForSeconds: number\n /** Total price, in whatever units your challenge already uses. */\n price: string\n /** Summary surfaced to the agent. */\n description?: string\n}\n\nexport interface EntitlementGatewayOptions extends PaymentChallengeOptions {\n store: EntitlementStore\n /** The bulk product the 402 advertises. */\n offer: BulkOffer\n /**\n * Emit `x-quota-remaining` on served responses so a paying crawler can see\n * its balance and slow down before running out.\n *\n * Off by default: this header is **not** part of x402 or MPP. It is a\n * convenience, and a crawler that does not know it will ignore it.\n */\n exposeRemaining?: boolean\n}\n\nfunction offerDescription(offer: BulkOffer): string {\n const days = Math.round(offer.validForSeconds / 86_400)\n const window = days >= 1 ? `${days} day${days === 1 ? '' : 's'}` : `${offer.validForSeconds}s`\n return (\n offer.description ??\n `${offer.units.toLocaleString('en-US')} ${offer.unit} for ${window}, ${offer.price}`\n )\n}\n\n/**\n * Gateway that honours a bulk licence instead of charging per request.\n *\n * A request carrying a valid credential is served and its quota decremented —\n * no challenge, no round-trip. A request without one gets a single 402\n * advertising the bulk offer.\n *\n * @example\n * ```ts\n * const gateway = entitlementGateway({\n * store: myKvStore,\n * offer: { units: 1_000_000, unit: 'pages', validForSeconds: 2_592_000, price: '$400' },\n * challenges: [{ protocol: 'mpp', id, realm: 'example.com', method: 'tempo' }]\n * })\n * ```\n */\nexport function entitlementGateway(opts: EntitlementGatewayOptions): PaymentGateway {\n const { store, offer, exposeRemaining, ...challenge } = opts\n\n return {\n async handle(req: Request): Promise<GatewayResult> {\n const submitted = paymentPayload(req)\n\n if (submitted) {\n const ent = await store.lookup(submitted.value, req)\n const now = Math.floor(Date.now() / 1000)\n\n const usable =\n ent !== null &&\n (ent.expiresAt === undefined || ent.expiresAt > now) &&\n // `undefined` remaining means unmetered, which is usable. Zero is not.\n (ent.remaining === undefined || ent.remaining > 0)\n\n if (usable && ent) {\n await store.consume?.(ent, req)\n const left = ent.remaining === undefined ? undefined : ent.remaining - 1\n return {\n status: 'paid',\n ...(exposeRemaining && left !== undefined\n ? {\n receipt: (res: Response) => {\n const h = new Headers(res.headers)\n h.set('x-quota-remaining', String(Math.max(0, left)))\n return new Response(res.body, {\n status: res.status,\n statusText: res.statusText,\n headers: h\n })\n }\n }\n : {})\n }\n }\n }\n\n // No credential, expired, or exhausted — all get the same offer. Saying\n // which of the three it was would leak quota state to anyone probing.\n return {\n status: 'challenge',\n response: paymentRequired({\n ...challenge,\n body: `Payment required for training access.\\nOffer: ${offerDescription(offer)}\\n`,\n headers: {\n ...(challenge.headers ?? {}),\n 'x-bulk-offer': offerDescription(offer)\n }\n })\n }\n }\n }\n}\n\n/**\n * In-memory store. For tests and local development only — an edge runtime\n * gives each instance its own memory, so quota would neither be shared nor\n * survive a deploy. Use KV, Redis, or your database in production.\n */\nexport function memoryEntitlementStore(\n seed: Record<string, Entitlement> = {}\n): EntitlementStore & { entries(): Record<string, Entitlement> } {\n const map = new Map<string, Entitlement>(Object.entries(seed))\n return {\n lookup: (credential) => map.get(credential) ?? null,\n consume: (ent) => {\n if (ent.remaining !== undefined) {\n map.set(ent.id, { ...ent, remaining: ent.remaining - 1 })\n }\n },\n entries: () => Object.fromEntries(map)\n }\n}\n"]}
package/dist/pay.d.cts ADDED
@@ -0,0 +1,112 @@
1
+ import { d as PaymentChallengeOptions, f as PaymentGateway } from './gateway-f52pV_qP.cjs';
2
+ export { G as GatewayResult, M as Meter, a as MeterRecord, b as MppChallenge, c as MppxResponse, P as PaymentChallenge, e as PaymentGateOptions, g as PaymentProtocol, h as PaymentRequirements, S as SubmittedPayment, X as X402Challenge, i as X402GatewayOptions, j as hasPaymentPayload, m as mppxGateway, p as paymentGate, k as paymentPayload, l as paymentRequired, r as respondToDecision, w as withSettlement, x as x402Gateway } from './gateway-f52pV_qP.cjs';
3
+ import './policy-CEJ-j-Lz.cjs';
4
+ import './types-Dw43eu7D.cjs';
5
+
6
+ /**
7
+ * Quota and entitlements — the model that actually works for training crawls.
8
+ * **EXPERIMENTAL**, like the rest of the payment surface.
9
+ *
10
+ * Per-request 402 is what x402 and MPP define, and it is the wrong shape for a
11
+ * training sweep. On one production site training traffic is ~199,000 requests a
12
+ * month. Charging each one means three times the traffic (402, pay, retry),
13
+ * 199,000 settlements whose per-transaction cost exceeds any sane per-page
14
+ * price, and — decisively — no crawler in the wild implements the retry, so a
15
+ * per-request 402 is just blocking with extra steps.
16
+ *
17
+ * Two workable shapes instead, both supported here:
18
+ *
19
+ * METER Serve the request, count it, bill out of band. Needs no crawler
20
+ * cooperation and works today. This is the one to ship.
21
+ *
22
+ * ENTITLEMENT Challenge once with a bulk offer, take payment, issue a
23
+ * credential. Every later request presents it and is served
24
+ * directly, decrementing quota. One settlement per licence rather
25
+ * than per page.
26
+ *
27
+ * MPP's reusable `Authorization: Payment` credential fits entitlements better
28
+ * than x402's per-resource signature, which proves payment for one URL.
29
+ */
30
+
31
+ /** What a buyer holds after paying. */
32
+ interface Entitlement {
33
+ /** Opaque licence id, for your own accounting. */
34
+ id: string;
35
+ /**
36
+ * Units left. Omit for an unmetered licence — `consume` is still called, so
37
+ * you can count without capping.
38
+ */
39
+ remaining?: number;
40
+ /** Expiry as epoch seconds. Omit for no expiry. */
41
+ expiresAt?: number;
42
+ }
43
+ /**
44
+ * Where entitlements live. A KV namespace, Redis, your database — anything
45
+ * reachable from the edge. The library deliberately ships no storage: quota
46
+ * state is yours, and so is the money it represents.
47
+ */
48
+ interface EntitlementStore {
49
+ /** Resolve the credential a client presented. Return null to challenge. */
50
+ lookup(credential: string, req: Request): Promise<Entitlement | null> | Entitlement | null;
51
+ /**
52
+ * Record consumption after a request is admitted. Called for every served
53
+ * request, including unmetered licences, so this doubles as your meter.
54
+ */
55
+ consume?(entitlement: Entitlement, req: Request): Promise<void> | void;
56
+ }
57
+ /**
58
+ * What is for sale. Folded into the challenge so an agent sees a bulk product
59
+ * rather than a price for the single page it happened to ask for.
60
+ */
61
+ interface BulkOffer {
62
+ /** e.g. 1_000_000 */
63
+ units: number;
64
+ /** e.g. `'pages'` */
65
+ unit: string;
66
+ /** Licence lifetime in seconds. */
67
+ validForSeconds: number;
68
+ /** Total price, in whatever units your challenge already uses. */
69
+ price: string;
70
+ /** Summary surfaced to the agent. */
71
+ description?: string;
72
+ }
73
+ interface EntitlementGatewayOptions extends PaymentChallengeOptions {
74
+ store: EntitlementStore;
75
+ /** The bulk product the 402 advertises. */
76
+ offer: BulkOffer;
77
+ /**
78
+ * Emit `x-quota-remaining` on served responses so a paying crawler can see
79
+ * its balance and slow down before running out.
80
+ *
81
+ * Off by default: this header is **not** part of x402 or MPP. It is a
82
+ * convenience, and a crawler that does not know it will ignore it.
83
+ */
84
+ exposeRemaining?: boolean;
85
+ }
86
+ /**
87
+ * Gateway that honours a bulk licence instead of charging per request.
88
+ *
89
+ * A request carrying a valid credential is served and its quota decremented —
90
+ * no challenge, no round-trip. A request without one gets a single 402
91
+ * advertising the bulk offer.
92
+ *
93
+ * @example
94
+ * ```ts
95
+ * const gateway = entitlementGateway({
96
+ * store: myKvStore,
97
+ * offer: { units: 1_000_000, unit: 'pages', validForSeconds: 2_592_000, price: '$400' },
98
+ * challenges: [{ protocol: 'mpp', id, realm: 'example.com', method: 'tempo' }]
99
+ * })
100
+ * ```
101
+ */
102
+ declare function entitlementGateway(opts: EntitlementGatewayOptions): PaymentGateway;
103
+ /**
104
+ * In-memory store. For tests and local development only — an edge runtime
105
+ * gives each instance its own memory, so quota would neither be shared nor
106
+ * survive a deploy. Use KV, Redis, or your database in production.
107
+ */
108
+ declare function memoryEntitlementStore(seed?: Record<string, Entitlement>): EntitlementStore & {
109
+ entries(): Record<string, Entitlement>;
110
+ };
111
+
112
+ export { type BulkOffer, type Entitlement, type EntitlementGatewayOptions, type EntitlementStore, PaymentChallengeOptions, PaymentGateway, entitlementGateway, memoryEntitlementStore };
package/dist/pay.d.ts ADDED
@@ -0,0 +1,112 @@
1
+ import { d as PaymentChallengeOptions, f as PaymentGateway } from './gateway-BuF0-FG_.js';
2
+ export { G as GatewayResult, M as Meter, a as MeterRecord, b as MppChallenge, c as MppxResponse, P as PaymentChallenge, e as PaymentGateOptions, g as PaymentProtocol, h as PaymentRequirements, S as SubmittedPayment, X as X402Challenge, i as X402GatewayOptions, j as hasPaymentPayload, m as mppxGateway, p as paymentGate, k as paymentPayload, l as paymentRequired, r as respondToDecision, w as withSettlement, x as x402Gateway } from './gateway-BuF0-FG_.js';
3
+ import './policy-ByqnGK1b.js';
4
+ import './types-Dw43eu7D.js';
5
+
6
+ /**
7
+ * Quota and entitlements — the model that actually works for training crawls.
8
+ * **EXPERIMENTAL**, like the rest of the payment surface.
9
+ *
10
+ * Per-request 402 is what x402 and MPP define, and it is the wrong shape for a
11
+ * training sweep. On one production site training traffic is ~199,000 requests a
12
+ * month. Charging each one means three times the traffic (402, pay, retry),
13
+ * 199,000 settlements whose per-transaction cost exceeds any sane per-page
14
+ * price, and — decisively — no crawler in the wild implements the retry, so a
15
+ * per-request 402 is just blocking with extra steps.
16
+ *
17
+ * Two workable shapes instead, both supported here:
18
+ *
19
+ * METER Serve the request, count it, bill out of band. Needs no crawler
20
+ * cooperation and works today. This is the one to ship.
21
+ *
22
+ * ENTITLEMENT Challenge once with a bulk offer, take payment, issue a
23
+ * credential. Every later request presents it and is served
24
+ * directly, decrementing quota. One settlement per licence rather
25
+ * than per page.
26
+ *
27
+ * MPP's reusable `Authorization: Payment` credential fits entitlements better
28
+ * than x402's per-resource signature, which proves payment for one URL.
29
+ */
30
+
31
+ /** What a buyer holds after paying. */
32
+ interface Entitlement {
33
+ /** Opaque licence id, for your own accounting. */
34
+ id: string;
35
+ /**
36
+ * Units left. Omit for an unmetered licence — `consume` is still called, so
37
+ * you can count without capping.
38
+ */
39
+ remaining?: number;
40
+ /** Expiry as epoch seconds. Omit for no expiry. */
41
+ expiresAt?: number;
42
+ }
43
+ /**
44
+ * Where entitlements live. A KV namespace, Redis, your database — anything
45
+ * reachable from the edge. The library deliberately ships no storage: quota
46
+ * state is yours, and so is the money it represents.
47
+ */
48
+ interface EntitlementStore {
49
+ /** Resolve the credential a client presented. Return null to challenge. */
50
+ lookup(credential: string, req: Request): Promise<Entitlement | null> | Entitlement | null;
51
+ /**
52
+ * Record consumption after a request is admitted. Called for every served
53
+ * request, including unmetered licences, so this doubles as your meter.
54
+ */
55
+ consume?(entitlement: Entitlement, req: Request): Promise<void> | void;
56
+ }
57
+ /**
58
+ * What is for sale. Folded into the challenge so an agent sees a bulk product
59
+ * rather than a price for the single page it happened to ask for.
60
+ */
61
+ interface BulkOffer {
62
+ /** e.g. 1_000_000 */
63
+ units: number;
64
+ /** e.g. `'pages'` */
65
+ unit: string;
66
+ /** Licence lifetime in seconds. */
67
+ validForSeconds: number;
68
+ /** Total price, in whatever units your challenge already uses. */
69
+ price: string;
70
+ /** Summary surfaced to the agent. */
71
+ description?: string;
72
+ }
73
+ interface EntitlementGatewayOptions extends PaymentChallengeOptions {
74
+ store: EntitlementStore;
75
+ /** The bulk product the 402 advertises. */
76
+ offer: BulkOffer;
77
+ /**
78
+ * Emit `x-quota-remaining` on served responses so a paying crawler can see
79
+ * its balance and slow down before running out.
80
+ *
81
+ * Off by default: this header is **not** part of x402 or MPP. It is a
82
+ * convenience, and a crawler that does not know it will ignore it.
83
+ */
84
+ exposeRemaining?: boolean;
85
+ }
86
+ /**
87
+ * Gateway that honours a bulk licence instead of charging per request.
88
+ *
89
+ * A request carrying a valid credential is served and its quota decremented —
90
+ * no challenge, no round-trip. A request without one gets a single 402
91
+ * advertising the bulk offer.
92
+ *
93
+ * @example
94
+ * ```ts
95
+ * const gateway = entitlementGateway({
96
+ * store: myKvStore,
97
+ * offer: { units: 1_000_000, unit: 'pages', validForSeconds: 2_592_000, price: '$400' },
98
+ * challenges: [{ protocol: 'mpp', id, realm: 'example.com', method: 'tempo' }]
99
+ * })
100
+ * ```
101
+ */
102
+ declare function entitlementGateway(opts: EntitlementGatewayOptions): PaymentGateway;
103
+ /**
104
+ * In-memory store. For tests and local development only — an edge runtime
105
+ * gives each instance its own memory, so quota would neither be shared nor
106
+ * survive a deploy. Use KV, Redis, or your database in production.
107
+ */
108
+ declare function memoryEntitlementStore(seed?: Record<string, Entitlement>): EntitlementStore & {
109
+ entries(): Record<string, Entitlement>;
110
+ };
111
+
112
+ export { type BulkOffer, type Entitlement, type EntitlementGatewayOptions, type EntitlementStore, PaymentChallengeOptions, PaymentGateway, entitlementGateway, memoryEntitlementStore };
package/dist/pay.js ADDED
@@ -0,0 +1,8 @@
1
+ var A="PAYMENT-REQUIRED",R="PAYMENT-SIGNATURE",P="PAYMENT-RESPONSE",k="WWW-Authenticate",B="Authorization";function p(t){return `"${t.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`}function y(t){let e=JSON.stringify(t),n=new TextEncoder().encode(e),r="";for(let i of n)r+=String.fromCharCode(i);return btoa(r)}function f(t){if(!t.challenges.length)throw new Error("paymentRequired needs at least one challenge");let e=new Headers({"content-type":"text/plain; charset=utf-8","content-signal":t.contentSignal??"search=yes, ai-input=yes, ai-train=paid"});for(let n of t.challenges)if(n.protocol==="x402"){if(!n.accepts.length)throw new Error("an x402 challenge needs at least one entry in `accepts`");e.set(A,y({x402Version:n.x402Version??1,accepts:n.accepts}));}else {let r=[`id=${p(n.id)}`,`realm=${p(n.realm)}`,`method=${p(n.method)}`,...n.intent?[`intent=${p(n.intent)}`]:[],...n.request?[`request=${p(n.request)}`]:[]];e.append(k,`Payment ${r.join(", ")}`);}for(let[n,r]of Object.entries(t.headers??{}))e.set(n,r);return new Response(t.body??`Payment required for training access.
2
+ `,{status:402,headers:e})}function m(t){let e=t.headers.get(R);if(e)return {protocol:"x402",value:e};let n=t.headers.get(B);if(n){let r=n.match(/^Payment\s+(.*)$/i);if(r?.[1])return {protocol:"mpp",value:r[1]}}return null}function L(t){return m(t)!==null}function N(t,e,n={}){let r=new Headers(t.headers);return r.set(n.header??P,y(e)),new Response(t.body,{status:t.status,statusText:t.statusText,headers:r})}function z(t,e){return t.action==="block"?new Response(`Forbidden: agent identity could not be verified.
3
+ `,{status:403}):t.action==="charge"?f({body:`Payment required: ${t.label} \u2014 ${t.reason}.
4
+ `,...e}):null}var C=/ClaudeBot|Claude-User|Claude-SearchBot|Claude-Web|Anthropic|GPTBot|ChatGPT-User|OAI-SearchBot|PerplexityBot|Perplexity-User|Google-Extended|Google-CloudVertexBot|Google-Agent|GoogleAgent-Mariner|Gemini-Deep-Research|Applebot|cohere|Bytespider|CCBot|Amazonbot|Amzn-SearchBot|NovaAct|AzureAI-SearchBot|Meta-ExternalAgent|meta-externalfetcher|meta-webindexer|FacebookBot|DuckAssistBot|MistralAI-User|YouBot|AI2Bot|Diffbot|DeepSeek|PanguBot|Webzio-Extended|omgili|Timpibot|Grok|Manus-User|quillbot|MyCentralAIScraperBot|Cursor|Windsurf/i,E=/axios\/|curl\/|(?:^|[\s(])got(?:\/|[\s(])|\bcolly\b|Electron\/|node-fetch\/|python-requests\/|Go-http-client\/|okhttp\/|aiohttp\/|Deno\//i;function S(t){return t?C.test(t):false}function h(t){return t?E.test(t):false}function T(t){if(!t||typeof t!="string")return "Other";let e=t.toLowerCase();return e.includes("chatgpt-user")||e.includes("gptbot")||e.includes("oai-searchbot")||e.includes("openai")?"ChatGPT":e.includes("claudebot")||e.includes("claude-user")||e.includes("claude-searchbot")||e.includes("claude-web")||e.includes("anthropic")?"Claude":e.includes("perplexitybot")||e.includes("perplexity-user")?"Perplexity":e.includes("ccbot")?"Common Crawl":e.includes("google-extended")||e.includes("googlebot")||e.includes("google-cloudvertexbot")||e.includes("google-agent")||e.includes("googleagent-mariner")||e.includes("gemini-deep-research")?"Google":e.includes("applebot")?"Apple":e.includes("bingbot")?"Bing":e.includes("bytespider")?"Bytespider":e.includes("amazonbot")||e.includes("amzn-searchbot")||e.includes("novaact")?"Amazon":e.includes("meta-externalagent")||e.includes("meta-externalfetcher")||e.includes("meta-webindexer")||e.includes("facebookbot")?"Meta":e.includes("mistralai-user")?"Mistral":e.includes("duckassistbot")?"DuckDuckGo":e.includes("youbot")?"You.com":e.includes("diffbot")?"Diffbot":e.includes("ai2bot")?"AI2":e.includes("cohere")?"Cohere":e.includes("cursor")?"Cursor":e.includes("windsurf")?"Windsurf":e.includes("deepseek")?"DeepSeek":e.includes("pangubot")?"Huawei":e.includes("webzio")||e.includes("omgili")?"Webz.io":e.includes("timpibot")?"Timpi":e.includes("grok")||e.includes("xai-")?"xAI":e.includes("manus-user")?"Manus":e.includes("quillbot")?"QuillBot":e.includes("azureai-searchbot")?"Microsoft":e.includes("mycentralaiscraperbot")?"MyCentralAI":e.includes("petalbot")?"PetalBot":e.includes("ahrefsbot")?"Ahrefs":e.includes("semrushbot")?"Semrush":e.includes("mj12bot")?"Majestic":e.includes("dotbot")||e.includes("rogerbot")?"Moz":e.includes("screaming frog")?"Screaming Frog":e.includes("sitebulb")?"Sitebulb":e.includes("linkfluence")?"Linkfluence":e.includes("dataforseo")?"DataForSEO":e.includes("serpstatbot")?"Serpstat":e.includes("uptimerobot")?"UptimeRobot":e.includes("pingdom")?"Pingdom":e.includes("statuscake")?"StatusCake":e.includes("newrelicpinger")?"New Relic":e.includes("datadogagent")||e.includes("datadog")?"Datadog":e.includes("slackbot")?"Slack":e.includes("twitterbot")?"Twitter":e.includes("linkedinbot")?"LinkedIn":e.includes("discordbot")?"Discord":e.includes("telegrambot")?"Telegram":e.includes("whatsapp")?"WhatsApp":e.includes("linkupbot")?"Linkup":e.includes("sogou")?"Sogou":e.includes("yandexbot")?"Yandex":e.includes("baiduspider")?"Baidu":e.includes("facebookexternalhit")?"Facebook":e.includes("com.apple.webkit")?"Apple URL Preview":e.includes("ohdear")?"Oh Dear":e.includes("scrapy")?"Scrapy":e.includes("headlesschrome")?"Headless Chrome":e.includes("phantomjs")?"PhantomJS":e.includes("wget")?"wget":e.includes("httpie")?"HTTPie":e.includes("guzzlehttp")?"Guzzle":e.includes("electron/")?"Electron":/curl\//.test(e)?"curl":/axios\//.test(e)?"axios":/(?:^|[\s(])got(?:\/|[\s(])/.test(e)?"got":/\bcolly\b/.test(e)?"colly":/node-fetch\//.test(e)?"node-fetch":/python-requests\//.test(e)?"python-requests":/go-http-client\//.test(e)?"Go http client":/okhttp\//.test(e)?"OkHttp":/aiohttp\//.test(e)?"aiohttp":/deno\//.test(e)?"Deno":e.includes("mozilla")||e.includes("chrome")||e.includes("safari")||e.includes("firefox")?"Browser":"Other"}function v(t){let e=[],n=(t.headers.get("user-agent")||"").toLowerCase();if(!(n.includes("mozilla")||n.includes("chrome")||n.includes("safari")||n.includes("firefox")))return {score:0,signals:[],likely:false};t.headers.get("accept-language")||e.push("missing-accept-language"),t.headers.get("sec-fetch-mode")||e.push("missing-sec-fetch-mode");let i=t.headers.get("sec-ch-ua");i?i.toLowerCase().includes("headlesschrome")&&e.push("headless-chrome-hint"):e.push("missing-sec-ch-ua");let o=t.headers.get("accept")||"";(!o||o==="*/*")&&e.push("missing-or-bare-accept"),(t.headers.get("connection")||"").toLowerCase()==="close"&&e.push("connection-close");let c=e.length;return {score:c,signals:e,likely:c>=2}}function G(t){let e=T(t),n=S(t),r=h(t),i;return n?i="declared-crawler":r?i="coding-agent-hint":e==="Browser"?i="browser":i="other",{kind:i,label:e,isAiBot:n,codingAgentHint:r}}function b(t){let e=t.headers.get("user-agent")||"",n=G(e),r=v(t),i=n.kind,o=n.label;return i==="browser"&&r.likely&&(i="headless-likely",o="Headless"),{...n,kind:i,label:o,headless:r}}var O=/ChatGPT-User|OAI-SearchBot|Claude-User|Claude-SearchBot|Perplexity-User|claude-code|DuckAssistBot|MistralAI-User|Gemini-Deep-Research|Manus-User|YouBot/i,D=/GPTBot|ClaudeBot|Claude-Web|CCBot|Bytespider|Amazonbot|Amzn-SearchBot|Meta-ExternalAgent|meta-externalfetcher|meta-webindexer|FacebookBot|Google-Extended|Applebot-Extended|AI2Bot|Diffbot|omgili|Webzio-Extended|Timpibot|PanguBot|cohere|DeepSeek|Grok|quillbot|MyCentralAIScraperBot|NovaAct|AzureAI-SearchBot|Google-CloudVertexBot/i,M=/bingbot|Googlebot|DuckDuckBot|YandexBot|Baiduspider|PetalBot|Sogou|PerplexityBot|Bravebot|Applebot(?!-Extended)/i,I=/facebookexternalhit|Twitterbot|LinkedInBot|Slackbot|Discordbot|TelegramBot|WhatsApp|redditbot|Pinterest|SkypeUriPreview|Iframely|Embedly|vkShare|Mastodon|Bluesky/i;function q(t){let e=t??"";return e?O.test(e)?"retrieval":D.test(e)?"training":M.test(e)?"search":I.test(e)?"preview":h(e)?"tooling":"unknown":"unknown"}function x(t,e={}){let n=t.headers.get("user-agent")||"",i=b(t).label,o=q(n);if(e.allowList?.some(l=>l===i||n.toLowerCase().includes(l.toLowerCase())))return {action:"allow",intent:o,label:i,reason:"on allowList"};let a=e.verification??(e.verify?e.verify(t):void 0),s;return a&&(s=a.verdict,s==="spoofed")?{action:"block",intent:o,label:i,verification:s,reason:`${i} claimed but client IP is outside its published ranges`}:{action:o==="training"?e.onTraining??"meter":o==="retrieval"?e.onRetrieval??"allow":o==="search"?e.onSearch??"allow":o==="preview"?e.onPreview??"allow":o==="tooling"?e.onTooling??"allow":"allow",intent:o,label:i,...s?{verification:s}:{},reason:{retrieval:"a person is waiting on this answer",training:"bulk corpus collection",search:"search index crawler",preview:"link unfurler building a preview card",tooling:"coding agent or HTTP client",unknown:"not a recognised agent"}[o]}}function F(t){return {async handle(e){let n=await t(e);return n.status===402?{status:"challenge",response:n.challenge}:{status:"paid",...n.withReceipt?{receipt:r=>n.withReceipt(r)}:{}}}}}function _(t){let{settle:e,receipt:n,...r}=t;return {async handle(i){let o=i.headers.get("PAYMENT-SIGNATURE");return o&&await e(o,i)?{status:"paid",...n?{receipt:n}:{}}:{status:"challenge",response:f(r)}}}}async function Y(t,e){let{gateway:n,onDecision:r,verify:i,meter:o,...c}=e,a=i?await i(t):void 0,s=x(t,{...c,...a?{verification:a}:{}});r?.(s);let u=l=>l;if(s.action==="block")return {decision:s,response:new Response(`Forbidden: agent identity could not be verified.
5
+ `,{status:403}),decorate:u};if(s.action==="meter"){try{let l=t.url,g=t.method;try{l=new URL(t.url).pathname;}catch{}await o?.record({decision:s,units:1,path:l,method:g});}catch{}return {decision:s,response:null,decorate:u}}if(s.action!=="charge")return {decision:s,response:null,decorate:u};let d=await n.handle(t);return d.status==="challenge"?{decision:s,response:d.response,decorate:u}:{decision:s,response:null,decorate:d.receipt??u}}function w(t){let e=Math.round(t.validForSeconds/86400),n=e>=1?`${e} day${e===1?"":"s"}`:`${t.validForSeconds}s`;return t.description??`${t.units.toLocaleString("en-US")} ${t.unit} for ${n}, ${t.price}`}function J(t){let{store:e,offer:n,exposeRemaining:r,...i}=t;return {async handle(o){let c=m(o);if(c){let a=await e.lookup(c.value,o),s=Math.floor(Date.now()/1e3);if(a!==null&&(a.expiresAt===void 0||a.expiresAt>s)&&(a.remaining===void 0||a.remaining>0)&&a){await e.consume?.(a,o);let d=a.remaining===void 0?void 0:a.remaining-1;return {status:"paid",...r&&d!==void 0?{receipt:l=>{let g=new Headers(l.headers);return g.set("x-quota-remaining",String(Math.max(0,d))),new Response(l.body,{status:l.status,statusText:l.statusText,headers:g})}}:{}}}}return {status:"challenge",response:f({...i,body:`Payment required for training access.
6
+ Offer: ${w(n)}
7
+ `,headers:{...i.headers??{},"x-bulk-offer":w(n)}})}}}}function Q(t={}){let e=new Map(Object.entries(t));return {lookup:n=>e.get(n)??null,consume:n=>{n.remaining!==void 0&&e.set(n.id,{...n,remaining:n.remaining-1});},entries:()=>Object.fromEntries(e)}}export{J as entitlementGateway,L as hasPaymentPayload,Q as memoryEntitlementStore,F as mppxGateway,Y as paymentGate,m as paymentPayload,f as paymentRequired,z as respondToDecision,N as withSettlement,_ as x402Gateway};//# sourceMappingURL=pay.js.map
8
+ //# sourceMappingURL=pay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/payments.ts","../src/bots.ts","../src/policy.ts","../src/gateway.ts","../src/entitlement.ts"],"names":["X402_CHALLENGE","X402_SIGNATURE","X402_SETTLEMENT","MPP_CHALLENGE","MPP_CREDENTIAL","quoted","v","b64","json","text","bytes","bin","b","paymentRequired","opts","headers","c","params","k","paymentPayload","req","x402","auth","m","hasPaymentPayload","withSettlement","res","settlement","respondToDecision","decision","AI_BOT_PATTERN","HTTP_CLIENT_PATTERN","isAiBot","userAgent","isHttpClient","parseBotName","s","detectHeadless","signals","ua","secChUa","accept","score","classifyAgent","label","aiBot","httpClient","kind","classifyRequest","base","headless","RETRIEVAL","TRAINING","SEARCH","PREVIEW","agentIntent","agentPolicy","intent","entry","resolved","verification","mppxGateway","handler","out","x402Gateway","settle","receipt","challenge","sig","paymentGate","gateway","onDecision","verify","meter","policyOpts","identity","path","method","offerDescription","offer","days","window","entitlementGateway","store","exposeRemaining","submitted","ent","now","left","h","memoryEntitlementStore","seed","map","credential"],"mappings":"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","file":"pay.js","sourcesContent":["/**\n * Charge for training crawls. **EXPERIMENTAL.**\n *\n * The protocols this speaks are weeks old and moving. x402 and MPP are both\n * live but their specs are unstable, MPP's settlement-confirmation header was\n * not pinned publicly at the time of writing, and no agent in our production\n * traffic has yet presented a payment credential. Expect this API to change\n * without a major version while that settles — everything else in the package\n * is stable, this is not.\n *\n * Today the industry's answer to bulk AI crawling is `Disallow` — over 2.5\n * million sites block AI training in robots.txt. That leaves money on the\n * table and depends on the crawler's goodwill to work at all.\n *\n * The alternative is to let them train and price it. That only works if you\n * can tell training from retrieval, because they have opposite economics: a\n * `GPTBot` fetch is corpus collection you get nothing back for, while a\n * `ChatGPT-User` fetch is a person asking about you — charging for the second\n * is charging for your own distribution. {@link agentPolicy} draws that line;\n * this module turns a `'charge'` decision into the HTTP challenge.\n *\n * Two protocols, one status code. Both settle at the HTTP layer and both use\n * 402, but the framing differs:\n *\n * x402 PAYMENT-REQUIRED: <base64 JSON> -> PAYMENT-SIGNATURE\n * MPP WWW-Authenticate: Payment id=\"…\" -> Authorization: Payment …\n *\n * MPP reuses standard HTTP authentication framing; x402 defines its own\n * headers. They do not collide, so a single 402 can advertise both and let the\n * agent pick — which is what {@link paymentRequired} does when given both.\n *\n * Scope: this emits the 402 and reads the client's payment header. It does not\n * settle anything. Settlement belongs to an x402 facilitator or Stripe's MPP —\n * a library that held money would inherit PCI scope and stop being something\n * you can drop into middleware.\n */\n\nimport type { AgentDecision } from './policy.js'\n\n/**\n * One way a client may pay. Field names follow x402's `PaymentRequirements`;\n * values are yours — the library never invents an amount, network or asset.\n */\nexport interface PaymentRequirements {\n scheme: string\n network: string\n maxAmountRequired: string\n resource: string\n description?: string\n mimeType?: string\n payTo: string\n maxTimeoutSeconds?: number\n asset: string\n extra?: Record<string, unknown>\n}\n\n/** Which settlement protocol a challenge speaks. */\nexport type PaymentProtocol = 'x402' | 'mpp'\n\n/** x402: base64 JSON in a `PAYMENT-REQUIRED` header. */\nexport interface X402Challenge {\n protocol: 'x402'\n /** Accepted payment methods, in preference order. At least one. */\n accepts: readonly PaymentRequirements[]\n /** Protocol version. Defaults to 1. */\n x402Version?: number\n}\n\n/**\n * MPP: an RFC 9110 `WWW-Authenticate: Payment` challenge.\n *\n * Field values are yours. `request` carries the encoded challenge payload your\n * MPP provider generates — the library does not construct or price it.\n */\nexport interface MppChallenge {\n protocol: 'mpp'\n /** Challenge identifier. */\n id: string\n /** Authentication realm. */\n realm: string\n /** Payment method, e.g. `'tempo'`. */\n method: string\n /** Transaction intent, e.g. `'charge'`. */\n intent?: string\n /** Encoded challenge data from your provider. */\n request?: string\n}\n\nexport type PaymentChallenge = X402Challenge | MppChallenge\n\nexport interface PaymentChallengeOptions {\n /**\n * Challenges to advertise. Supplying both an x402 and an MPP challenge is\n * valid and usually correct: they use non-colliding headers, so one 402 can\n * offer both and the agent takes whichever it speaks.\n */\n challenges: readonly PaymentChallenge[]\n /**\n * `Content-Signal` to send with the challenge. Defaults to\n * `search=yes, ai-input=yes, ai-train=paid` — the whole point being that\n * training is available rather than forbidden.\n */\n contentSignal?: string\n /** Extra response headers. */\n headers?: Record<string, string>\n /** Human-readable body. Agents read the header; people read logs. */\n body?: string\n}\n\nconst X402_CHALLENGE = 'PAYMENT-REQUIRED'\nconst X402_SIGNATURE = 'PAYMENT-SIGNATURE'\nconst X402_SETTLEMENT = 'PAYMENT-RESPONSE'\nconst MPP_CHALLENGE = 'WWW-Authenticate'\nconst MPP_CREDENTIAL = 'Authorization'\n\n/** Quote and escape a WWW-Authenticate auth-param value per RFC 9110. */\nfunction quoted(v: string): string {\n return `\"${v.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"')}\"`\n}\n\nfunction b64(json: unknown): string {\n const text = JSON.stringify(json)\n // btoa is Latin-1 only; encode first so non-ASCII descriptions survive.\n const bytes = new TextEncoder().encode(text)\n let bin = ''\n for (const b of bytes) bin += String.fromCharCode(b)\n return btoa(bin)\n}\n\n/**\n * Build a 402 challenge.\n *\n * @example\n * ```ts\n * const decision = agentPolicy(req, { onTraining: 'charge' })\n * if (decision.action === 'charge') {\n * return paymentRequired({\n * challenges: [\n * {\n * protocol: 'x402',\n * accepts: [{\n * scheme: 'exact',\n * network: 'base',\n * maxAmountRequired: '1000', // your price, your units\n * resource: req.url,\n * payTo: process.env.WALLET!,\n * asset: process.env.USDC!\n * }]\n * },\n * { protocol: 'mpp', id: challengeId, realm: 'example.com', method: 'tempo', intent: 'charge' }\n * ]\n * })\n * }\n * ```\n */\nexport function paymentRequired(opts: PaymentChallengeOptions): Response {\n if (!opts.challenges.length) {\n throw new Error('paymentRequired needs at least one challenge')\n }\n\n const headers = new Headers({\n 'content-type': 'text/plain; charset=utf-8',\n // Says the quiet part out loud: training is for sale, not forbidden.\n 'content-signal': opts.contentSignal ?? 'search=yes, ai-input=yes, ai-train=paid'\n })\n\n for (const c of opts.challenges) {\n if (c.protocol === 'x402') {\n if (!c.accepts.length) {\n throw new Error('an x402 challenge needs at least one entry in `accepts`')\n }\n headers.set(X402_CHALLENGE, b64({ x402Version: c.x402Version ?? 1, accepts: c.accepts }))\n } else {\n const params = [\n `id=${quoted(c.id)}`,\n `realm=${quoted(c.realm)}`,\n `method=${quoted(c.method)}`,\n ...(c.intent ? [`intent=${quoted(c.intent)}`] : []),\n ...(c.request ? [`request=${quoted(c.request)}`] : [])\n ]\n // `append`, not `set`: WWW-Authenticate legitimately carries multiple\n // challenges, and a caller may already have added one.\n headers.append(MPP_CHALLENGE, `Payment ${params.join(', ')}`)\n }\n }\n\n for (const [k, v] of Object.entries(opts.headers ?? {})) headers.set(k, v)\n\n return new Response(opts.body ?? 'Payment required for training access.\\n', {\n status: 402,\n headers\n })\n}\n\n/** A payment credential the client sent back, and which protocol it speaks. */\nexport interface SubmittedPayment {\n protocol: PaymentProtocol\n /** Raw header value, for handing to a facilitator. */\n value: string\n}\n\n/**\n * Read the client's payment credential, whichever protocol it used.\n *\n * x402 sends `PAYMENT-SIGNATURE`; MPP sends `Authorization: Payment …`. The\n * `Payment` scheme check matters — a site behind normal auth will also have a\n * Bearer or Basic `Authorization` header, and mistaking that for a payment\n * would be a security-relevant confusion.\n */\nexport function paymentPayload(req: Request): SubmittedPayment | null {\n const x402 = req.headers.get(X402_SIGNATURE)\n if (x402) return { protocol: 'x402', value: x402 }\n\n const auth = req.headers.get(MPP_CREDENTIAL)\n if (auth) {\n const m = auth.match(/^Payment\\s+(.*)$/i)\n if (m?.[1]) return { protocol: 'mpp', value: m[1] }\n }\n return null\n}\n\n/**\n * True when the client attached a payment credential — i.e. this is the retry\n * after a 402, not a fresh unpaid request.\n *\n * Presence is not proof. Hand the value to your facilitator to verify and\n * settle; only then serve the resource.\n */\nexport function hasPaymentPayload(req: Request): boolean {\n return paymentPayload(req) !== null\n}\n\n/**\n * Attach a facilitator's settlement result to a successful response.\n *\n * x402 defines `PAYMENT-RESPONSE` for this. MPP's public spec did not pin a\n * settlement-confirmation header at the time of writing, so pass `header` to\n * name whatever your provider expects rather than have the library guess.\n */\nexport function withSettlement(\n res: Response,\n settlement: unknown,\n opts: { header?: string } = {}\n): Response {\n const headers = new Headers(res.headers)\n headers.set(opts.header ?? X402_SETTLEMENT, b64(settlement))\n return new Response(res.body, { status: res.status, statusText: res.statusText, headers })\n}\n\n/**\n * Convenience: turn an {@link AgentDecision} straight into a response, or\n * `null` when the request should simply be served.\n *\n * Returns 403 for `'block'`, a 402 challenge for `'charge'`, and `null` for\n * `'allow'` and `'meter'` — metering is an accounting concern, not a gate, so\n * the request still gets served while `trackVisit` records it.\n */\nexport function respondToDecision(\n decision: AgentDecision,\n opts: PaymentChallengeOptions\n): Response | null {\n if (decision.action === 'block') {\n return new Response('Forbidden: agent identity could not be verified.\\n', { status: 403 })\n }\n if (decision.action === 'charge') {\n return paymentRequired({\n body: `Payment required: ${decision.label} — ${decision.reason}.\\n`,\n ...opts\n })\n }\n return null\n}\n","/**\n * User-agent substrings that identify **publicly declared** AI crawlers — the\n * branded bots that identify themselves by name (OpenAI's GPTBot, Anthropic's\n * ClaudeBot, Perplexity-User, Google-Extended, etc.). High-confidence: when\n * this matches, the request almost certainly comes from that vendor's crawler\n * fleet.\n *\n * Does NOT include **coding-agent traffic** (Claude Code, Cline, Cursor,\n * Windsurf, Aider, OpenCode, VS Code). Those tools use generic HTTP library\n * UAs (axios, curl, got, colly, Electron) or spoof full browser UAs — they\n * can't be distinguished from non-AI traffic by UA alone. See\n * {@link HTTP_CLIENT_PATTERN} for the loose heuristic layer.\n *\n * Sources consulted when updating: darkvisitors.com, vendor docs from OpenAI,\n * Anthropic, Google, Perplexity, Cohere, Apple, Bytedance.\n */\nexport const AI_BOT_PATTERN =\n /ClaudeBot|Claude-User|Claude-SearchBot|Claude-Web|Anthropic|GPTBot|ChatGPT-User|OAI-SearchBot|PerplexityBot|Perplexity-User|Google-Extended|Google-CloudVertexBot|Google-Agent|GoogleAgent-Mariner|Gemini-Deep-Research|Applebot|cohere|Bytespider|CCBot|Amazonbot|Amzn-SearchBot|NovaAct|AzureAI-SearchBot|Meta-ExternalAgent|meta-externalfetcher|meta-webindexer|FacebookBot|DuckAssistBot|MistralAI-User|YouBot|AI2Bot|Diffbot|DeepSeek|PanguBot|Webzio-Extended|omgili|Timpibot|Grok|Manus-User|quillbot|MyCentralAIScraperBot|Cursor|Windsurf/i\n\n/**\n * HTTP library / runtime signatures frequently used by coding agents. Matching\n * any of these is a **loose** signal — legitimate curl scripts, CI jobs, and\n * server-to-server traffic use the same libraries. Use this for the wider\n * net (`coding_agent_hint: true`) and pair with other signals (request\n * shape, JA4 fingerprint, path patterns) for higher confidence.\n *\n * Based on behavioural signatures observed by Addy Osmani:\n * Claude Code → axios/1.8.4\n * Cline, Junie → curl/8.4.0\n * Cursor → got (sindresorhus/got)\n * Windsurf → colly\n * VS Code → Electron / Chromium\n *\n * Aider and OpenCode use Playwright-driven full Mozilla/Safari UAs and are\n * indistinguishable from real browsers at the UA layer.\n */\nexport const HTTP_CLIENT_PATTERN =\n /axios\\/|curl\\/|(?:^|[\\s(])got(?:\\/|[\\s(])|\\bcolly\\b|Electron\\/|node-fetch\\/|python-requests\\/|Go-http-client\\/|okhttp\\/|aiohttp\\/|Deno\\//i\n\nexport function isAiBot(userAgent: string | null | undefined): boolean {\n if (!userAgent) return false\n return AI_BOT_PATTERN.test(userAgent)\n}\n\nexport function isHttpClient(userAgent: string | null | undefined): boolean {\n if (!userAgent) return false\n return HTTP_CLIENT_PATTERN.test(userAgent)\n}\n\n/**\n * Map a user-agent string to a coarse, human-readable label. Returns one of:\n *\n * - A branded-crawler name (`'Claude'`, `'ChatGPT'`, …) — pair with\n * {@link isAiBot} for `is_ai_bot: true` segmentation.\n * - An HTTP-library name (`'curl'`, `'axios'`, `'got'`, `'colly'`,\n * `'Electron'`, …) — hint of a coding agent or automation; not\n * conclusive. Pair with {@link isHttpClient}.\n * - `'Browser'` for typical desktop browsers (possibly spoofed by\n * Playwright-based agents like Aider/OpenCode — this label alone can't\n * tell you).\n * - `'Other'` for anything unrecognised or empty input.\n */\nexport function parseBotName(userAgent: string | null | undefined): string {\n if (!userAgent || typeof userAgent !== 'string') return 'Other'\n const s = userAgent.toLowerCase()\n\n // Publicly declared AI crawlers (high confidence).\n if (s.includes('chatgpt-user') || s.includes('gptbot') || s.includes('oai-searchbot') || s.includes('openai'))\n return 'ChatGPT'\n if (\n s.includes('claudebot') ||\n s.includes('claude-user') ||\n s.includes('claude-searchbot') ||\n s.includes('claude-web') ||\n s.includes('anthropic')\n )\n return 'Claude'\n if (s.includes('perplexitybot') || s.includes('perplexity-user')) return 'Perplexity'\n if (s.includes('ccbot')) return 'Common Crawl'\n if (\n s.includes('google-extended') ||\n s.includes('googlebot') ||\n s.includes('google-cloudvertexbot') ||\n s.includes('google-agent') ||\n s.includes('googleagent-mariner') ||\n s.includes('gemini-deep-research')\n )\n return 'Google'\n if (s.includes('applebot')) return 'Apple'\n if (s.includes('bingbot')) return 'Bing'\n if (s.includes('bytespider')) return 'Bytespider'\n if (s.includes('amazonbot') || s.includes('amzn-searchbot') || s.includes('novaact')) return 'Amazon'\n if (\n s.includes('meta-externalagent') ||\n s.includes('meta-externalfetcher') ||\n s.includes('meta-webindexer') ||\n s.includes('facebookbot')\n )\n return 'Meta'\n if (s.includes('mistralai-user')) return 'Mistral'\n if (s.includes('duckassistbot')) return 'DuckDuckGo'\n if (s.includes('youbot')) return 'You.com'\n if (s.includes('diffbot')) return 'Diffbot'\n if (s.includes('ai2bot')) return 'AI2'\n if (s.includes('cohere')) return 'Cohere'\n if (s.includes('cursor')) return 'Cursor'\n if (s.includes('windsurf')) return 'Windsurf'\n if (s.includes('deepseek')) return 'DeepSeek'\n if (s.includes('pangubot')) return 'Huawei'\n if (s.includes('webzio') || s.includes('omgili')) return 'Webz.io'\n if (s.includes('timpibot')) return 'Timpi'\n if (s.includes('grok') || s.includes('xai-')) return 'xAI'\n if (s.includes('manus-user')) return 'Manus'\n if (s.includes('quillbot')) return 'QuillBot'\n if (s.includes('azureai-searchbot')) return 'Microsoft'\n if (s.includes('mycentralaiscraperbot')) return 'MyCentralAI'\n if (s.includes('petalbot')) return 'PetalBot'\n\n // SEO crawlers and monitoring bots.\n if (s.includes('ahrefsbot')) return 'Ahrefs'\n if (s.includes('semrushbot')) return 'Semrush'\n if (s.includes('mj12bot')) return 'Majestic'\n if (s.includes('dotbot')) return 'Moz'\n if (s.includes('rogerbot')) return 'Moz'\n if (s.includes('screaming frog')) return 'Screaming Frog'\n if (s.includes('sitebulb')) return 'Sitebulb'\n if (s.includes('linkfluence')) return 'Linkfluence'\n if (s.includes('dataforseo')) return 'DataForSEO'\n if (s.includes('serpstatbot')) return 'Serpstat'\n\n // Monitoring and feed bots.\n if (s.includes('uptimerobot')) return 'UptimeRobot'\n if (s.includes('pingdom')) return 'Pingdom'\n if (s.includes('statuscake')) return 'StatusCake'\n if (s.includes('newrelicpinger')) return 'New Relic'\n if (s.includes('datadogagent') || s.includes('datadog')) return 'Datadog'\n if (s.includes('slackbot')) return 'Slack'\n if (s.includes('twitterbot')) return 'Twitter'\n if (s.includes('linkedinbot')) return 'LinkedIn'\n if (s.includes('discordbot')) return 'Discord'\n if (s.includes('telegrambot')) return 'Telegram'\n if (s.includes('whatsapp')) return 'WhatsApp'\n\n // AI search and indexing bots.\n if (s.includes('linkupbot')) return 'Linkup'\n if (s.includes('sogou')) return 'Sogou'\n if (s.includes('yandexbot')) return 'Yandex'\n if (s.includes('baiduspider')) return 'Baidu'\n\n // Link preview fetchers.\n if (s.includes('facebookexternalhit')) return 'Facebook'\n if (s.includes('com.apple.webkit')) return 'Apple URL Preview'\n\n // Uptime and monitoring.\n if (s.includes('ohdear')) return 'Oh Dear'\n\n // Generic scrapers.\n if (s.includes('scrapy')) return 'Scrapy'\n if (s.includes('headlesschrome')) return 'Headless Chrome'\n if (s.includes('phantomjs')) return 'PhantomJS'\n if (s.includes('wget')) return 'wget'\n if (s.includes('httpie')) return 'HTTPie'\n if (s.includes('guzzlehttp')) return 'Guzzle'\n\n // HTTP library / runtime signatures (loose — coding agent or automation).\n // Check Electron before Browser since Electron UAs contain Chrome/Safari.\n if (s.includes('electron/')) return 'Electron'\n if (/curl\\//.test(s)) return 'curl'\n if (/axios\\//.test(s)) return 'axios'\n if (/(?:^|[\\s(])got(?:\\/|[\\s(])/.test(s)) return 'got'\n if (/\\bcolly\\b/.test(s)) return 'colly'\n if (/node-fetch\\//.test(s)) return 'node-fetch'\n if (/python-requests\\//.test(s)) return 'python-requests'\n if (/go-http-client\\//.test(s)) return 'Go http client'\n if (/okhttp\\//.test(s)) return 'OkHttp'\n if (/aiohttp\\//.test(s)) return 'aiohttp'\n if (/deno\\//.test(s)) return 'Deno'\n\n // Real browsers (or UAs spoofed to look like them — see Aider/OpenCode note).\n if (s.includes('mozilla') || s.includes('chrome') || s.includes('safari') || s.includes('firefox'))\n return 'Browser'\n\n return 'Other'\n}\n\n/**\n * Return the first product token from a UA header, useful for segmenting by\n * client without hard-coding every bot name. Falls back to `'Other'` for empty\n * input.\n */\nexport function firstUserAgentProduct(userAgent: string | null | undefined): string {\n if (!userAgent || typeof userAgent !== 'string') return 'Other'\n const compatibleMatch = userAgent.match(/compatible;\\s*([^/;\\s]+)(?:\\/[^\\s;]*)?/i)\n if (compatibleMatch && compatibleMatch[1]) return compatibleMatch[1].trim()\n const first = userAgent.trim().split('/')[0]?.trim().split(/\\s+/)[0]?.trim()\n return first || 'Other'\n}\n\n/**\n * Detect likely headless/automated browsers by checking for missing headers\n * that real browsers always send. Playwright, Puppeteer, and similar tools\n * spoof the UA but often omit standard browser headers.\n *\n * Signals checked (each scores 1 point):\n * - Missing `Accept-Language` — every real browser sends this\n * - Missing `Sec-Fetch-Mode` — sent by all modern browsers\n * - Missing `Sec-CH-UA` — Client Hints, Chromium 89+\n * - `Sec-CH-UA` contains \"HeadlessChrome\"\n * - Missing or bare Accept header — browsers send detailed accept lists\n * - `Connection: close` with browser UA — browsers use keep-alive\n *\n * Returns a score (0-6), the signals that fired, and a boolean `likely`\n * flag (score >= 2 with a browser-like UA).\n */\nexport function detectHeadless(req: Request): HeadlessDetection {\n const signals: string[] = []\n const ua = (req.headers.get('user-agent') || '').toLowerCase()\n const isBrowserUA =\n ua.includes('mozilla') || ua.includes('chrome') || ua.includes('safari') || ua.includes('firefox')\n\n if (!isBrowserUA) return { score: 0, signals: [], likely: false }\n\n if (!req.headers.get('accept-language')) {\n signals.push('missing-accept-language')\n }\n if (!req.headers.get('sec-fetch-mode')) {\n signals.push('missing-sec-fetch-mode')\n }\n const secChUa = req.headers.get('sec-ch-ua')\n if (!secChUa) {\n signals.push('missing-sec-ch-ua')\n } else if (secChUa.toLowerCase().includes('headlesschrome')) {\n signals.push('headless-chrome-hint')\n }\n const accept = req.headers.get('accept') || ''\n if (!accept || accept === '*/*') {\n signals.push('missing-or-bare-accept')\n }\n if ((req.headers.get('connection') || '').toLowerCase() === 'close') {\n signals.push('connection-close')\n }\n\n const score = signals.length\n return { score, signals, likely: score >= 2 }\n}\n\nexport interface HeadlessDetection {\n /** Number of suspicious signals found (0-6). */\n score: number\n /** Names of the specific signals that fired. */\n signals: string[]\n /** True when score >= 2 — strong headless indication. */\n likely: boolean\n}\n\nexport type AgentKind =\n | 'declared-crawler'\n | 'coding-agent-hint'\n | 'headless-likely'\n | 'browser'\n | 'other'\n\nexport interface AgentClassification {\n /**\n * Categorical tag for the request:\n *\n * - `'declared-crawler'` — {@link AI_BOT_PATTERN} matched. High confidence.\n * - `'coding-agent-hint'` — {@link HTTP_CLIENT_PATTERN} matched. Loose\n * signal; could be a coding agent, a curl script, or any automation.\n * - `'headless-likely'` — Browser-like UA but missing standard headers.\n * Strong signal of Playwright/Puppeteer automation (Aider, OpenCode, etc.).\n * - `'browser'` — Looks like a real browser with expected headers present.\n * - `'other'` — Unrecognised or empty.\n */\n kind: AgentKind\n /** Human-readable label, same string {@link parseBotName} returns. */\n label: string\n /** Strict: `true` only when the UA matches a branded AI crawler. */\n isAiBot: boolean\n /** Loose: `true` for known HTTP-library / automation UAs. */\n codingAgentHint: boolean\n /** Headless browser detection result. Only populated when `req` is passed. */\n headless?: HeadlessDetection\n}\n\n/**\n * UA-only classification. Use {@link classifyRequest} for full detection\n * including headless browser heuristics.\n */\nexport function classifyAgent(userAgent: string | null | undefined): AgentClassification {\n const label = parseBotName(userAgent)\n const aiBot = isAiBot(userAgent)\n const httpClient = isHttpClient(userAgent)\n\n let kind: AgentKind\n if (aiBot) kind = 'declared-crawler'\n else if (httpClient) kind = 'coding-agent-hint'\n else if (label === 'Browser') kind = 'browser'\n else kind = 'other'\n\n return { kind, label, isAiBot: aiBot, codingAgentHint: httpClient }\n}\n\n/**\n * Full request classification — combines UA parsing with header-based\n * headless detection. When a browser-like UA is missing standard headers,\n * the kind is promoted from `'browser'` to `'headless-likely'`.\n */\nexport function classifyRequest(req: Request): AgentClassification {\n const userAgent = req.headers.get('user-agent') || ''\n const base = classifyAgent(userAgent)\n const headless = detectHeadless(req)\n\n let kind = base.kind\n let label = base.label\n if (kind === 'browser' && headless.likely) {\n kind = 'headless-likely'\n // Relabel too. Leaving it as 'Browser' meant automation with a spoofed\n // browser UA — 79% of one production site's agent traffic — was\n // indistinguishable from a human in any `bot_name` breakdown, and was\n // silently excluded by the obvious `bot_name != 'Browser'` filter.\n label = 'Headless'\n }\n\n return { ...base, kind, label, headless }\n}\n","import { classifyRequest, isHttpClient } from './bots.js'\nimport type { BotVerificationLike } from './types.js'\n\n/**\n * What to do with an agent request.\n *\n * - `'allow'` — serve it, free. Humans, search crawlers, and the retrieval\n * agents you *want* reading your site.\n * - `'meter'` — serve it, but count it as billable. Bulk corpus collection.\n * - `'charge'` — don't serve it until it pays (HTTP 402).\n * - `'block'` — refuse. Failed identity verification, mostly.\n */\nexport type AgentAction = 'allow' | 'meter' | 'charge' | 'block'\n\n/**\n * Why an agent fetched the page. This is the distinction the whole module\n * exists for, and no other signal on the request carries it.\n *\n * - `'retrieval'` — a person asked a question and the assistant went to read\n * the page for them. This is *demand*: the agent is a distribution channel,\n * and charging for it is charging for your own marketing.\n * - `'training'` — bulk corpus collection for model training. You get nothing\n * back per fetch, which is where a price makes sense.\n * - `'search'` — index crawlers, traditional and AI-native. Blocking these\n * costs you organic traffic or citations in an assistant's answer.\n * - `'preview'` — link unfurlers. Someone pasted your URL into Slack, iMessage\n * or a tweet and the platform fetched it to render a card. No model involved,\n * but blocking it means your links look broken wherever they get shared.\n * - `'tooling'` — coding agents and HTTP clients. Usually developers using\n * your docs; treat like retrieval unless you see abuse.\n * - `'unknown'` — everything else, including real browsers.\n */\nexport type AgentIntent = 'retrieval' | 'training' | 'search' | 'preview' | 'tooling' | 'unknown'\n\n/**\n * User agents where a human is waiting on the answer. Deliberately explicit\n * rather than pattern-guessed: `-User` is not a reliable marker (OpenAI's\n * ChatGPT-User fetches server-side; Claude Code's Claude-User runs on a\n * laptop), and getting this wrong means charging your own demand channel.\n */\nconst RETRIEVAL = /ChatGPT-User|OAI-SearchBot|Claude-User|Claude-SearchBot|Perplexity-User|claude-code|DuckAssistBot|MistralAI-User|Gemini-Deep-Research|Manus-User|YouBot/i\n\n/** Bulk crawlers that collect corpora. No human is waiting on these. */\nconst TRAINING = /GPTBot|ClaudeBot|Claude-Web|CCBot|Bytespider|Amazonbot|Amzn-SearchBot|Meta-ExternalAgent|meta-externalfetcher|meta-webindexer|FacebookBot|Google-Extended|Applebot-Extended|AI2Bot|Diffbot|omgili|Webzio-Extended|Timpibot|PanguBot|cohere|DeepSeek|Grok|quillbot|MyCentralAIScraperBot|NovaAct|AzureAI-SearchBot|Google-CloudVertexBot/i\n\n/**\n * Index crawlers — blocking these costs you organic traffic.\n *\n * `PerplexityBot` sits here rather than in TRAINING despite the `Bot` suffix:\n * Perplexity documents it as the crawler behind their *search results* and\n * states it does not feed foundation-model training. Blocking it costs you\n * citations, which is the same shape of loss as blocking Googlebot. It was\n * previously in no list at all, so it classified as `unknown` and fell through\n * both the protective bypass and the training rate limit — a live gap found in\n * production traffic, not in review.\n */\nconst SEARCH = /bingbot|Googlebot|DuckDuckBot|YandexBot|Baiduspider|PetalBot|Sogou|PerplexityBot|Bravebot|Applebot(?!-Extended)/i\n\n/**\n * Link unfurlers. A human shared the URL and a platform fetched it to build a\n * preview card — one request, no crawl, and the payoff is a rendered link in a\n * conversation. They get their own intent rather than being folded into\n * `retrieval` because retrieval is the library's demand signal: counting\n * Slackbot as \"an assistant went to read this for someone\" would inflate the\n * one number the split exists to measure.\n *\n * These tokens are trivially spoofable — `facebookexternalhit` is among the\n * most-forged strings on the web. Treat this as a routing hint, never as\n * identity, and note that {@link recommendFirewallRules} proposes them as a\n * separate, higher-risk rule for exactly that reason.\n */\nconst PREVIEW = /facebookexternalhit|Twitterbot|LinkedInBot|Slackbot|Discordbot|TelegramBot|WhatsApp|redditbot|Pinterest|SkypeUriPreview|Iframely|Embedly|vkShare|Mastodon|Bluesky/i\n\nexport interface AgentDecision {\n action: AgentAction\n intent: AgentIntent\n /** Vendor label, same string `parseBotName` returns. */\n label: string\n /** Identity verdict, when a verifier was supplied. */\n verification?: string\n /** Short human-readable justification — log it, don't parse it. */\n reason: string\n}\n\nexport interface AgentPolicyOptions {\n /**\n * Identity verifier. Import `verifyRequest` from\n * `@apideck/agent-analytics/verify` and pass it here to have a `spoofed`\n * verdict produce `'block'`.\n *\n * Injected rather than imported so the published IP range tables only reach\n * bundles that use them. Only meaningful when your edge controls\n * `x-forwarded-for`: behind a proxy that forwards a client-supplied header,\n * an attacker picks their own verdict.\n */\n verify?: (req: Request) => BotVerificationLike\n /**\n * A verification already computed elsewhere. Use this when your verifier is\n * async — {@link verifyWebBotAuth} fetches a key directory, so the natural\n * verifier from `@apideck/agent-analytics/verify` returns a promise and\n * cannot be passed to `verify` on this synchronous function.\n *\n * {@link paymentGate} does this for you: it awaits the verifier and forwards\n * the result here.\n */\n verification?: BotVerificationLike\n /** What to do with bulk training crawlers. Defaults to `'meter'`. */\n onTraining?: AgentAction\n /** What to do with retrieval agents. Defaults to `'allow'` — see AgentIntent. */\n onRetrieval?: AgentAction\n /** What to do with search indexers. Defaults to `'allow'`. */\n onSearch?: AgentAction\n /**\n * What to do with link unfurlers. Defaults to `'allow'` — gating these does\n * not earn you anything, it just makes your links render as bare URLs.\n */\n onPreview?: AgentAction\n /** What to do with coding agents and HTTP clients. Defaults to `'allow'`. */\n onTooling?: AgentAction\n /** Vendor labels or UA substrings always allowed, whatever the intent. */\n allowList?: readonly string[]\n}\n\n/**\n * Classify why an agent is here, from its user agent alone.\n *\n * This must return exactly what {@link agentPolicy} reports for the same UA.\n * It previously did not: the `tooling` promotion for HTTP-library UAs lived\n * only inside `agentPolicy`, so `agentIntent('curl/8.4.0')` said `'unknown'`\n * while the policy said `'tooling'` — two exported functions disagreeing on\n * every HTTP client, with no way for a caller to know which was right. The\n * invariant is pinned by a test.\n */\nexport function agentIntent(userAgent: string | null | undefined): AgentIntent {\n const ua = userAgent ?? ''\n if (!ua) return 'unknown'\n // Retrieval is checked first: several vendors ship both a bulk crawler and a\n // user-facing fetcher whose tokens overlap (ClaudeBot vs Claude-User).\n if (RETRIEVAL.test(ua)) return 'retrieval'\n if (TRAINING.test(ua)) return 'training'\n if (SEARCH.test(ua)) return 'search'\n // After SEARCH so Applebot stays a search crawler rather than an iMessage\n // unfurler — Apple uses the same token for both.\n if (PREVIEW.test(ua)) return 'preview'\n // An HTTP-library UA that matched no vendor is a coding agent or a script.\n if (isHttpClient(ua)) return 'tooling'\n return 'unknown'\n}\n\n/**\n * Decide what to do with a request. Pure classification plus policy — no\n * payment rails, no network calls, nothing to configure beyond the four\n * intent knobs.\n *\n * @example\n * ```ts\n * const decision = agentPolicy(req, { verify: true, onTraining: 'charge' })\n * if (decision.action === 'block') return new Response(null, { status: 403 })\n * if (decision.action === 'charge') return paymentRequired(decision)\n * ```\n */\nexport function agentPolicy(req: Request, opts: AgentPolicyOptions = {}): AgentDecision {\n const ua = req.headers.get('user-agent') || ''\n const classification = classifyRequest(req)\n const label = classification.label\n\n // Single source of truth — the promotion that used to live here now lives in\n // agentIntent, so the two can no longer drift apart.\n const intent = agentIntent(ua)\n\n const allowed = opts.allowList?.some(\n (entry) => entry === label || ua.toLowerCase().includes(entry.toLowerCase())\n )\n if (allowed) {\n return { action: 'allow', intent, label, reason: 'on allowList' }\n }\n\n // A pre-resolved verification wins: it is the only way an async verifier can\n // reach this synchronous function.\n const resolved = opts.verification ?? (opts.verify ? opts.verify(req) : undefined)\n let verification: string | undefined\n if (resolved) {\n verification = resolved.verdict\n // Only 'spoofed' is actionable. 'unverifiable' means we couldn't check —\n // blocking on it would refuse every vendor without a published feed and\n // every coding agent running on someone's own machine.\n if (verification === 'spoofed') {\n return {\n action: 'block',\n intent,\n label,\n verification,\n reason: `${label} claimed but client IP is outside its published ranges`\n }\n }\n }\n\n const action: AgentAction =\n intent === 'training'\n ? (opts.onTraining ?? 'meter')\n : intent === 'retrieval'\n ? (opts.onRetrieval ?? 'allow')\n : intent === 'search'\n ? (opts.onSearch ?? 'allow')\n : intent === 'preview'\n ? (opts.onPreview ?? 'allow')\n : intent === 'tooling'\n ? (opts.onTooling ?? 'allow')\n : 'allow'\n\n const REASONS: Record<AgentIntent, string> = {\n retrieval: 'a person is waiting on this answer',\n training: 'bulk corpus collection',\n search: 'search index crawler',\n preview: 'link unfurler building a preview card',\n tooling: 'coding agent or HTTP client',\n unknown: 'not a recognised agent'\n }\n\n return {\n action,\n intent,\n label,\n // Spread rather than assign: `exactOptionalPropertyTypes` distinguishes an\n // absent key from one explicitly set to undefined.\n ...(verification ? { verification } : {}),\n reason: REASONS[intent]\n }\n}\n","/**\n * The paid-access gate: policy decides *whether* to charge, a gateway decides\n * *how*. **EXPERIMENTAL** — see `payments.ts`. The classification and policy\n * layers underneath are stable; the payment surface is not.\n *\n * The split matters. We own classification — telling a training crawl from a\n * retrieval fetch, which is the part nobody else does and the part that makes\n * charging sane. Settlement is somebody else's job: Stripe's MPP SDK, an x402\n * facilitator, whatever comes next. A library that held money would inherit PCI\n * scope and stop being something you drop into middleware.\n *\n * So gateways are injected, exactly like analytics adapters, and this module\n * takes no dependency on Stripe or any chain.\n */\n\nimport { agentPolicy, type AgentDecision, type AgentPolicyOptions } from './policy.js'\nimport type { BotVerificationLike } from './types.js'\nimport { paymentRequired, type PaymentChallengeOptions } from './payments.js'\n\n/**\n * Outcome of handing a request to a payment gateway.\n *\n * - `challenge` — respond with this. The client has not paid.\n * - `paid` — settled; serve the resource. `receipt` decorates the response with\n * whatever proof the protocol expects.\n */\nexport type GatewayResult =\n | { status: 'challenge'; response: Response }\n | { status: 'paid'; receipt?: (res: Response) => Response }\n\nexport interface PaymentGateway {\n handle(req: Request): Promise<GatewayResult>\n}\n\n/**\n * Wrap Stripe's MPP SDK.\n *\n * `Mppx.compose(...)` returns a handler that either yields a 402 with a\n * `.challenge` response, or a settled result with `.withReceipt(res)`. This\n * adapts that shape without importing it — pass the composed handler in.\n *\n * @example\n * ```ts\n * const mppx = Mppx.create({ methods: [...], secretKey })\n * const handler = Mppx.compose(\n * mppx.tempo.charge({ amount: '0.01', recipient }),\n * mppx.stripe.charge({ amount: '0.50', currency: 'usd' })\n * )\n * const gateway = mppxGateway(handler)\n * ```\n */\nexport function mppxGateway(\n handler: (req: Request) => Promise<MppxResponse> | MppxResponse\n): PaymentGateway {\n return {\n async handle(req: Request): Promise<GatewayResult> {\n const out = await handler(req)\n if (out.status === 402) {\n return { status: 'challenge', response: out.challenge }\n }\n return {\n status: 'paid',\n ...(out.withReceipt ? { receipt: (res: Response) => out.withReceipt!(res) } : {})\n }\n }\n }\n}\n\n/** The subset of Stripe's MPP response we rely on. Structural, not imported. */\nexport interface MppxResponse {\n status: number\n challenge: Response\n withReceipt?: (res: Response) => Response\n}\n\nexport interface X402GatewayOptions extends PaymentChallengeOptions {\n /**\n * Verify and settle a `PAYMENT-SIGNATURE` payload with your facilitator.\n * Resolve truthy to serve the resource, falsy to re-challenge.\n */\n settle: (payload: string, req: Request) => Promise<boolean> | boolean\n /** Attach the facilitator's settlement result to the served response. */\n receipt?: (res: Response) => Response\n}\n\n/**\n * Gateway using this library's own challenge builder plus a facilitator you\n * supply. For x402, or for MPP if you are not using Stripe's SDK.\n */\nexport function x402Gateway(opts: X402GatewayOptions): PaymentGateway {\n const { settle, receipt, ...challenge } = opts\n return {\n async handle(req: Request): Promise<GatewayResult> {\n const sig = req.headers.get('PAYMENT-SIGNATURE')\n if (sig && (await settle(sig, req))) {\n return { status: 'paid', ...(receipt ? { receipt } : {}) }\n }\n return { status: 'challenge', response: paymentRequired(challenge) }\n }\n }\n}\n\n/** One unit of billable agent traffic. */\nexport interface MeterRecord {\n decision: AgentDecision\n /** Units consumed. One request is one unit unless you price by bytes or tokens. */\n units: number\n path: string\n method: string\n}\n\n/**\n * Where billable usage goes.\n *\n * Metering is the model to ship first: it needs no crawler cooperation, works\n * today, and produces the number you would negotiate a licence with. Charging\n * per request is what the protocols define but not what a training sweep can\n * actually do — no crawler in the wild retries a 402.\n */\nexport interface Meter {\n record(entry: MeterRecord): Promise<void> | void\n}\n\nexport interface PaymentGateOptions extends Omit<AgentPolicyOptions, 'verify'> {\n gateway: PaymentGateway\n /**\n * Sink for billable traffic. Called for every `'meter'` decision — serve the\n * request, count it, bill out of band.\n *\n * Errors are swallowed: a metering failure must not turn into a failed\n * response, for the same reason analytics failures do not.\n */\n meter?: Meter\n /**\n * Identity verifier, sync or async. Unlike {@link agentPolicy}'s option this\n * accepts a promise, because `paymentGate` is already async and can await it.\n * That matters: `combinedVerifier()` and `webBotAuthVerifier()` are async by\n * necessity — Web Bot Auth fetches the signer's key directory — so without\n * this they could not be used with policy or payments at all.\n */\n verify?: (req: Request) => BotVerificationLike | Promise<BotVerificationLike>\n /**\n * Called for every decision, paid or not — wire it to your metering so\n * `'meter'` traffic is actually counted rather than merely allowed.\n */\n onDecision?: (decision: AgentDecision) => void\n}\n\n/**\n * Full gate: classify, decide, and either let the request through or return the\n * response it should get instead.\n *\n * Returns `null` when the request should be served normally. That covers\n * `'allow'`, `'meter'` (accounting, not a gate) and any request that has already\n * paid — in which case `receipt` is handed back so you can decorate the response\n * you were going to send anyway.\n *\n * @example\n * ```ts\n * const gate = await paymentGate(req, {\n * onTraining: 'charge',\n * verify: combinedVerifier(),\n * gateway: mppxGateway(handler),\n * onDecision: (d) => void trackVisit(req, { analytics, properties: { action: d.action } })\n * })\n * if (gate.response) return gate.response\n * return gate.decorate(await serve(req))\n * ```\n */\nexport async function paymentGate(\n req: Request,\n opts: PaymentGateOptions\n): Promise<{\n decision: AgentDecision\n /** Respond with this instead of serving, when set. */\n response: Response | null\n /** Wrap the response you were going to send. Identity when nothing to add. */\n decorate: (res: Response) => Response\n}> {\n const { gateway, onDecision, verify, meter, ...policyOpts } = opts\n // Await here so an async verifier works. Passing the function straight into\n // agentPolicy would hand it a promise to read `.verdict` off — undefined at\n // runtime, and a type error at compile time.\n const verification = verify ? await verify(req) : undefined\n const decision = agentPolicy(req, {\n ...policyOpts,\n ...(verification ? { verification } : {})\n })\n onDecision?.(decision)\n\n const identity = (res: Response) => res\n\n if (decision.action === 'block') {\n return {\n decision,\n response: new Response('Forbidden: agent identity could not be verified.\\n', {\n status: 403\n }),\n decorate: identity\n }\n }\n\n if (decision.action === 'meter') {\n // Count it and serve it. This is the path most sites should be on.\n try {\n let path = req.url\n let method = req.method\n try {\n path = new URL(req.url).pathname\n } catch {\n /* relative URL from some runtimes — keep the raw string */\n }\n await meter?.record({ decision, units: 1, path, method })\n } catch {\n // Metering must never turn into a failed response.\n }\n return { decision, response: null, decorate: identity }\n }\n\n if (decision.action !== 'charge') {\n return { decision, response: null, decorate: identity }\n }\n\n const out = await gateway.handle(req)\n if (out.status === 'challenge') {\n return { decision, response: out.response, decorate: identity }\n }\n return { decision, response: null, decorate: out.receipt ?? identity }\n}\n","/**\n * Quota and entitlements — the model that actually works for training crawls.\n * **EXPERIMENTAL**, like the rest of the payment surface.\n *\n * Per-request 402 is what x402 and MPP define, and it is the wrong shape for a\n * training sweep. On one production site training traffic is ~199,000 requests a\n * month. Charging each one means three times the traffic (402, pay, retry),\n * 199,000 settlements whose per-transaction cost exceeds any sane per-page\n * price, and — decisively — no crawler in the wild implements the retry, so a\n * per-request 402 is just blocking with extra steps.\n *\n * Two workable shapes instead, both supported here:\n *\n * METER Serve the request, count it, bill out of band. Needs no crawler\n * cooperation and works today. This is the one to ship.\n *\n * ENTITLEMENT Challenge once with a bulk offer, take payment, issue a\n * credential. Every later request presents it and is served\n * directly, decrementing quota. One settlement per licence rather\n * than per page.\n *\n * MPP's reusable `Authorization: Payment` credential fits entitlements better\n * than x402's per-resource signature, which proves payment for one URL.\n */\n\nimport { paymentRequired, paymentPayload, type PaymentChallengeOptions } from './payments.js'\nimport type { GatewayResult, PaymentGateway } from './gateway.js'\n\n/** What a buyer holds after paying. */\nexport interface Entitlement {\n /** Opaque licence id, for your own accounting. */\n id: string\n /**\n * Units left. Omit for an unmetered licence — `consume` is still called, so\n * you can count without capping.\n */\n remaining?: number\n /** Expiry as epoch seconds. Omit for no expiry. */\n expiresAt?: number\n}\n\n/**\n * Where entitlements live. A KV namespace, Redis, your database — anything\n * reachable from the edge. The library deliberately ships no storage: quota\n * state is yours, and so is the money it represents.\n */\nexport interface EntitlementStore {\n /** Resolve the credential a client presented. Return null to challenge. */\n lookup(credential: string, req: Request): Promise<Entitlement | null> | Entitlement | null\n /**\n * Record consumption after a request is admitted. Called for every served\n * request, including unmetered licences, so this doubles as your meter.\n */\n consume?(entitlement: Entitlement, req: Request): Promise<void> | void\n}\n\n/**\n * What is for sale. Folded into the challenge so an agent sees a bulk product\n * rather than a price for the single page it happened to ask for.\n */\nexport interface BulkOffer {\n /** e.g. 1_000_000 */\n units: number\n /** e.g. `'pages'` */\n unit: string\n /** Licence lifetime in seconds. */\n validForSeconds: number\n /** Total price, in whatever units your challenge already uses. */\n price: string\n /** Summary surfaced to the agent. */\n description?: string\n}\n\nexport interface EntitlementGatewayOptions extends PaymentChallengeOptions {\n store: EntitlementStore\n /** The bulk product the 402 advertises. */\n offer: BulkOffer\n /**\n * Emit `x-quota-remaining` on served responses so a paying crawler can see\n * its balance and slow down before running out.\n *\n * Off by default: this header is **not** part of x402 or MPP. It is a\n * convenience, and a crawler that does not know it will ignore it.\n */\n exposeRemaining?: boolean\n}\n\nfunction offerDescription(offer: BulkOffer): string {\n const days = Math.round(offer.validForSeconds / 86_400)\n const window = days >= 1 ? `${days} day${days === 1 ? '' : 's'}` : `${offer.validForSeconds}s`\n return (\n offer.description ??\n `${offer.units.toLocaleString('en-US')} ${offer.unit} for ${window}, ${offer.price}`\n )\n}\n\n/**\n * Gateway that honours a bulk licence instead of charging per request.\n *\n * A request carrying a valid credential is served and its quota decremented —\n * no challenge, no round-trip. A request without one gets a single 402\n * advertising the bulk offer.\n *\n * @example\n * ```ts\n * const gateway = entitlementGateway({\n * store: myKvStore,\n * offer: { units: 1_000_000, unit: 'pages', validForSeconds: 2_592_000, price: '$400' },\n * challenges: [{ protocol: 'mpp', id, realm: 'example.com', method: 'tempo' }]\n * })\n * ```\n */\nexport function entitlementGateway(opts: EntitlementGatewayOptions): PaymentGateway {\n const { store, offer, exposeRemaining, ...challenge } = opts\n\n return {\n async handle(req: Request): Promise<GatewayResult> {\n const submitted = paymentPayload(req)\n\n if (submitted) {\n const ent = await store.lookup(submitted.value, req)\n const now = Math.floor(Date.now() / 1000)\n\n const usable =\n ent !== null &&\n (ent.expiresAt === undefined || ent.expiresAt > now) &&\n // `undefined` remaining means unmetered, which is usable. Zero is not.\n (ent.remaining === undefined || ent.remaining > 0)\n\n if (usable && ent) {\n await store.consume?.(ent, req)\n const left = ent.remaining === undefined ? undefined : ent.remaining - 1\n return {\n status: 'paid',\n ...(exposeRemaining && left !== undefined\n ? {\n receipt: (res: Response) => {\n const h = new Headers(res.headers)\n h.set('x-quota-remaining', String(Math.max(0, left)))\n return new Response(res.body, {\n status: res.status,\n statusText: res.statusText,\n headers: h\n })\n }\n }\n : {})\n }\n }\n }\n\n // No credential, expired, or exhausted — all get the same offer. Saying\n // which of the three it was would leak quota state to anyone probing.\n return {\n status: 'challenge',\n response: paymentRequired({\n ...challenge,\n body: `Payment required for training access.\\nOffer: ${offerDescription(offer)}\\n`,\n headers: {\n ...(challenge.headers ?? {}),\n 'x-bulk-offer': offerDescription(offer)\n }\n })\n }\n }\n }\n}\n\n/**\n * In-memory store. For tests and local development only — an edge runtime\n * gives each instance its own memory, so quota would neither be shared nor\n * survive a deploy. Use KV, Redis, or your database in production.\n */\nexport function memoryEntitlementStore(\n seed: Record<string, Entitlement> = {}\n): EntitlementStore & { entries(): Record<string, Entitlement> } {\n const map = new Map<string, Entitlement>(Object.entries(seed))\n return {\n lookup: (credential) => map.get(credential) ?? null,\n consume: (ent) => {\n if (ent.remaining !== undefined) {\n map.set(ent.id, { ...ent, remaining: ent.remaining - 1 })\n }\n },\n entries: () => Object.fromEntries(map)\n }\n}\n"]}