@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.
package/dist/index.js CHANGED
@@ -1,11 +1,3 @@
1
- var O=/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,G=/axios\/|curl\/|(?:^|[\s(])got(?:\/|[\s(])|\bcolly\b|Electron\/|node-fetch\/|python-requests\/|Go-http-client\/|okhttp\/|aiohttp\/|Deno\//i;function b(t){return t?O.test(t):false}function h(t){return t?G.test(t):false}function $(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 H(t){if(!t||typeof t!="string")return "Other";let e=t.match(/compatible;\s*([^/;\s]+)(?:\/[^\s;]*)?/i);return e&&e[1]?e[1].trim():t.trim().split("/")[0]?.trim().split(/\s+/)[0]?.trim()||"Other"}function R(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 r=t.headers.get("sec-ch-ua");r?r.toLowerCase().includes("headlesschrome")&&e.push("headless-chrome-hint"):e.push("missing-sec-ch-ua");let s=t.headers.get("accept")||"";(!s||s==="*/*")&&e.push("missing-or-bare-accept"),(t.headers.get("connection")||"").toLowerCase()==="close"&&e.push("connection-close");let d=e.length;return {score:d,signals:e,likely:d>=2}}function _(t){let e=$(t),n=b(t),i=h(t),r;return n?r="declared-crawler":i?r="coding-agent-hint":e==="Browser"?r="browser":r="other",{kind:r,label:e,isAiBot:n,codingAgentHint:i}}function w(t){let e=t.headers.get("user-agent")||"",n=_(e),i=R(t),r=n.kind,s=n.label;return r==="browser"&&i.likely&&(r="headless-likely",s="Headless"),{...n,kind:r,label:s,headless:i}}var x=class extends Error{constructor(e){super(e),this.name="HashSecretError";}};function P(){let t=globalThis.crypto;if(!t?.subtle)throw new x("Web Crypto is unavailable. agent-analytics requires Node >= 20, or any runtime exposing globalThis.crypto.subtle (Vercel Edge, Cloudflare Workers, Deno, browsers).");return t.subtle}var I=new Map;function F(t){let e=I.get(t);return e||(e=P().importKey("raw",new TextEncoder().encode(t),{name:"HMAC",hash:"SHA-256"},false,["sign"]),I.set(t,e)),e}async function S(t,e){if(typeof e!="string"||e.length===0)throw new x("hashId requires a non-empty secret");let n=await P().sign("HMAC",await F(e),new TextEncoder().encode(t)),i=new Uint8Array(n,0,8),r="";for(let s of i)r+=s.toString(16).padStart(2,"0");return "anon_"+r}function C(){let t=new Uint8Array(32);P(),globalThis.crypto.getRandomValues(t);let e="";for(let n of t)e+=n.toString(16).padStart(2,"0");return e}var E,M=false;function z(t){if(t)return t;let e=typeof process<"u"?process.env?.AGENT_ANALYTICS_ID_SECRET:void 0;return e||(E||(E=C(),M||(M=true,console.warn("[agent-analytics] No idSecret or AGENT_ANALYTICS_ID_SECRET set. Using a per-instance random secret: distinctIds will not correlate across instances or deploys."))),E)}async function V(t,e){let n=t.headers.get("user-agent")||"",i=e.onlyBots??false,r=e.skipBrowsers??false;if(!(i&&!b(n))&&!(r&&!b(n)&&!h(n)&&!R(t).likely))try{let s="/",d="";try{let B=new URL(t.url);s=B.pathname,d=B.origin;}catch{s=t.url||"/";}let l=e.origin??d,f=(t.headers.get("x-forwarded-for")||"").split(",")[0]?.trim()??"",o=t.headers.get("referer"),a=e.captureCountry&&(t.headers.get("x-vercel-ip-country")||t.headers.get("cf-ipcountry")||t.headers.get("x-country-code"))||null,p=e.captureGeo?W(t):null,u=w(t),g=e.verify?await e.verify(t):null,D=u.kind==="headless-likely"||u.kind==="browser",j=await S(`${f}:${n}`,z(e.idSecret)),U={event:e.eventName??"agent_visit",distinctId:j,timestamp:new Date().toISOString(),properties:{...e.properties,$process_person_profile:!1,$current_url:l?`${l}${s}`:s,path:s,method:t.method,...e.captureCountry?{country_code:a}:{},...p??{},...e.captureIp?{client_ip:f||null}:{},user_agent:n,is_ai_bot:u.isAiBot,bot_name:u.label,ua_category:u.kind,coding_agent_hint:u.codingAgentHint,...D?{headless_score:u.headless?.score??0,headless_likely:u.headless?.likely??!1}:{},...g?{bot_verified:g.verified,bot_verification:g.verdict,...g.reason?{bot_verification_reason:g.reason}:{}}:{},referer:o,source:e.source??null}};await e.analytics.capture(U);}catch(s){e.onError?.(s instanceof Error?s:new Error(String(s)));}}function W(t){let e=r=>{if(!r)return "";try{return decodeURIComponent(r)}catch{return r}},n=[["region",e(t.headers.get("x-vercel-ip-country-region"))],["city",e(t.headers.get("x-vercel-ip-city"))],["latitude",t.headers.get("x-vercel-ip-latitude")??""],["longitude",t.headers.get("x-vercel-ip-longitude")??""],["timezone",t.headers.get("x-vercel-ip-timezone")??""]],i={};for(let[r,s]of n)s&&(i[r]=s);return i}var m=class extends Error{status;body;constructor(e,n,i){super(e),this.name="CaptureTransportError",this.status=n,this.body=i;}};var K=/ChatGPT-User|OAI-SearchBot|Claude-User|Claude-SearchBot|Perplexity-User|claude-code|DuckAssistBot|MistralAI-User|Gemini-Deep-Research|Manus-User|YouBot/i,Y=/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,X=/bingbot|Googlebot|DuckDuckBot|YandexBot|Baiduspider|PetalBot|Sogou|Applebot(?!-Extended)/i;function N(t){let e=t??"";return e?K.test(e)?"retrieval":Y.test(e)?"training":X.test(e)?"search":h(e)?"tooling":"unknown":"unknown"}function T(t,e={}){let n=t.headers.get("user-agent")||"",r=w(t).label,s=N(n);if(e.allowList?.some(a=>a===r||n.toLowerCase().includes(a.toLowerCase())))return {action:"allow",intent:s,label:r,reason:"on allowList"};let l=e.verification??(e.verify?e.verify(t):void 0),c;return l&&(c=l.verdict,c==="spoofed")?{action:"block",intent:s,label:r,verification:c,reason:`${r} claimed but client IP is outside its published ranges`}:{action:s==="training"?e.onTraining??"meter":s==="retrieval"?e.onRetrieval??"allow":s==="search"?e.onSearch??"allow":s==="tooling"?e.onTooling??"allow":"allow",intent:s,label:r,...c?{verification:c}:{},reason:{retrieval:"a person is waiting on this answer",training:"bulk corpus collection",search:"search index crawler",tooling:"coding agent or HTTP client",unknown:"not a recognised agent"}[s]}}var J="PAYMENT-REQUIRED",Q="PAYMENT-SIGNATURE",Z="PAYMENT-RESPONSE",ee="WWW-Authenticate",te="Authorization";function A(t){return `"${t.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`}function q(t){let e=JSON.stringify(t),n=new TextEncoder().encode(e),i="";for(let r of n)i+=String.fromCharCode(r);return btoa(i)}function y(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(J,q({x402Version:n.x402Version??1,accepts:n.accepts}));}else {let i=[`id=${A(n.id)}`,`realm=${A(n.realm)}`,`method=${A(n.method)}`,...n.intent?[`intent=${A(n.intent)}`]:[],...n.request?[`request=${A(n.request)}`]:[]];e.append(ee,`Payment ${i.join(", ")}`);}for(let[n,i]of Object.entries(t.headers??{}))e.set(n,i);return new Response(t.body??`Payment required for training access.
2
- `,{status:402,headers:e})}function k(t){let e=t.headers.get(Q);if(e)return {protocol:"x402",value:e};let n=t.headers.get(te);if(n){let i=n.match(/^Payment\s+(.*)$/i);if(i?.[1])return {protocol:"mpp",value:i[1]}}return null}function ne(t){return k(t)!==null}function re(t,e,n={}){let i=new Headers(t.headers);return i.set(n.header??Z,q(e)),new Response(t.body,{status:t.status,statusText:t.statusText,headers:i})}function ie(t,e){return t.action==="block"?new Response(`Forbidden: agent identity could not be verified.
3
- `,{status:403}):t.action==="charge"?y({body:`Payment required: ${t.label} \u2014 ${t.reason}.
4
- `,...e}):null}function oe(t){return {async handle(e){let n=await t(e);return n.status===402?{status:"challenge",response:n.challenge}:{status:"paid",...n.withReceipt?{receipt:i=>n.withReceipt(i)}:{}}}}}function se(t){let{settle:e,receipt:n,...i}=t;return {async handle(r){let s=r.headers.get("PAYMENT-SIGNATURE");return s&&await e(s,r)?{status:"paid",...n?{receipt:n}:{}}:{status:"challenge",response:y(i)}}}}async function ae(t,e){let{gateway:n,onDecision:i,verify:r,meter:s,...d}=e,l=r?await r(t):void 0,c=T(t,{...d,...l?{verification:l}:{}});i?.(c);let f=a=>a;if(c.action==="block")return {decision:c,response:new Response(`Forbidden: agent identity could not be verified.
5
- `,{status:403}),decorate:f};if(c.action==="meter"){try{let a=t.url,p=t.method;try{a=new URL(t.url).pathname;}catch{}await s?.record({decision:c,units:1,path:a,method:p});}catch{}return {decision:c,response:null,decorate:f}}if(c.action!=="charge")return {decision:c,response:null,decorate:f};let o=await n.handle(t);return o.status==="challenge"?{decision:c,response:o.response,decorate:f}:{decision:c,response:null,decorate:o.receipt??f}}function L(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 le(t){let{store:e,offer:n,exposeRemaining:i,...r}=t;return {async handle(s){let d=k(s);if(d){let l=await e.lookup(d.value,s),c=Math.floor(Date.now()/1e3);if(l!==null&&(l.expiresAt===void 0||l.expiresAt>c)&&(l.remaining===void 0||l.remaining>0)&&l){await e.consume?.(l,s);let o=l.remaining===void 0?void 0:l.remaining-1;return {status:"paid",...i&&o!==void 0?{receipt:a=>{let p=new Headers(a.headers);return p.set("x-quota-remaining",String(Math.max(0,o))),new Response(a.body,{status:a.status,statusText:a.statusText,headers:p})}}:{}}}}return {status:"challenge",response:y({...r,body:`Payment required for training access.
6
- Offer: ${L(n)}
7
- `,headers:{...r.headers??{},"x-bulk-offer":L(n)}})}}}}function ce(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)}}function ue(t){return `'${JSON.stringify(t).replace(/'/g,"'\\''")}'`}function de(t){let e=[`vercel firewall rules add ${JSON.stringify(t.name)}`];if(t.groups.forEach((n,i)=>{i>0&&e.push(" --or");for(let r of n)e.push(` --condition ${ue(r)}`);}),e.push(` --action ${t.action}`),t.action==="rate_limit"&&t.rateLimit){e.push(` --rate-limit-window ${t.rateLimit.window}`),e.push(` --rate-limit-requests ${t.rateLimit.requests}`),e.push(` --rate-limit-action ${t.rateLimit.action}`);for(let n of t.rateLimit.keys)e.push(` --rate-limit-keys ${n}`);}return e.push(" --yes"),e.join(` \\
8
- `)}function pe(t){return {name:t.name,conditionGroup:t.groups.map(e=>({conditions:e})),action:{mitigate:{action:t.action}}}}function v(t){return {...t,cli:de(t),json:pe(t)}}function fe(t){if(!t.length)return 0;let e=[...t].sort((i,r)=>i-r),n=Math.floor(e.length/2);return e.length%2?e[n]:(e[n-1]+e[n])/2}function ge(t,e={}){let n=[];if(!e.omitProtectiveBypass){let o=t.filter(u=>u.intent==="retrieval"||u.intent==="search"),a=o.reduce((u,g)=>u+g.requests,0),p=[...new Set(o.map(u=>u.botName))];n.push(v({name:"Allow retrieval and search agents",rationale:"Retrieval agents and search crawlers must never be caught by the rules below \u2014 they bring readers and rankings.",evidence:a?`${a.toLocaleString("en-US")} observed requests across ${p.length} vendors (${p.slice(0,6).join(", ")})`:"no retrieval or search traffic observed yet; installed pre-emptively",groups:[[{type:"user_agent",op:"inc",value:["ChatGPT-User","OAI-SearchBot","Claude-User","Claude-SearchBot","Perplexity-User","Googlebot","bingbot","DuckDuckBot","Applebot"]}]],action:"bypass",eventual:"bypass",risk:"low",caveat:"Place this rule first (`vercel firewall rules reorder ... --first`). A user-agent allowlist is spoofable, so pair with verification in middleware rather than relying on it for security \u2014 its job here is to stop your own rules misfiring."}));}let i=t.filter(o=>o.verification==="spoofed"),r=[...new Set(i.map(o=>o.ip).filter(o=>!!o))];if(r.length){let o=i.reduce((p,u)=>p+u.requests,0),a=[...new Set(i.map(p=>p.botName))];n.push(v({name:"Deny impersonated crawler identities",rationale:"These addresses claimed a crawler identity that failed verification against the vendor\u2019s published ranges or signature.",evidence:`${o.toLocaleString("en-US")} requests from ${r.length} address${r.length===1?"":"es"} impersonating ${a.join(", ")}`,groups:[[{type:"ip_address",op:"inc",value:r}]],action:"log",eventual:"deny",risk:"low",caveat:"Verification failure is strong evidence, but confirm your edge controls x-forwarded-for before enforcing \u2014 behind a proxy that forwards a client-supplied header the verdict is worthless."}));}let s=t.filter(o=>o.ip&&o.verification!=="verified"),d=e.abuseThreshold??Math.max(500,Math.round(fe(s.map(o=>o.requests))*10)),l=s.filter(o=>o.requests>=d).sort((o,a)=>a.requests-o.requests).slice(0,50);if(l.length){let o=l.filter(a=>(a.distinctPaths??0)>100);n.push(v({name:"Rate limit high-volume unverified addresses",rationale:"A single address making orders of magnitude more requests than the median, with no verified identity.",evidence:`${l.length} address${l.length===1?"":"es"} above ${d.toLocaleString("en-US")} requests`+(o.length?`; ${o.length} swept >100 distinct paths, which reads as a scrape rather than a reader`:""),groups:[[{type:"ip_address",op:"inc",value:l.map(a=>a.ip)}]],action:"log",eventual:"rate_limit",rateLimit:{window:60,requests:60,action:"rate_limit",keys:["ip"]},risk:"medium",caveat:"Shared egress means one address can front many real users \u2014 a corporate NAT, a mobile carrier, or a VPN. Review the dashboard before enforcing."}));}let c=t.filter(o=>o.intent==="training");if(c.length){let o=c.reduce((u,g)=>u+g.requests,0),a=[...new Set(c.map(u=>u.botName))],p=e.trainingBudget??{window:3600,requests:600};n.push(v({name:"Rate limit training crawlers",rationale:"Bound what bulk corpus collection costs you without removing yourself from training sets.",evidence:`${o.toLocaleString("en-US")} training requests from ${a.length} vendors (${a.slice(0,6).join(", ")})`,groups:[[{type:"user_agent",op:"inc",value:["GPTBot","ClaudeBot","CCBot","Bytespider","Amazonbot","meta-externalagent"]}]],action:"log",eventual:"rate_limit",rateLimit:{window:p.window,requests:p.requests,action:"rate_limit",keys:["ip"]},risk:"medium",caveat:"Denying these removes you from future training sets, which may be exactly wrong for discoverability. Rate limit rather than deny unless you have decided otherwise. Note Vercel counters are per region, so N regions can collectively exceed the limit by ~Nx."}));}let f=[...new Set(t.filter(o=>o.asn!==void 0&&/Mozilla|Chrome|Safari/i.test(o.userAgent)).filter(o=>o.verification!=="verified").map(o=>o.asn))];if(f.length){let a=t.filter(p=>p.asn!==void 0&&f.includes(p.asn)).reduce((p,u)=>p+u.requests,0);n.push(v({name:"Challenge browser user agents from datacenter networks",rationale:"A browser user agent arriving from a hosting network is automation wearing a costume \u2014 real browsers come from consumer ISPs.",evidence:`${a.toLocaleString("en-US")} requests across ${f.length} datacenter AS numbers`,groups:[[{type:"geo_as_number",op:"inc",value:f},{type:"user_agent",op:"sub",value:"Mozilla"}]],action:"log",eventual:"challenge",risk:"high",caveat:"Highest false-positive risk here. Corporate VPNs, privacy relays and some mobile carriers egress from hosting ASNs, and a challenge page breaks API clients and link unfurlers outright. Keep this in log mode for a full week before considering enforcement."}));}return n}function me(t){let e=["#!/usr/bin/env bash","# Vercel WAF proposals generated from observed agent traffic.","#","# Every rule starts in LOG mode and blocks nothing. Vercel stages rule","# changes as drafts, so nothing is live until you run:","#","# vercel firewall diff # review","# vercel firewall publish --yes # go live","#","# Review each rule in the dashboard before promoting it to its eventual","# action. Rules evaluate top to bottom, so keep the bypass rule first.","set -euo pipefail",""];return t.forEach((n,i)=>{e.push(`# ${i+1}. ${n.name}`),e.push(`# why: ${n.rationale}`),e.push(`# evidence: ${n.evidence}`),e.push(`# risk: ${n.risk} \u2014 eventual action: ${n.eventual}`),n.caveat&&e.push(`# caveat: ${n.caveat}`),e.push(n.cli),e.push("");}),t.length&&(e.push("# Keep the protective allow rule at the top of the evaluation order."),e.push(`vercel firewall rules reorder ${JSON.stringify(t[0].name)} --first --yes`),e.push(""),e.push("vercel firewall diff"),e.push('echo "Review above, then: vercel firewall publish --yes"')),e.join(`
9
- `)}function he(t){let e=t.host??"https://us.i.posthog.com",n=(/^https?:\/\//.test(e)?e:`https://${e}`).replace(/\/$/,""),i=(t.path??"/i/v0/e/").replace(/^(?!\/)/,"/"),r=`${n}${i}`,s=t.fetchImpl??fetch;return {async capture(d){let l=d.properties.user_agent,c=d.properties.client_ip,f={api_key:t.apiKey,event:d.event,distinct_id:d.distinctId,timestamp:d.timestamp,properties:{...d.properties,...typeof l=="string"&&l?{$raw_user_agent:l}:{},...typeof c=="string"&&c?{$ip:c}:{}}},o=await s(r,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(f),keepalive:true,signal:AbortSignal.timeout(t.timeoutMs??3e3)});if(!o.ok)throw new m(`PostHog capture failed: ${o.status} ${o.statusText}`,o.status,await o.text().catch(()=>{}))}}}function ye(t){let e=t.fetchImpl??fetch,n=t.transform??(i=>i);return {async capture(i){let r=await e(t.url,{method:"POST",headers:{"Content-Type":"application/json",...t.headers??{}},body:JSON.stringify(n(i)),keepalive:true,signal:AbortSignal.timeout(t.timeoutMs??3e3)});if(!r.ok)throw new m(`Webhook capture failed: ${r.status} ${r.statusText}`,r.status,await r.text().catch(()=>{}))}}}function be(t){return {capture:t}}
10
- export{O as AI_BOT_PATTERN,m as CaptureTransportError,G as HTTP_CLIENT_PATTERN,x as HashSecretError,N as agentIntent,T as agentPolicy,_ as classifyAgent,w as classifyRequest,be as customAnalytics,R as detectHeadless,le as entitlementGateway,me as firewallScript,H as firstUserAgentProduct,ne as hasPaymentPayload,S as hashId,b as isAiBot,h as isHttpClient,ce as memoryEntitlementStore,oe as mppxGateway,$ as parseBotName,ae as paymentGate,k as paymentPayload,y as paymentRequired,he as posthogAnalytics,C as randomSecret,ge as recommendFirewallRules,ie as respondToDecision,V as trackVisit,ye as webhookAnalytics,re as withSettlement,se as x402Gateway};//# sourceMappingURL=index.js.map
1
+ var v=/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,S=/axios\/|curl\/|(?:^|[\s(])got(?:\/|[\s(])|\bcolly\b|Electron\/|node-fetch\/|python-requests\/|Go-http-client\/|okhttp\/|aiohttp\/|Deno\//i;function h(t){return t?v.test(t):false}function p(t){return t?S.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 H(t){if(!t||typeof t!="string")return "Other";let e=t.match(/compatible;\s*([^/;\s]+)(?:\/[^\s;]*)?/i);return e&&e[1]?e[1].trim():t.trim().split("/")[0]?.trim().split(/\s+/)[0]?.trim()||"Other"}function A(t){let e=[],r=(t.headers.get("user-agent")||"").toLowerCase();if(!(r.includes("mozilla")||r.includes("chrome")||r.includes("safari")||r.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 n=t.headers.get("sec-ch-ua");n?n.toLowerCase().includes("headlesschrome")&&e.push("headless-chrome-hint"):e.push("missing-sec-ch-ua");let i=t.headers.get("accept")||"";(!i||i==="*/*")&&e.push("missing-or-bare-accept"),(t.headers.get("connection")||"").toLowerCase()==="close"&&e.push("connection-close");let s=e.length;return {score:s,signals:e,likely:s>=2}}function P(t){let e=T(t),r=h(t),o=p(t),n;return r?n="declared-crawler":o?n="coding-agent-hint":e==="Browser"?n="browser":n="other",{kind:n,label:e,isAiBot:r,codingAgentHint:o}}function m(t){let e=t.headers.get("user-agent")||"",r=P(e),o=A(t),n=r.kind,i=r.label;return n==="browser"&&o.likely&&(n="headless-likely",i="Headless"),{...r,kind:n,label:i,headless:o}}var y=class extends Error{constructor(e){super(e),this.name="HashSecretError";}};function w(){let t=globalThis.crypto;if(!t?.subtle)throw new y("Web Crypto is unavailable. agent-analytics requires Node >= 20, or any runtime exposing globalThis.crypto.subtle (Vercel Edge, Cloudflare Workers, Deno, browsers).");return t.subtle}var E=new Map;function O(t){let e=E.get(t);return e||(e=w().importKey("raw",new TextEncoder().encode(t),{name:"HMAC",hash:"SHA-256"},false,["sign"]),E.set(t,e)),e}async function x(t,e){if(typeof e!="string"||e.length===0)throw new y("hashId requires a non-empty secret");let r=await w().sign("HMAC",await O(e),new TextEncoder().encode(t)),o=new Uint8Array(r,0,8),n="";for(let i of o)n+=i.toString(16).padStart(2,"0");return "anon_"+n}function k(){let t=new Uint8Array(32);w(),globalThis.crypto.getRandomValues(t);let e="";for(let r of t)e+=r.toString(16).padStart(2,"0");return e}var C,I=false;function N(t){if(t)return t;let e=typeof process<"u"?process.env?.AGENT_ANALYTICS_ID_SECRET:void 0;return e||(C||(C=k(),I||(I=true,console.warn("[agent-analytics] No idSecret or AGENT_ANALYTICS_ID_SECRET set. Using a per-instance random secret: distinctIds will not correlate across instances or deploys."))),C)}async function z(t,e){let r=t.headers.get("user-agent")||"",o=e.onlyBots??false,n=e.skipBrowsers??false;if(!(o&&!h(r))&&!(n&&!h(r)&&!p(r)&&!A(t).likely))try{let i="/",s="";try{let B=new URL(t.url);i=B.pathname,s=B.origin;}catch{i=t.url||"/";}let c=e.origin??s,f=(t.headers.get("x-forwarded-for")||"").split(",")[0]?.trim()??"",l=t.headers.get("referer"),b=e.captureCountry&&(t.headers.get("x-vercel-ip-country")||t.headers.get("cf-ipcountry")||t.headers.get("x-country-code"))||null,_=e.captureGeo?L(t):null,u=m(t),g=e.verify?await e.verify(t):null,D=u.kind==="headless-likely"||u.kind==="browser",M=await x(`${f}:${r}`,N(e.idSecret)),G={event:e.eventName??"agent_visit",distinctId:M,timestamp:new Date().toISOString(),properties:{...e.properties,$process_person_profile:!1,$current_url:c?`${c}${i}`:i,path:i,method:t.method,...e.captureCountry?{country_code:b}:{},..._??{},...e.captureIp?{client_ip:f||null}:{},user_agent:r,is_ai_bot:u.isAiBot,bot_name:u.label,ua_category:u.kind,coding_agent_hint:u.codingAgentHint,...D?{headless_score:u.headless?.score??0,headless_likely:u.headless?.likely??!1}:{},...g?{bot_verified:g.verified,bot_verification:g.verdict,...g.reason?{bot_verification_reason:g.reason}:{}}:{},referer:l,source:e.source??null}};await e.analytics.capture(G);}catch(i){e.onError?.(i instanceof Error?i:new Error(String(i)));}}function L(t){let e=n=>{if(!n)return "";try{return decodeURIComponent(n)}catch{return n}},r=[["region",e(t.headers.get("x-vercel-ip-country-region"))],["city",e(t.headers.get("x-vercel-ip-city"))],["latitude",t.headers.get("x-vercel-ip-latitude")??""],["longitude",t.headers.get("x-vercel-ip-longitude")??""],["timezone",t.headers.get("x-vercel-ip-timezone")??""]],o={};for(let[n,i]of r)i&&(o[n]=i);return o}var d=class extends Error{status;body;constructor(e,r,o){super(e),this.name="CaptureTransportError",this.status=r,this.body=o;}};var U=/ChatGPT-User|OAI-SearchBot|Claude-User|Claude-SearchBot|Perplexity-User|claude-code|DuckAssistBot|MistralAI-User|Gemini-Deep-Research|Manus-User|YouBot/i,$=/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,j=/bingbot|Googlebot|DuckDuckBot|YandexBot|Baiduspider|PetalBot|Sogou|PerplexityBot|Bravebot|Applebot(?!-Extended)/i,V=/facebookexternalhit|Twitterbot|LinkedInBot|Slackbot|Discordbot|TelegramBot|WhatsApp|redditbot|Pinterest|SkypeUriPreview|Iframely|Embedly|vkShare|Mastodon|Bluesky/i;function R(t){let e=t??"";return e?U.test(e)?"retrieval":$.test(e)?"training":j.test(e)?"search":V.test(e)?"preview":p(e)?"tooling":"unknown":"unknown"}function W(t,e={}){let r=t.headers.get("user-agent")||"",n=m(t).label,i=R(r);if(e.allowList?.some(b=>b===n||r.toLowerCase().includes(b.toLowerCase())))return {action:"allow",intent:i,label:n,reason:"on allowList"};let c=e.verification??(e.verify?e.verify(t):void 0),a;return c&&(a=c.verdict,a==="spoofed")?{action:"block",intent:i,label:n,verification:a,reason:`${n} claimed but client IP is outside its published ranges`}:{action:i==="training"?e.onTraining??"meter":i==="retrieval"?e.onRetrieval??"allow":i==="search"?e.onSearch??"allow":i==="preview"?e.onPreview??"allow":i==="tooling"?e.onTooling??"allow":"allow",intent:i,label:n,...a?{verification:a}:{},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"}[i]}}function K(t){let e=t.host??"https://us.i.posthog.com",r=(/^https?:\/\//.test(e)?e:`https://${e}`).replace(/\/$/,""),o=(t.path??"/i/v0/e/").replace(/^(?!\/)/,"/"),n=`${r}${o}`,i=t.fetchImpl??fetch;return {async capture(s){let c=s.properties.user_agent,a=s.properties.client_ip,f={api_key:t.apiKey,event:s.event,distinct_id:s.distinctId,timestamp:s.timestamp,properties:{...s.properties,...typeof c=="string"&&c?{$raw_user_agent:c}:{},...typeof a=="string"&&a?{$ip:a}:{}}},l=await i(n,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(f),keepalive:true,signal:AbortSignal.timeout(t.timeoutMs??3e3)});if(!l.ok)throw new d(`PostHog capture failed: ${l.status} ${l.statusText}`,l.status,await l.text().catch(()=>{}))}}}function F(t){let e=t.fetchImpl??fetch,r=t.transform??(o=>o);return {async capture(o){let n=await e(t.url,{method:"POST",headers:{"Content-Type":"application/json",...t.headers??{}},body:JSON.stringify(r(o)),keepalive:true,signal:AbortSignal.timeout(t.timeoutMs??3e3)});if(!n.ok)throw new d(`Webhook capture failed: ${n.status} ${n.statusText}`,n.status,await n.text().catch(()=>{}))}}}function Y(t){return {capture:t}}
2
+ export{v as AI_BOT_PATTERN,d as CaptureTransportError,S as HTTP_CLIENT_PATTERN,y as HashSecretError,R as agentIntent,W as agentPolicy,P as classifyAgent,m as classifyRequest,Y as customAnalytics,A as detectHeadless,H as firstUserAgentProduct,x as hashId,h as isAiBot,p as isHttpClient,T as parseBotName,K as posthogAnalytics,k as randomSecret,z as trackVisit,F as webhookAnalytics};//# sourceMappingURL=index.js.map
11
3
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/bots.ts","../src/hash.ts","../src/track.ts","../src/errors.ts","../src/policy.ts","../src/payments.ts","../src/gateway.ts","../src/entitlement.ts","../src/firewall.ts","../src/adapters/posthog.ts","../src/adapters/webhook.ts","../src/adapters/custom.ts"],"names":["AI_BOT_PATTERN","HTTP_CLIENT_PATTERN","isAiBot","userAgent","isHttpClient","parseBotName","s","firstUserAgentProduct","compatibleMatch","detectHeadless","req","signals","ua","secChUa","accept","score","classifyAgent","label","aiBot","httpClient","kind","classifyRequest","base","headless","HashSecretError","message","subtle","c","KEYS","keyFor","secret","k","hashId","input","sig","bytes","out","b","randomSecret","x","fallbackSecret","warnedNoSecret","resolveSecret","explicit","fromEnv","trackVisit","opts","onlyBots","skipBrowsers","pathname","originFromUrl","url","origin","ip","referer","country","geo","extractGeo","classification","verification","headlessMeaningful","distinctId","event","err","decode","v","fields","CaptureTransportError","status","body","RETRIEVAL","TRAINING","SEARCH","agentIntent","agentPolicy","intent","entry","resolved","X402_CHALLENGE","X402_SIGNATURE","X402_SETTLEMENT","MPP_CHALLENGE","MPP_CREDENTIAL","quoted","b64","json","text","bin","paymentRequired","headers","params","paymentPayload","x402","auth","m","hasPaymentPayload","withSettlement","res","settlement","respondToDecision","decision","mppxGateway","handler","x402Gateway","settle","receipt","challenge","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","shellQuote","toCli","r","parts","group","toJson","conditions","finish","median","ns","a","mid","recommendFirewallRules","observations","wanted","o","requests","n","names","spoofed","spoofedIps","vendors","perIp","threshold","heavy","sweeping","training","budget","headlessAsns","firewallScript","recommendations","lines","posthogAnalytics","config","hostRaw","endpoint","fetchImpl","payload","webhookAnalytics","transform","e","customAnalytics","capture"],"mappings":"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","file":"index.js","sourcesContent":["/**\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","/**\n * Keyed, non-reversible anonymous identifiers.\n *\n * The previous implementation was an unsalted 32-bit djb2 over `ip:userAgent`.\n * Because the user agent is emitted in plaintext on the same event, an attacker\n * held half the preimage and only had to search the IPv4 space — recovering a\n * residential IP took 75 seconds single-threaded. That is pseudonymisation, not\n * anonymisation, and it does not survive GDPR Recital 26.\n *\n * This uses HMAC-SHA-256 with a caller-supplied secret, truncated to 64 bits.\n * Web Crypto is available on Vercel Edge, Cloudflare Workers, Deno and Node 18+.\n */\n\n/** Thrown when a secret is missing or unusable. */\nexport class HashSecretError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'HashSecretError'\n }\n}\n\n/**\n * Web Crypto, or a clear error explaining why it is missing.\n *\n * `globalThis.crypto` is present by default from Node 19; on Node 18 it sat\n * behind `--experimental-global-webcrypto`. We require Node >= 20 rather than\n * shipping a `node:crypto` fallback, because a static import of a Node builtin\n * breaks bundling for the edge runtimes this library primarily targets — and\n * Node 18 reached end of life in April 2025.\n */\nfunction subtle(): SubtleCrypto {\n const c = globalThis.crypto\n if (!c?.subtle) {\n throw new HashSecretError(\n 'Web Crypto is unavailable. agent-analytics requires Node >= 20, or any ' +\n 'runtime exposing globalThis.crypto.subtle (Vercel Edge, Cloudflare ' +\n 'Workers, Deno, browsers).'\n )\n }\n return c.subtle\n}\n\n// Importing a CryptoKey costs more than the signature itself, so keep one per\n// secret. Bounded by however many secrets a process configures — realistically\n// one.\nconst KEYS = new Map<string, Promise<CryptoKey>>()\n\nfunction keyFor(secret: string): Promise<CryptoKey> {\n let k = KEYS.get(secret)\n if (!k) {\n k = subtle().importKey(\n 'raw',\n new TextEncoder().encode(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign']\n )\n KEYS.set(secret, k)\n }\n return k\n}\n\n/**\n * Hash `input` under `secret`, returning `anon_` followed by 16 hex characters\n * (64 bits — collision-free well past any realistic distinct-visitor count).\n *\n * The secret must be stable across instances for identifiers to be comparable\n * over time, and secret from anyone who can read your events: publishing it\n * makes the identifier exactly as reversible as the old implementation was.\n * Rotating it deliberately breaks continuity, which is correct behaviour for a\n * privacy-preserving id.\n */\nexport async function hashId(input: string, secret: string): Promise<string> {\n if (typeof secret !== 'string' || secret.length === 0) {\n throw new HashSecretError('hashId requires a non-empty secret')\n }\n const sig = await subtle().sign('HMAC', await keyFor(secret), new TextEncoder().encode(input))\n const bytes = new Uint8Array(sig, 0, 8)\n let out = ''\n for (const b of bytes) out += b.toString(16).padStart(2, '0')\n return 'anon_' + out\n}\n\n/**\n * Generate a random secret. Used as the default when none is configured, so the\n * privacy-preserving path is the one you get by doing nothing. Identifiers are\n * then only stable within a single instance's lifetime — set a real secret when\n * you need them comparable across instances and over time.\n */\nexport function randomSecret(): string {\n const b = new Uint8Array(32)\n subtle() // surface the same clear error if Web Crypto is missing\n globalThis.crypto.getRandomValues(b)\n let out = ''\n for (const x of b) out += x.toString(16).padStart(2, '0')\n return out\n}\n","import { classifyRequest, detectHeadless, isAiBot, isHttpClient } from './bots.js'\nimport { hashId, randomSecret } from './hash.js'\nimport type { TrackVisitOptions } from './types.js'\n\n/**\n * Fallback secret, generated once per instance. Keeps the default path\n * privacy-preserving rather than making callers opt in to safety, at the cost\n * of identifiers that only correlate within one instance's lifetime.\n */\nlet fallbackSecret: string | undefined\nlet warnedNoSecret = false\n\nfunction resolveSecret(explicit: string | undefined): string {\n if (explicit) return explicit\n const fromEnv =\n typeof process !== 'undefined' ? process.env?.AGENT_ANALYTICS_ID_SECRET : undefined\n if (fromEnv) return fromEnv\n if (!fallbackSecret) {\n fallbackSecret = randomSecret()\n if (!warnedNoSecret) {\n warnedNoSecret = true\n // Once per instance, not once per request.\n console.warn(\n '[agent-analytics] No idSecret or AGENT_ANALYTICS_ID_SECRET set. ' +\n 'Using a per-instance random secret: distinctIds will not correlate ' +\n 'across instances or deploys.'\n )\n }\n }\n return fallbackSecret\n}\n\n/**\n * Capture an event describing the incoming request. Fire-and-forget: awaits the\n * adapter but routes errors to {@link TrackVisitOptions.onError} rather than\n * letting them reach the response path. Callers typically don't await it.\n *\n * By default, captures every request so coding-agent traffic (axios, curl,\n * Electron, …) shows up alongside branded crawlers. Set `onlyBots: true` to\n * restrict capture to UAs matching {@link AI_BOT_PATTERN}.\n */\nexport async function trackVisit(req: Request, opts: TrackVisitOptions): Promise<void> {\n const userAgent = req.headers.get('user-agent') || ''\n\n const onlyBots = opts.onlyBots ?? false\n const skipBrowsers = opts.skipBrowsers ?? false\n if (onlyBots && !isAiBot(userAgent)) return\n if (skipBrowsers && !isAiBot(userAgent) && !isHttpClient(userAgent)) {\n // Not a declared bot or HTTP client — check headless heuristics.\n // Playwright-based agents (Aider, OpenCode) will pass if they're missing\n // standard browser headers. Real browsers get skipped.\n if (!detectHeadless(req).likely) return\n }\n\n try {\n let pathname = '/'\n let originFromUrl = ''\n try {\n const url = new URL(req.url)\n pathname = url.pathname\n originFromUrl = url.origin\n } catch {\n // Some runtimes hand us a relative URL; fall back to the raw string.\n pathname = req.url || '/'\n }\n const origin = opts.origin ?? originFromUrl\n\n const forwardedFor = req.headers.get('x-forwarded-for') || ''\n const ip = forwardedFor.split(',')[0]?.trim() ?? ''\n const referer = req.headers.get('referer')\n const country = opts.captureCountry\n ? req.headers.get('x-vercel-ip-country') ||\n req.headers.get('cf-ipcountry') ||\n req.headers.get('x-country-code') ||\n null\n : null\n const geo = opts.captureGeo ? extractGeo(req) : null\n const classification = classifyRequest(req)\n\n // Verification is injected rather than imported, so the published IP range\n // tables only reach bundles that actually use them. Import `verifyRequest`\n // from `@apideck/agent-analytics/verify` and pass it as `verify`.\n // May be async: Web Bot Auth fetches a signer's key directory on first\n // sight of that origin, then serves from cache.\n const verification = opts.verify ? await opts.verify(req) : null\n\n // Headless scoring only discriminates for browser-shaped UAs. On a declared\n // crawler or an HTTP client it fires on nearly everything — measured true on\n // 99% of captured events — so it reads as signal when it is noise. Omitted\n // rather than emitted as a near-constant.\n const headlessMeaningful =\n classification.kind === 'headless-likely' || classification.kind === 'browser'\n\n const distinctId = await hashId(`${ip}:${userAgent}`, resolveSecret(opts.idSecret))\n\n const event = {\n event: opts.eventName ?? 'agent_visit',\n distinctId,\n timestamp: new Date().toISOString(),\n properties: {\n // Caller properties are spread FIRST so library-computed fields always\n // win. Spreading them last let a colliding key silently overwrite\n // bot_name or is_ai_bot — corrupting the very classification they were\n // meant to annotate.\n ...opts.properties,\n $process_person_profile: false,\n $current_url: origin ? `${origin}${pathname}` : pathname,\n path: pathname,\n method: req.method,\n ...(opts.captureCountry ? { country_code: country } : {}),\n ...(geo ?? {}),\n ...(opts.captureIp ? { client_ip: ip || null } : {}),\n user_agent: userAgent,\n is_ai_bot: classification.isAiBot,\n bot_name: classification.label,\n ua_category: classification.kind,\n coding_agent_hint: classification.codingAgentHint,\n ...(headlessMeaningful\n ? {\n headless_score: classification.headless?.score ?? 0,\n headless_likely: classification.headless?.likely ?? false\n }\n : {}),\n ...(verification\n ? {\n bot_verified: verification.verified,\n bot_verification: verification.verdict,\n ...(verification.reason ? { bot_verification_reason: verification.reason } : {})\n }\n : {}),\n referer,\n source: opts.source ?? null\n }\n }\n\n await opts.analytics.capture(event)\n } catch (err) {\n // Analytics must never affect the response — but silence is how a wrong API\n // key goes unnoticed for a week, so surface it when the caller asks.\n opts.onError?.(err instanceof Error ? err : new Error(String(err)))\n }\n}\n\n// Vercel edge URL-encodes city/region (e.g. `San%20Francisco`); decode so\n// downstream consumers don't have to. Numeric fields (lat/lng) and timezone\n// pass through untouched. Headers without a value are dropped rather than\n// emitted as empty strings.\nfunction extractGeo(req: Request): Record<string, string> {\n const decode = (v: string | null) => {\n if (!v) return ''\n try {\n return decodeURIComponent(v)\n } catch {\n return v\n }\n }\n const fields: Array<[string, string]> = [\n ['region', decode(req.headers.get('x-vercel-ip-country-region'))],\n ['city', decode(req.headers.get('x-vercel-ip-city'))],\n ['latitude', req.headers.get('x-vercel-ip-latitude') ?? ''],\n ['longitude', req.headers.get('x-vercel-ip-longitude') ?? ''],\n ['timezone', req.headers.get('x-vercel-ip-timezone') ?? '']\n ]\n const out: Record<string, string> = {}\n for (const [k, v] of fields) if (v) out[k] = v\n return out\n}\n","/** Thrown when the analytics backend rejects, errors, or times out a capture. */\nexport class CaptureTransportError extends Error {\n readonly status: number | undefined\n readonly body: string | undefined\n constructor(message: string, status?: number, body?: string) {\n super(message)\n this.name = 'CaptureTransportError'\n this.status = status\n this.body = body\n }\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'` — classic index crawlers. Blocking these costs you SEO.\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' | '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/** Classic search indexers — blocking these costs you organic traffic. */\nconst SEARCH = /bingbot|Googlebot|DuckDuckBot|YandexBot|Baiduspider|PetalBot|Sogou|Applebot(?!-Extended)/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 /** 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 // 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 === '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 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 * 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 * 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","/**\n * Recommend Vercel WAF rules from observed agent traffic.\n *\n * This generates *proposals*, never live changes. Every recommendation comes out\n * with `action: 'log'`, because a firewall rule's blast radius is unpredictable\n * until real traffic hits it and a bad `deny` takes out real users or your SEO.\n * Vercel's own guidance is log → review → preview → production; the `eventual`\n * field records where a rule is meant to end up, and `cli` emits the command for\n * the *current* stage only.\n *\n * Two hard rules, both from measurement rather than taste:\n *\n * 1. Retrieval agents and search crawlers are never proposed for blocking.\n * 60% of AI traffic on one production site is retrieval — a person asked a\n * question and an assistant went to read the page. Blocking that is\n * blocking your own distribution. The recommender emits a `bypass` rule to\n * protect them *first*, so later rules cannot catch them.\n *\n * 2. Training crawlers get rate limits, not denials, by default. The point is\n * to bound cost, not to disappear from corpora.\n *\n * Only abuse gets a denial: an identity that failed cryptographic or IP\n * verification, or a single address behaving like a scraper.\n */\n\nimport type { AgentIntent } from './policy.js'\n\n/** A Vercel WAF condition. Mirrors the CLI's `--condition` JSON. */\nexport interface FirewallCondition {\n type:\n | 'user_agent'\n | 'ip_address'\n | 'geo_as_number'\n | 'geo_country'\n | 'path'\n | 'method'\n | 'environment'\n | 'ja4_digest'\n op: 'eq' | 'neq' | 'sub' | 'pre' | 'suf' | 're' | 'inc' | 'ninc' | 'gt' | 'gte'\n value?: string | number | Array<string | number>\n key?: string\n neg?: boolean\n}\n\nexport type FirewallAction = 'log' | 'deny' | 'challenge' | 'bypass' | 'rate_limit'\n\nexport interface RateLimitSpec {\n /** Seconds, 10–3600. */\n window: number\n /** Max requests per window. */\n requests: number\n /** What happens on breach. */\n action: 'rate_limit' | 'deny' | 'challenge' | 'log'\n keys: Array<'ip' | 'ja4'>\n}\n\nexport interface FirewallRecommendation {\n name: string\n /** Why this rule is proposed, in one sentence. */\n rationale: string\n /** The measurement behind it. Never propose a rule without evidence. */\n evidence: string\n /** OR of ANDs: outer array is groups, inner is conditions within a group. */\n groups: FirewallCondition[][]\n /** Always `'log'` or `'bypass'` — see the module note. */\n action: FirewallAction\n /** Where this rule is intended to end up after review. */\n eventual: FirewallAction\n rateLimit?: RateLimitSpec\n /** How likely this is to catch traffic you wanted. */\n risk: 'low' | 'medium' | 'high'\n /** What could go wrong, when it is not obvious. */\n caveat?: string\n /** Ready-to-run CLI for the *current* stage. */\n cli: string\n /** Equivalent `--json` payload. */\n json: unknown\n}\n\n/** One aggregated slice of observed traffic. */\nexport interface TrafficObservation {\n userAgent: string\n botName: string\n intent: AgentIntent\n requests: number\n ip?: string\n /** Autonomous system number, if you resolved one. */\n asn?: number\n /** Distinct paths this slice touched — a scraper sweeps, a reader does not. */\n distinctPaths?: number\n /** Verification verdict, if you ran one. */\n verification?: 'verified' | 'spoofed' | 'unverifiable' | 'not-claimed'\n country?: string\n}\n\nexport interface RecommendOptions {\n /**\n * Requests-per-slice above which a single IP is considered abusive. Defaults\n * to 10x the median across observations, floored at 500.\n */\n abuseThreshold?: number\n /** Rate-limit budget proposed for training crawlers. Defaults to 600/hour. */\n trainingBudget?: { window: number; requests: number }\n /** Skip the protective bypass rule. Rarely a good idea. */\n omitProtectiveBypass?: boolean\n}\n\n/* -------------------------------------------------------------------------- */\n\nfunction shellQuote(json: unknown): string {\n return `'${JSON.stringify(json).replace(/'/g, `'\\\\''`)}'`\n}\n\nfunction toCli(r: Omit<FirewallRecommendation, 'cli' | 'json'>): string {\n const parts = [`vercel firewall rules add ${JSON.stringify(r.name)}`]\n r.groups.forEach((group, i) => {\n if (i > 0) parts.push(' --or')\n for (const c of group) parts.push(` --condition ${shellQuote(c)}`)\n })\n parts.push(` --action ${r.action}`)\n if (r.action === 'rate_limit' && r.rateLimit) {\n parts.push(` --rate-limit-window ${r.rateLimit.window}`)\n parts.push(` --rate-limit-requests ${r.rateLimit.requests}`)\n parts.push(` --rate-limit-action ${r.rateLimit.action}`)\n for (const k of r.rateLimit.keys) parts.push(` --rate-limit-keys ${k}`)\n }\n parts.push(' --yes')\n return parts.join(' \\\\\\n')\n}\n\nfunction toJson(r: Omit<FirewallRecommendation, 'cli' | 'json'>): unknown {\n return {\n name: r.name,\n conditionGroup: r.groups.map((conditions) => ({ conditions })),\n action: { mitigate: { action: r.action } }\n }\n}\n\nfunction finish(r: Omit<FirewallRecommendation, 'cli' | 'json'>): FirewallRecommendation {\n return { ...r, cli: toCli(r), json: toJson(r) }\n}\n\nfunction median(ns: number[]): number {\n if (!ns.length) return 0\n const s = [...ns].sort((a, b) => a - b)\n const mid = Math.floor(s.length / 2)\n return s.length % 2 ? s[mid]! : (s[mid - 1]! + s[mid]!) / 2\n}\n\n/* -------------------------------------------------------------------------- */\n\n/**\n * Turn observations into staged WAF proposals.\n *\n * @example\n * ```ts\n * const rules = recommendFirewallRules(observations)\n * for (const r of rules) {\n * console.log(`# ${r.name} — ${r.rationale}`)\n * console.log(`# evidence: ${r.evidence}`)\n * console.log(r.cli)\n * }\n * ```\n */\nexport function recommendFirewallRules(\n observations: readonly TrafficObservation[],\n opts: RecommendOptions = {}\n): FirewallRecommendation[] {\n const out: FirewallRecommendation[] = []\n\n /* 1. Protect the traffic you want, first and above everything else. --------\n Rules are evaluated top to bottom, so this has to be rule #1 or a later\n user-agent rule will swallow the agents that bring you readers. */\n if (!opts.omitProtectiveBypass) {\n const wanted = observations.filter((o) => o.intent === 'retrieval' || o.intent === 'search')\n const requests = wanted.reduce((n, o) => n + o.requests, 0)\n const names = [...new Set(wanted.map((o) => o.botName))]\n out.push(\n finish({\n name: 'Allow retrieval and search agents',\n rationale:\n 'Retrieval agents and search crawlers must never be caught by the rules below — they bring readers and rankings.',\n evidence: requests\n ? `${requests.toLocaleString('en-US')} observed requests across ${names.length} vendors (${names.slice(0, 6).join(', ')})`\n : 'no retrieval or search traffic observed yet; installed pre-emptively',\n groups: [\n [\n {\n type: 'user_agent',\n op: 'inc',\n value: [\n 'ChatGPT-User',\n 'OAI-SearchBot',\n 'Claude-User',\n 'Claude-SearchBot',\n 'Perplexity-User',\n 'Googlebot',\n 'bingbot',\n 'DuckDuckBot',\n 'Applebot'\n ]\n }\n ]\n ],\n action: 'bypass',\n eventual: 'bypass',\n risk: 'low',\n caveat:\n 'Place this rule first (`vercel firewall rules reorder ... --first`). A user-agent allowlist is spoofable, so pair with verification in middleware rather than relying on it for security — its job here is to stop your own rules misfiring.'\n })\n )\n }\n\n /* 2. Failed verification — the only class that earns a denial. ------------- */\n const spoofed = observations.filter((o) => o.verification === 'spoofed')\n const spoofedIps = [...new Set(spoofed.map((o) => o.ip).filter((v): v is string => !!v))]\n if (spoofedIps.length) {\n const requests = spoofed.reduce((n, o) => n + o.requests, 0)\n const vendors = [...new Set(spoofed.map((o) => o.botName))]\n out.push(\n finish({\n name: 'Deny impersonated crawler identities',\n rationale:\n 'These addresses claimed a crawler identity that failed verification against the vendor’s published ranges or signature.',\n evidence: `${requests.toLocaleString('en-US')} requests from ${spoofedIps.length} address${spoofedIps.length === 1 ? '' : 'es'} impersonating ${vendors.join(', ')}`,\n groups: [[{ type: 'ip_address', op: 'inc', value: spoofedIps }]],\n action: 'log',\n eventual: 'deny',\n risk: 'low',\n caveat:\n 'Verification failure is strong evidence, but confirm your edge controls x-forwarded-for before enforcing — behind a proxy that forwards a client-supplied header the verdict is worthless.'\n })\n )\n }\n\n /* 3. Single addresses behaving like scrapers. ------------------------------ */\n const perIp = observations.filter((o) => o.ip && o.verification !== 'verified')\n const threshold =\n opts.abuseThreshold ?? Math.max(500, Math.round(median(perIp.map((o) => o.requests)) * 10))\n const heavy = perIp\n .filter((o) => o.requests >= threshold)\n .sort((a, b) => b.requests - a.requests)\n .slice(0, 50)\n if (heavy.length) {\n const sweeping = heavy.filter((o) => (o.distinctPaths ?? 0) > 100)\n out.push(\n finish({\n name: 'Rate limit high-volume unverified addresses',\n rationale:\n 'A single address making orders of magnitude more requests than the median, with no verified identity.',\n evidence:\n `${heavy.length} address${heavy.length === 1 ? '' : 'es'} above ${threshold.toLocaleString('en-US')} requests` +\n (sweeping.length\n ? `; ${sweeping.length} swept >100 distinct paths, which reads as a scrape rather than a reader`\n : ''),\n groups: [[{ type: 'ip_address', op: 'inc', value: heavy.map((o) => o.ip!) }]],\n action: 'log',\n eventual: 'rate_limit',\n rateLimit: { window: 60, requests: 60, action: 'rate_limit', keys: ['ip'] },\n risk: 'medium',\n caveat:\n 'Shared egress means one address can front many real users — a corporate NAT, a mobile carrier, or a VPN. Review the dashboard before enforcing.'\n })\n )\n }\n\n /* 4. Training crawlers: bound the cost, do not disappear from corpora. ----- */\n const training = observations.filter((o) => o.intent === 'training')\n if (training.length) {\n const requests = training.reduce((n, o) => n + o.requests, 0)\n const vendors = [...new Set(training.map((o) => o.botName))]\n const budget = opts.trainingBudget ?? { window: 3600, requests: 600 }\n out.push(\n finish({\n name: 'Rate limit training crawlers',\n rationale:\n 'Bound what bulk corpus collection costs you without removing yourself from training sets.',\n evidence: `${requests.toLocaleString('en-US')} training requests from ${vendors.length} vendors (${vendors.slice(0, 6).join(', ')})`,\n groups: [\n [\n {\n type: 'user_agent',\n op: 'inc',\n value: ['GPTBot', 'ClaudeBot', 'CCBot', 'Bytespider', 'Amazonbot', 'meta-externalagent']\n }\n ]\n ],\n action: 'log',\n eventual: 'rate_limit',\n rateLimit: { window: budget.window, requests: budget.requests, action: 'rate_limit', keys: ['ip'] },\n risk: 'medium',\n caveat:\n 'Denying these removes you from future training sets, which may be exactly wrong for discoverability. Rate limit rather than deny unless you have decided otherwise. Note Vercel counters are per region, so N regions can collectively exceed the limit by ~Nx.'\n })\n )\n }\n\n /* 5. Datacenter ASNs presenting browser user agents. ---------------------- */\n const headlessAsns = [\n ...new Set(\n observations\n .filter((o) => o.asn !== undefined && /Mozilla|Chrome|Safari/i.test(o.userAgent))\n .filter((o) => o.verification !== 'verified')\n .map((o) => o.asn!)\n )\n ]\n if (headlessAsns.length) {\n const slices = observations.filter((o) => o.asn !== undefined && headlessAsns.includes(o.asn))\n const requests = slices.reduce((n, o) => n + o.requests, 0)\n out.push(\n finish({\n name: 'Challenge browser user agents from datacenter networks',\n rationale:\n 'A browser user agent arriving from a hosting network is automation wearing a costume — real browsers come from consumer ISPs.',\n evidence: `${requests.toLocaleString('en-US')} requests across ${headlessAsns.length} datacenter AS numbers`,\n groups: [\n [\n { type: 'geo_as_number', op: 'inc', value: headlessAsns },\n { type: 'user_agent', op: 'sub', value: 'Mozilla' }\n ]\n ],\n action: 'log',\n eventual: 'challenge',\n risk: 'high',\n caveat:\n 'Highest false-positive risk here. Corporate VPNs, privacy relays and some mobile carriers egress from hosting ASNs, and a challenge page breaks API clients and link unfurlers outright. Keep this in log mode for a full week before considering enforcement.'\n })\n )\n }\n\n return out\n}\n\n/** Render recommendations as a runnable, commented shell script. */\nexport function firewallScript(recommendations: readonly FirewallRecommendation[]): string {\n const lines = [\n '#!/usr/bin/env bash',\n '# Vercel WAF proposals generated from observed agent traffic.',\n '#',\n '# Every rule starts in LOG mode and blocks nothing. Vercel stages rule',\n '# changes as drafts, so nothing is live until you run:',\n '#',\n '# vercel firewall diff # review',\n '# vercel firewall publish --yes # go live',\n '#',\n '# Review each rule in the dashboard before promoting it to its eventual',\n '# action. Rules evaluate top to bottom, so keep the bypass rule first.',\n 'set -euo pipefail',\n ''\n ]\n recommendations.forEach((r, i) => {\n lines.push(`# ${i + 1}. ${r.name}`)\n lines.push(`# why: ${r.rationale}`)\n lines.push(`# evidence: ${r.evidence}`)\n lines.push(`# risk: ${r.risk} — eventual action: ${r.eventual}`)\n if (r.caveat) lines.push(`# caveat: ${r.caveat}`)\n lines.push(r.cli)\n lines.push('')\n })\n if (recommendations.length) {\n lines.push('# Keep the protective allow rule at the top of the evaluation order.')\n lines.push(\n `vercel firewall rules reorder ${JSON.stringify(recommendations[0]!.name)} --first --yes`\n )\n lines.push('')\n lines.push('vercel firewall diff')\n lines.push('echo \"Review above, then: vercel firewall publish --yes\"')\n }\n return lines.join('\\n')\n}\n","import type { AnalyticsAdapter, CaptureEvent } from '../types.js'\nimport { CaptureTransportError } from '../errors.js'\n\n\nexport interface PostHogAdapterConfig {\n /** PostHog project API key (the public one used by the JS SDK). */\n apiKey: string\n /**\n * PostHog host, with or without scheme. Defaults to `https://us.i.posthog.com`.\n * Use `https://eu.i.posthog.com` for EU cloud, or your own reverse-proxy\n * domain (e.g. `https://svc.example.com`).\n */\n host?: string\n /**\n * Path on the host that accepts single-event captures. Defaults to\n * `/i/v0/e/` which is PostHog's current endpoint for this.\n */\n path?: string\n /**\n * Override the `fetch` implementation (useful for tests or custom runtimes\n * that need a pinned fetch).\n */\n fetchImpl?: typeof fetch\n /**\n * Abort the capture after this many milliseconds. Defaults to 3000. Without\n * a bound, a hung backend leaves a pending promise for the lifetime of an\n * edge invocation.\n */\n timeoutMs?: number\n}\n\n/**\n * Adapter that posts each event to the PostHog capture endpoint. Uses\n * `keepalive: true` so the request survives after a serverless response\n * returns — events aren't guaranteed (fire-and-forget), but that's the\n * trade we want to keep the hot path fast.\n */\nexport function posthogAnalytics(config: PostHogAdapterConfig): AnalyticsAdapter {\n const hostRaw = config.host ?? 'https://us.i.posthog.com'\n const base = (/^https?:\\/\\//.test(hostRaw) ? hostRaw : `https://${hostRaw}`).replace(/\\/$/, '')\n const path = (config.path ?? '/i/v0/e/').replace(/^(?!\\/)/, '/')\n const endpoint = `${base}${path}`\n const fetchImpl = config.fetchImpl ?? fetch\n\n return {\n async capture(event: CaptureEvent): Promise<void> {\n // PostHog runs its own user-agent and GeoIP enrichment, but only off its\n // canonical property names. We already carry both values under our own\n // keys, so mirroring them costs nothing and unlocks a free second opinion:\n // getTrafficCategory(), getBotName() and friends all read\n // `properties.$raw_user_agent`, and GeoIP reads `properties.$ip`.\n //\n // Without this every event arrives with traffic category `no_user_agent`\n // and PostHog's whole bot taxonomy sits dormant — which is exactly what\n // happened on ours until someone queried it.\n //\n // `$ip` is mirrored only when the caller already opted into `captureIp`.\n // Adding it otherwise would put a raw address on the event that the\n // caller deliberately kept off.\n const ua = event.properties.user_agent\n const ip = event.properties.client_ip\n const payload = {\n api_key: config.apiKey,\n event: event.event,\n distinct_id: event.distinctId,\n timestamp: event.timestamp,\n properties: {\n ...event.properties,\n ...(typeof ua === 'string' && ua ? { $raw_user_agent: ua } : {}),\n ...(typeof ip === 'string' && ip ? { $ip: ip } : {})\n }\n }\n // A 401 from a mistyped key used to look identical to success. Surface\n // it: `trackVisit` routes it to `onError` and still never throws into\n // the response path.\n const res = await fetchImpl(endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n keepalive: true,\n signal: AbortSignal.timeout(config.timeoutMs ?? 3000)\n })\n if (!res.ok) {\n throw new CaptureTransportError(\n `PostHog capture failed: ${res.status} ${res.statusText}`,\n res.status,\n await res.text().catch(() => undefined)\n )\n }\n }\n }\n}\n","import type { AnalyticsAdapter, CaptureEvent } from '../types.js'\nimport { CaptureTransportError } from '../errors.js'\n\nexport interface WebhookAdapterConfig {\n /** Destination URL that receives a POST for each event. */\n url: string\n /** Extra headers merged onto the POST (useful for shared-secret auth). */\n headers?: Record<string, string>\n /**\n * Transform the event into the exact JSON body the destination expects.\n * Defaults to sending the {@link CaptureEvent} as-is.\n */\n transform?: (event: CaptureEvent) => unknown\n /** Override the `fetch` implementation. */\n fetchImpl?: typeof fetch\n /** Abort the capture after this many milliseconds. Defaults to 3000. */\n timeoutMs?: number\n}\n\n/**\n * Adapter that POSTs each event to an arbitrary webhook URL. Keeps the\n * library analytics-backend-agnostic — use this when PostHog isn't your\n * analytics of record, or when you want to multiplex events through your\n * own ingestion layer.\n */\nexport function webhookAnalytics(config: WebhookAdapterConfig): AnalyticsAdapter {\n const fetchImpl = config.fetchImpl ?? fetch\n const transform = config.transform ?? ((e: CaptureEvent): unknown => e)\n\n return {\n async capture(event: CaptureEvent): Promise<void> {\n const res = await fetchImpl(config.url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(config.headers ?? {})\n },\n body: JSON.stringify(transform(event)),\n keepalive: true,\n signal: AbortSignal.timeout(config.timeoutMs ?? 3000)\n })\n if (!res.ok) {\n throw new CaptureTransportError(\n `Webhook capture failed: ${res.status} ${res.statusText}`,\n res.status,\n await res.text().catch(() => undefined)\n )\n }\n }\n }\n}\n","import type { AnalyticsAdapter, CaptureEvent } from '../types.js'\n\n/**\n * Escape hatch for wiring a callback directly as an analytics adapter.\n * Useful when you want to log events, pipe them through your own SDK, or\n * compose multiple adapters.\n *\n * @example\n * ```ts\n * const devAnalytics = customAnalytics((e) => console.log('[doc_view]', e))\n * ```\n */\nexport function customAnalytics(\n capture: (event: CaptureEvent) => Promise<void> | void\n): AnalyticsAdapter {\n return { capture }\n}\n"]}
1
+ {"version":3,"sources":["../src/bots.ts","../src/hash.ts","../src/track.ts","../src/errors.ts","../src/policy.ts","../src/adapters/posthog.ts","../src/adapters/webhook.ts","../src/adapters/custom.ts"],"names":["AI_BOT_PATTERN","HTTP_CLIENT_PATTERN","isAiBot","userAgent","isHttpClient","parseBotName","s","firstUserAgentProduct","compatibleMatch","detectHeadless","req","signals","ua","secChUa","accept","score","classifyAgent","label","aiBot","httpClient","kind","classifyRequest","base","headless","HashSecretError","message","subtle","c","KEYS","keyFor","secret","k","hashId","input","sig","bytes","out","b","randomSecret","x","fallbackSecret","warnedNoSecret","resolveSecret","explicit","fromEnv","trackVisit","opts","onlyBots","skipBrowsers","pathname","originFromUrl","url","origin","ip","referer","country","geo","extractGeo","classification","verification","headlessMeaningful","distinctId","event","err","decode","v","fields","CaptureTransportError","status","body","RETRIEVAL","TRAINING","SEARCH","PREVIEW","agentIntent","agentPolicy","intent","entry","resolved","posthogAnalytics","config","hostRaw","path","endpoint","fetchImpl","payload","res","webhookAnalytics","transform","e","customAnalytics","capture"],"mappings":"AAgBO,IAAMA,CAAAA,CACX,shBAAA,CAmBWC,CAAAA,CACX,4IAEK,SAASC,EAAQC,CAAAA,CAA+C,CACrE,OAAKA,CAAAA,CACEH,CAAAA,CAAe,IAAA,CAAKG,CAAS,CAAA,CADb,KAEzB,CAEO,SAASC,CAAAA,CAAaD,CAAAA,CAA+C,CAC1E,OAAKA,CAAAA,CACEF,CAAAA,CAAoB,IAAA,CAAKE,CAAS,CAAA,CADlB,KAEzB,CAeO,SAASE,CAAAA,CAAaF,CAAAA,CAA8C,CACzE,GAAI,CAACA,CAAAA,EAAa,OAAOA,CAAAA,EAAc,QAAA,CAAU,OAAO,OAAA,CACxD,IAAMG,CAAAA,CAAIH,CAAAA,CAAU,WAAA,EAAY,CAGhC,OAAIG,CAAAA,CAAE,QAAA,CAAS,cAAc,CAAA,EAAKA,CAAAA,CAAE,QAAA,CAAS,QAAQ,CAAA,EAAKA,CAAAA,CAAE,SAAS,eAAe,CAAA,EAAKA,CAAAA,CAAE,QAAA,CAAS,QAAQ,CAAA,CACnG,UAEPA,CAAAA,CAAE,QAAA,CAAS,WAAW,CAAA,EACtBA,CAAAA,CAAE,QAAA,CAAS,aAAa,CAAA,EACxBA,CAAAA,CAAE,QAAA,CAAS,kBAAkB,CAAA,EAC7BA,CAAAA,CAAE,QAAA,CAAS,YAAY,CAAA,EACvBA,CAAAA,CAAE,QAAA,CAAS,WAAW,CAAA,CAEf,QAAA,CACLA,EAAE,QAAA,CAAS,eAAe,CAAA,EAAKA,CAAAA,CAAE,QAAA,CAAS,iBAAiB,EAAU,YAAA,CACrEA,CAAAA,CAAE,QAAA,CAAS,OAAO,CAAA,CAAU,cAAA,CAE9BA,EAAE,QAAA,CAAS,iBAAiB,CAAA,EAC5BA,CAAAA,CAAE,QAAA,CAAS,WAAW,CAAA,EACtBA,CAAAA,CAAE,QAAA,CAAS,uBAAuB,CAAA,EAClCA,CAAAA,CAAE,QAAA,CAAS,cAAc,GACzBA,CAAAA,CAAE,QAAA,CAAS,qBAAqB,CAAA,EAChCA,CAAAA,CAAE,QAAA,CAAS,sBAAsB,CAAA,CAE1B,QAAA,CACLA,CAAAA,CAAE,QAAA,CAAS,UAAU,CAAA,CAAU,QAC/BA,CAAAA,CAAE,QAAA,CAAS,SAAS,CAAA,CAAU,MAAA,CAC9BA,CAAAA,CAAE,SAAS,YAAY,CAAA,CAAU,YAAA,CACjCA,CAAAA,CAAE,QAAA,CAAS,WAAW,GAAKA,CAAAA,CAAE,QAAA,CAAS,gBAAgB,CAAA,EAAKA,CAAAA,CAAE,QAAA,CAAS,SAAS,CAAA,CAAU,QAAA,CAE3FA,CAAAA,CAAE,QAAA,CAAS,oBAAoB,CAAA,EAC/BA,EAAE,QAAA,CAAS,sBAAsB,CAAA,EACjCA,CAAAA,CAAE,QAAA,CAAS,iBAAiB,CAAA,EAC5BA,CAAAA,CAAE,QAAA,CAAS,aAAa,CAAA,CAEjB,MAAA,CACLA,CAAAA,CAAE,QAAA,CAAS,gBAAgB,CAAA,CAAU,SAAA,CACrCA,CAAAA,CAAE,QAAA,CAAS,eAAe,CAAA,CAAU,aACpCA,CAAAA,CAAE,QAAA,CAAS,QAAQ,CAAA,CAAU,SAAA,CAC7BA,CAAAA,CAAE,SAAS,SAAS,CAAA,CAAU,SAAA,CAC9BA,CAAAA,CAAE,QAAA,CAAS,QAAQ,CAAA,CAAU,KAAA,CAC7BA,CAAAA,CAAE,QAAA,CAAS,QAAQ,CAAA,CAAU,QAAA,CAC7BA,CAAAA,CAAE,SAAS,QAAQ,CAAA,CAAU,QAAA,CAC7BA,CAAAA,CAAE,QAAA,CAAS,UAAU,EAAU,UAAA,CAC/BA,CAAAA,CAAE,QAAA,CAAS,UAAU,CAAA,CAAU,UAAA,CAC/BA,EAAE,QAAA,CAAS,UAAU,CAAA,CAAU,QAAA,CAC/BA,CAAAA,CAAE,QAAA,CAAS,QAAQ,CAAA,EAAKA,CAAAA,CAAE,QAAA,CAAS,QAAQ,CAAA,CAAU,SAAA,CACrDA,CAAAA,CAAE,SAAS,UAAU,CAAA,CAAU,OAAA,CAC/BA,CAAAA,CAAE,QAAA,CAAS,MAAM,GAAKA,CAAAA,CAAE,QAAA,CAAS,MAAM,CAAA,CAAU,KAAA,CACjDA,CAAAA,CAAE,SAAS,YAAY,CAAA,CAAU,OAAA,CACjCA,CAAAA,CAAE,QAAA,CAAS,UAAU,EAAU,UAAA,CAC/BA,CAAAA,CAAE,QAAA,CAAS,mBAAmB,CAAA,CAAU,WAAA,CACxCA,EAAE,QAAA,CAAS,uBAAuB,CAAA,CAAU,aAAA,CAC5CA,CAAAA,CAAE,QAAA,CAAS,UAAU,CAAA,CAAU,UAAA,CAG/BA,CAAAA,CAAE,QAAA,CAAS,WAAW,CAAA,CAAU,SAChCA,CAAAA,CAAE,QAAA,CAAS,YAAY,CAAA,CAAU,SAAA,CACjCA,CAAAA,CAAE,QAAA,CAAS,SAAS,CAAA,CAAU,UAAA,CAC9BA,CAAAA,CAAE,QAAA,CAAS,QAAQ,CAAA,EACnBA,EAAE,QAAA,CAAS,UAAU,CAAA,CAAU,KAAA,CAC/BA,CAAAA,CAAE,QAAA,CAAS,gBAAgB,CAAA,CAAU,gBAAA,CACrCA,CAAAA,CAAE,QAAA,CAAS,UAAU,CAAA,CAAU,WAC/BA,CAAAA,CAAE,QAAA,CAAS,aAAa,CAAA,CAAU,aAAA,CAClCA,CAAAA,CAAE,QAAA,CAAS,YAAY,CAAA,CAAU,YAAA,CACjCA,CAAAA,CAAE,QAAA,CAAS,aAAa,CAAA,CAAU,WAGlCA,CAAAA,CAAE,QAAA,CAAS,aAAa,CAAA,CAAU,aAAA,CAClCA,CAAAA,CAAE,SAAS,SAAS,CAAA,CAAU,SAAA,CAC9BA,CAAAA,CAAE,QAAA,CAAS,YAAY,EAAU,YAAA,CACjCA,CAAAA,CAAE,QAAA,CAAS,gBAAgB,CAAA,CAAU,WAAA,CACrCA,CAAAA,CAAE,QAAA,CAAS,cAAc,CAAA,EAAKA,CAAAA,CAAE,QAAA,CAAS,SAAS,CAAA,CAAU,UAC5DA,CAAAA,CAAE,QAAA,CAAS,UAAU,CAAA,CAAU,OAAA,CAC/BA,CAAAA,CAAE,SAAS,YAAY,CAAA,CAAU,SAAA,CACjCA,CAAAA,CAAE,QAAA,CAAS,aAAa,EAAU,UAAA,CAClCA,CAAAA,CAAE,QAAA,CAAS,YAAY,CAAA,CAAU,SAAA,CACjCA,EAAE,QAAA,CAAS,aAAa,CAAA,CAAU,UAAA,CAClCA,CAAAA,CAAE,QAAA,CAAS,UAAU,CAAA,CAAU,UAAA,CAG/BA,CAAAA,CAAE,QAAA,CAAS,WAAW,CAAA,CAAU,SAChCA,CAAAA,CAAE,QAAA,CAAS,OAAO,CAAA,CAAU,OAAA,CAC5BA,CAAAA,CAAE,SAAS,WAAW,CAAA,CAAU,QAAA,CAChCA,CAAAA,CAAE,QAAA,CAAS,aAAa,CAAA,CAAU,OAAA,CAGlCA,CAAAA,CAAE,QAAA,CAAS,qBAAqB,CAAA,CAAU,UAAA,CAC1CA,CAAAA,CAAE,SAAS,kBAAkB,CAAA,CAAU,mBAAA,CAGvCA,CAAAA,CAAE,QAAA,CAAS,QAAQ,EAAU,SAAA,CAG7BA,CAAAA,CAAE,QAAA,CAAS,QAAQ,CAAA,CAAU,QAAA,CAC7BA,EAAE,QAAA,CAAS,gBAAgB,CAAA,CAAU,iBAAA,CACrCA,CAAAA,CAAE,QAAA,CAAS,WAAW,CAAA,CAAU,WAAA,CAChCA,CAAAA,CAAE,QAAA,CAAS,MAAM,CAAA,CAAU,MAAA,CAC3BA,EAAE,QAAA,CAAS,QAAQ,CAAA,CAAU,QAAA,CAC7BA,CAAAA,CAAE,QAAA,CAAS,YAAY,CAAA,CAAU,QAAA,CAIjCA,CAAAA,CAAE,QAAA,CAAS,WAAW,CAAA,CAAU,WAChC,QAAA,CAAS,IAAA,CAAKA,CAAC,CAAA,CAAU,MAAA,CACzB,SAAA,CAAU,IAAA,CAAKA,CAAC,CAAA,CAAU,OAAA,CAC1B,4BAAA,CAA6B,IAAA,CAAKA,CAAC,CAAA,CAAU,MAC7C,WAAA,CAAY,IAAA,CAAKA,CAAC,CAAA,CAAU,OAAA,CAC5B,cAAA,CAAe,KAAKA,CAAC,CAAA,CAAU,YAAA,CAC/B,mBAAA,CAAoB,IAAA,CAAKA,CAAC,EAAU,iBAAA,CACpC,kBAAA,CAAmB,IAAA,CAAKA,CAAC,CAAA,CAAU,gBAAA,CACnC,WAAW,IAAA,CAAKA,CAAC,CAAA,CAAU,QAAA,CAC3B,WAAA,CAAY,IAAA,CAAKA,CAAC,CAAA,CAAU,SAAA,CAC5B,QAAA,CAAS,IAAA,CAAKA,CAAC,CAAA,CAAU,OAGzBA,CAAAA,CAAE,QAAA,CAAS,SAAS,CAAA,EAAKA,CAAAA,CAAE,QAAA,CAAS,QAAQ,CAAA,EAAKA,CAAAA,CAAE,QAAA,CAAS,QAAQ,CAAA,EAAKA,CAAAA,CAAE,QAAA,CAAS,SAAS,CAAA,CACxF,SAAA,CAEF,OACT,CAOO,SAASC,CAAAA,CAAsBJ,EAA8C,CAClF,GAAI,CAACA,CAAAA,EAAa,OAAOA,CAAAA,EAAc,SAAU,OAAO,OAAA,CACxD,IAAMK,CAAAA,CAAkBL,CAAAA,CAAU,KAAA,CAAM,yCAAyC,CAAA,CACjF,OAAIK,CAAAA,EAAmBA,CAAAA,CAAgB,CAAC,CAAA,CAAUA,CAAAA,CAAgB,CAAC,CAAA,CAAE,IAAA,EAAK,CAC5DL,CAAAA,CAAU,IAAA,EAAK,CAAE,MAAM,GAAG,CAAA,CAAE,CAAC,CAAA,EAAG,IAAA,EAAK,CAAE,MAAM,KAAK,CAAA,CAAE,CAAC,CAAA,EAAG,IAAA,EAAK,EAC3D,OAClB,CAkBO,SAASM,CAAAA,CAAeC,CAAAA,CAAiC,CAC9D,IAAMC,CAAAA,CAAoB,EAAC,CACrBC,CAAAA,CAAAA,CAAMF,CAAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,YAAY,GAAK,EAAA,EAAI,WAAA,EAAY,CAI7D,GAAI,EAFFE,CAAAA,CAAG,SAAS,SAAS,CAAA,EAAKA,CAAAA,CAAG,QAAA,CAAS,QAAQ,CAAA,EAAKA,EAAG,QAAA,CAAS,QAAQ,CAAA,EAAKA,CAAAA,CAAG,QAAA,CAAS,SAAS,GAEjF,OAAO,CAAE,KAAA,CAAO,CAAA,CAAG,OAAA,CAAS,GAAI,MAAA,CAAQ,KAAM,CAAA,CAE3DF,CAAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,iBAAiB,CAAA,EACpCC,CAAAA,CAAQ,IAAA,CAAK,yBAAyB,CAAA,CAEnCD,CAAAA,CAAI,QAAQ,GAAA,CAAI,gBAAgB,CAAA,EACnCC,CAAAA,CAAQ,IAAA,CAAK,wBAAwB,CAAA,CAEvC,IAAME,CAAAA,CAAUH,CAAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,WAAW,CAAA,CACtCG,EAEMA,CAAAA,CAAQ,WAAA,EAAY,CAAE,QAAA,CAAS,gBAAgB,CAAA,EACxDF,EAAQ,IAAA,CAAK,sBAAsB,CAAA,CAFnCA,CAAAA,CAAQ,IAAA,CAAK,mBAAmB,EAIlC,IAAMG,CAAAA,CAASJ,CAAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,QAAQ,CAAA,EAAK,EAAA,CAAA,CACxC,CAACI,CAAAA,EAAUA,CAAAA,GAAW,KAAA,GACxBH,CAAAA,CAAQ,IAAA,CAAK,wBAAwB,CAAA,CAAA,CAElCD,CAAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,YAAY,CAAA,EAAK,IAAI,WAAA,EAAY,GAAM,OAAA,EAC1DC,CAAAA,CAAQ,IAAA,CAAK,kBAAkB,EAGjC,IAAMI,CAAAA,CAAQJ,CAAAA,CAAQ,MAAA,CACtB,OAAO,CAAE,KAAA,CAAAI,CAAAA,CAAO,OAAA,CAAAJ,CAAAA,CAAS,MAAA,CAAQI,CAAAA,EAAS,CAAE,CAC9C,CA6CO,SAASC,CAAAA,CAAcb,CAAAA,CAA2D,CACvF,IAAMc,CAAAA,CAAQZ,EAAaF,CAAS,CAAA,CAC9Be,CAAAA,CAAQhB,CAAAA,CAAQC,CAAS,CAAA,CACzBgB,EAAaf,CAAAA,CAAaD,CAAS,CAAA,CAErCiB,CAAAA,CACJ,OAAIF,CAAAA,CAAOE,EAAO,kBAAA,CACTD,CAAAA,CAAYC,CAAAA,CAAO,mBAAA,CACnBH,CAAAA,GAAU,SAAA,CAAWG,EAAO,SAAA,CAChCA,CAAAA,CAAO,OAAA,CAEL,CAAE,IAAA,CAAAA,CAAAA,CAAM,MAAAH,CAAAA,CAAO,OAAA,CAASC,CAAAA,CAAO,eAAA,CAAiBC,CAAW,CACpE,CAOO,SAASE,CAAAA,CAAgBX,CAAAA,CAAmC,CACjE,IAAMP,CAAAA,CAAYO,CAAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,YAAY,CAAA,EAAK,EAAA,CAC7CY,CAAAA,CAAON,CAAAA,CAAcb,CAAS,CAAA,CAC9BoB,CAAAA,CAAWd,CAAAA,CAAeC,CAAG,CAAA,CAE/BU,CAAAA,CAAOE,EAAK,IAAA,CACZL,CAAAA,CAAQK,CAAAA,CAAK,KAAA,CACjB,OAAIF,CAAAA,GAAS,WAAaG,CAAAA,CAAS,MAAA,GACjCH,CAAAA,CAAO,iBAAA,CAKPH,CAAAA,CAAQ,UAAA,CAAA,CAGH,CAAE,GAAGK,CAAAA,CAAM,IAAA,CAAAF,CAAAA,CAAM,KAAA,CAAAH,CAAAA,CAAO,QAAA,CAAAM,CAAS,CAC1C,CCvTO,IAAMC,CAAAA,CAAN,cAA8B,KAAM,CACzC,WAAA,CAAYC,CAAAA,CAAiB,CAC3B,KAAA,CAAMA,CAAO,CAAA,CACb,KAAK,IAAA,CAAO,kBACd,CACF,EAWA,SAASC,CAAAA,EAAuB,CAC9B,IAAMC,CAAAA,CAAI,UAAA,CAAW,MAAA,CACrB,GAAI,CAACA,CAAAA,EAAG,OACN,MAAM,IAAIH,CAAAA,CACR,qKAGF,CAAA,CAEF,OAAOG,EAAE,MACX,CAKA,IAAMC,CAAAA,CAAO,IAAI,GAAA,CAEjB,SAASC,CAAAA,CAAOC,CAAAA,CAAoC,CAClD,IAAIC,CAAAA,CAAIH,CAAAA,CAAK,GAAA,CAAIE,CAAM,CAAA,CACvB,OAAKC,CAAAA,GACHA,CAAAA,CAAIL,CAAAA,EAAO,CAAE,UACX,KAAA,CACA,IAAI,WAAA,EAAY,CAAE,MAAA,CAAOI,CAAM,EAC/B,CAAE,IAAA,CAAM,MAAA,CAAQ,IAAA,CAAM,SAAU,CAAA,CAChC,MACA,CAAC,MAAM,CACT,CAAA,CACAF,CAAAA,CAAK,GAAA,CAAIE,CAAAA,CAAQC,CAAC,CAAA,CAAA,CAEbA,CACT,CAYA,eAAsBC,CAAAA,CAAOC,CAAAA,CAAeH,EAAiC,CAC3E,GAAI,OAAOA,CAAAA,EAAW,QAAA,EAAYA,CAAAA,CAAO,SAAW,CAAA,CAClD,MAAM,IAAIN,CAAAA,CAAgB,oCAAoC,CAAA,CAEhE,IAAMU,CAAAA,CAAM,MAAMR,CAAAA,EAAO,CAAE,IAAA,CAAK,MAAA,CAAQ,MAAMG,CAAAA,CAAOC,CAAM,CAAA,CAAG,IAAI,WAAA,EAAY,CAAE,MAAA,CAAOG,CAAK,CAAC,CAAA,CACvFE,CAAAA,CAAQ,IAAI,UAAA,CAAWD,CAAAA,CAAK,EAAG,CAAC,CAAA,CAClCE,CAAAA,CAAM,EAAA,CACV,IAAA,IAAWC,CAAAA,IAAKF,EAAOC,CAAAA,EAAOC,CAAAA,CAAE,QAAA,CAAS,EAAE,CAAA,CAAE,QAAA,CAAS,CAAA,CAAG,GAAG,CAAA,CAC5D,OAAO,OAAA,CAAUD,CACnB,CAQO,SAASE,GAAuB,CACrC,IAAMD,CAAAA,CAAI,IAAI,UAAA,CAAW,EAAE,EAC3BX,CAAAA,EAAO,CACP,UAAA,CAAW,MAAA,CAAO,eAAA,CAAgBW,CAAC,EACnC,IAAID,CAAAA,CAAM,EAAA,CACV,IAAA,IAAWG,CAAAA,IAAKF,CAAAA,CAAGD,GAAOG,CAAAA,CAAE,QAAA,CAAS,EAAE,CAAA,CAAE,QAAA,CAAS,CAAA,CAAG,GAAG,CAAA,CACxD,OAAOH,CACT,CCvFA,IAAII,CAAAA,CACAC,EAAiB,KAAA,CAErB,SAASC,CAAAA,CAAcC,CAAAA,CAAsC,CAC3D,GAAIA,EAAU,OAAOA,CAAAA,CACrB,IAAMC,CAAAA,CACJ,OAAO,OAAA,CAAY,GAAA,CAAc,OAAA,CAAQ,GAAA,EAAK,yBAAA,CAA4B,MAAA,CAC5E,OAAIA,CAAAA,GACCJ,CAAAA,GACHA,EAAiBF,CAAAA,EAAa,CACzBG,CAAAA,GACHA,CAAAA,CAAiB,IAAA,CAEjB,OAAA,CAAQ,KACN,iKAGF,CAAA,CAAA,CAAA,CAGGD,CAAAA,CACT,CAWA,eAAsBK,CAAAA,CAAWnC,EAAcoC,CAAAA,CAAwC,CACrF,IAAM3C,CAAAA,CAAYO,CAAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,YAAY,CAAA,EAAK,EAAA,CAE7CqC,CAAAA,CAAWD,CAAAA,CAAK,QAAA,EAAY,KAAA,CAC5BE,EAAeF,CAAAA,CAAK,YAAA,EAAgB,KAAA,CAC1C,GAAI,EAAAC,CAAAA,EAAY,CAAC7C,CAAAA,CAAQC,CAAS,CAAA,CAAA,EAC9B,EAAA6C,CAAAA,EAAgB,CAAC9C,EAAQC,CAAS,CAAA,EAAK,CAACC,CAAAA,CAAaD,CAAS,CAAA,EAI5D,CAACM,CAAAA,CAAeC,CAAG,CAAA,CAAE,MAAA,CAAA,CAG3B,GAAI,CACF,IAAIuC,EAAW,GAAA,CACXC,CAAAA,CAAgB,EAAA,CACpB,GAAI,CACF,IAAMC,EAAM,IAAI,GAAA,CAAIzC,CAAAA,CAAI,GAAG,CAAA,CAC3BuC,CAAAA,CAAWE,EAAI,QAAA,CACfD,CAAAA,CAAgBC,CAAAA,CAAI,OACtB,CAAA,KAAQ,CAENF,EAAWvC,CAAAA,CAAI,GAAA,EAAO,IACxB,CACA,IAAM0C,CAAAA,CAASN,EAAK,MAAA,EAAUI,CAAAA,CAGxBG,CAAAA,CAAAA,CADe3C,CAAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,iBAAiB,CAAA,EAAK,EAAA,EACnC,KAAA,CAAM,GAAG,CAAA,CAAE,CAAC,GAAG,IAAA,EAAK,EAAK,EAAA,CAC3C4C,CAAAA,CAAU5C,CAAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,SAAS,CAAA,CACnC6C,CAAAA,CAAUT,CAAAA,CAAK,cAAA,GACjBpC,CAAAA,CAAI,OAAA,CAAQ,IAAI,qBAAqB,CAAA,EACrCA,CAAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,cAAc,GAC9BA,CAAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,gBAAgB,CAAA,CAAA,EAChC,IAAA,CAEE8C,EAAMV,CAAAA,CAAK,UAAA,CAAaW,CAAAA,CAAW/C,CAAG,CAAA,CAAI,IAAA,CAC1CgD,CAAAA,CAAiBrC,CAAAA,CAAgBX,CAAG,CAAA,CAOpCiD,CAAAA,CAAeb,CAAAA,CAAK,MAAA,CAAS,MAAMA,EAAK,MAAA,CAAOpC,CAAG,CAAA,CAAI,IAAA,CAMtDkD,CAAAA,CACJF,CAAAA,CAAe,OAAS,iBAAA,EAAqBA,CAAAA,CAAe,IAAA,GAAS,SAAA,CAEjEG,CAAAA,CAAa,MAAM7B,EAAO,CAAA,EAAGqB,CAAE,CAAA,CAAA,EAAIlD,CAAS,CAAA,CAAA,CAAIuC,CAAAA,CAAcI,CAAAA,CAAK,QAAQ,CAAC,CAAA,CAE5EgB,CAAAA,CAAQ,CACZ,KAAA,CAAOhB,CAAAA,CAAK,WAAa,aAAA,CACzB,UAAA,CAAAe,CAAAA,CACA,SAAA,CAAW,IAAI,IAAA,GAAO,WAAA,EAAY,CAClC,UAAA,CAAY,CAKV,GAAGf,CAAAA,CAAK,WACR,uBAAA,CAAyB,CAAA,CAAA,CACzB,YAAA,CAAcM,CAAAA,CAAS,CAAA,EAAGA,CAAM,GAAGH,CAAQ,CAAA,CAAA,CAAKA,CAAAA,CAChD,IAAA,CAAMA,CAAAA,CACN,MAAA,CAAQvC,EAAI,MAAA,CACZ,GAAIoC,CAAAA,CAAK,cAAA,CAAiB,CAAE,YAAA,CAAcS,CAAQ,CAAA,CAAI,EAAC,CACvD,GAAIC,CAAAA,EAAO,GACX,GAAIV,CAAAA,CAAK,SAAA,CAAY,CAAE,SAAA,CAAWO,CAAAA,EAAM,IAAK,CAAA,CAAI,EAAC,CAClD,UAAA,CAAYlD,CAAAA,CACZ,SAAA,CAAWuD,CAAAA,CAAe,QAC1B,QAAA,CAAUA,CAAAA,CAAe,KAAA,CACzB,WAAA,CAAaA,CAAAA,CAAe,IAAA,CAC5B,kBAAmBA,CAAAA,CAAe,eAAA,CAClC,GAAIE,CAAAA,CACA,CACE,cAAA,CAAgBF,EAAe,QAAA,EAAU,KAAA,EAAS,CAAA,CAClD,eAAA,CAAiBA,CAAAA,CAAe,QAAA,EAAU,MAAA,EAAU,CAAA,CACtD,CAAA,CACA,EAAC,CACL,GAAIC,CAAAA,CACA,CACE,aAAcA,CAAAA,CAAa,QAAA,CAC3B,gBAAA,CAAkBA,CAAAA,CAAa,OAAA,CAC/B,GAAIA,EAAa,MAAA,CAAS,CAAE,uBAAA,CAAyBA,CAAAA,CAAa,MAAO,CAAA,CAAI,EAC/E,CAAA,CACA,EAAC,CACL,OAAA,CAAAL,CAAAA,CACA,MAAA,CAAQR,CAAAA,CAAK,MAAA,EAAU,IACzB,CACF,CAAA,CAEA,MAAMA,CAAAA,CAAK,UAAU,OAAA,CAAQgB,CAAK,EACpC,CAAA,MAASC,CAAAA,CAAK,CAGZjB,EAAK,OAAA,GAAUiB,CAAAA,YAAe,KAAA,CAAQA,CAAAA,CAAM,IAAI,KAAA,CAAM,OAAOA,CAAG,CAAC,CAAC,EACpE,CACF,CAMA,SAASN,CAAAA,CAAW/C,CAAAA,CAAsC,CACxD,IAAMsD,CAAAA,CAAUC,CAAAA,EAAqB,CACnC,GAAI,CAACA,CAAAA,CAAG,OAAO,EAAA,CACf,GAAI,CACF,OAAO,kBAAA,CAAmBA,CAAC,CAC7B,CAAA,KAAQ,CACN,OAAOA,CACT,CACF,CAAA,CACMC,CAAAA,CAAkC,CACtC,CAAC,QAAA,CAAUF,CAAAA,CAAOtD,CAAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,4BAA4B,CAAC,CAAC,CAAA,CAChE,CAAC,MAAA,CAAQsD,CAAAA,CAAOtD,CAAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,kBAAkB,CAAC,CAAC,CAAA,CACpD,CAAC,UAAA,CAAYA,CAAAA,CAAI,OAAA,CAAQ,IAAI,sBAAsB,CAAA,EAAK,EAAE,CAAA,CAC1D,CAAC,WAAA,CAAaA,CAAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,uBAAuB,CAAA,EAAK,EAAE,CAAA,CAC5D,CAAC,WAAYA,CAAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,sBAAsB,CAAA,EAAK,EAAE,CAC5D,CAAA,CACM0B,CAAAA,CAA8B,EAAC,CACrC,IAAA,GAAW,CAACL,EAAGkC,CAAC,CAAA,GAAKC,CAAAA,CAAYD,CAAAA,GAAG7B,CAAAA,CAAIL,CAAC,CAAA,CAAIkC,CAAAA,CAAAA,CAC7C,OAAO7B,CACT,CCrKO,IAAM+B,CAAAA,CAAN,cAAoC,KAAM,CACtC,MAAA,CACA,IAAA,CACT,WAAA,CAAY1C,CAAAA,CAAiB2C,CAAAA,CAAiBC,EAAe,CAC3D,KAAA,CAAM5C,CAAO,CAAA,CACb,IAAA,CAAK,IAAA,CAAO,wBACZ,IAAA,CAAK,MAAA,CAAS2C,CAAAA,CACd,IAAA,CAAK,IAAA,CAAOC,EACd,CACF,EC8BA,IAAMC,CAAAA,CAAY,0JAAA,CAGZC,CAAAA,CAAW,0UAAA,CAaXC,EAAS,kHAAA,CAeTC,CAAAA,CAAU,oKAAA,CA8DT,SAASC,CAAAA,CAAYvE,CAAAA,CAAmD,CAC7E,IAAMS,CAAAA,CAAKT,CAAAA,EAAa,EAAA,CACxB,OAAKS,CAAAA,CAGD0D,EAAU,IAAA,CAAK1D,CAAE,CAAA,CAAU,WAAA,CAC3B2D,CAAAA,CAAS,IAAA,CAAK3D,CAAE,CAAA,CAAU,UAAA,CAC1B4D,CAAAA,CAAO,IAAA,CAAK5D,CAAE,CAAA,CAAU,QAAA,CAGxB6D,EAAQ,IAAA,CAAK7D,CAAE,CAAA,CAAU,SAAA,CAEzBR,CAAAA,CAAaQ,CAAE,EAAU,SAAA,CACtB,SAAA,CAXS,SAYlB,CAcO,SAAS+D,CAAAA,CAAYjE,EAAcoC,CAAAA,CAA2B,EAAC,CAAkB,CACtF,IAAMlC,CAAAA,CAAKF,CAAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,YAAY,CAAA,EAAK,EAAA,CAEtCO,CAAAA,CADiBI,CAAAA,CAAgBX,CAAG,CAAA,CACb,KAAA,CAIvBkE,CAAAA,CAASF,CAAAA,CAAY9D,CAAE,CAAA,CAK7B,GAHgBkC,CAAAA,CAAK,SAAA,EAAW,IAAA,CAC7B+B,CAAAA,EAAUA,CAAAA,GAAU5D,CAAAA,EAASL,EAAG,WAAA,EAAY,CAAE,QAAA,CAASiE,CAAAA,CAAM,WAAA,EAAa,CAC7E,CAAA,CAEE,OAAO,CAAE,MAAA,CAAQ,OAAA,CAAS,MAAA,CAAAD,CAAAA,CAAQ,MAAA3D,CAAAA,CAAO,MAAA,CAAQ,cAAe,CAAA,CAKlE,IAAM6D,CAAAA,CAAWhC,EAAK,YAAA,GAAiBA,CAAAA,CAAK,MAAA,CAASA,CAAAA,CAAK,MAAA,CAAOpC,CAAG,EAAI,MAAA,CAAA,CACpEiD,CAAAA,CACJ,OAAImB,CAAAA,GACFnB,CAAAA,CAAemB,CAAAA,CAAS,QAIpBnB,CAAAA,GAAiB,SAAA,CAAA,CACZ,CACL,MAAA,CAAQ,OAAA,CACR,MAAA,CAAAiB,EACA,KAAA,CAAA3D,CAAAA,CACA,YAAA,CAAA0C,CAAAA,CACA,MAAA,CAAQ,CAAA,EAAG1C,CAAK,CAAA,sDAAA,CAClB,CAAA,CA0BG,CACL,MAAA,CAtBA2D,CAAAA,GAAW,UAAA,CACN9B,EAAK,UAAA,EAAc,OAAA,CACpB8B,CAAAA,GAAW,WAAA,CACR9B,CAAAA,CAAK,WAAA,EAAe,OAAA,CACrB8B,CAAAA,GAAW,QAAA,CACR9B,CAAAA,CAAK,QAAA,EAAY,OAAA,CAClB8B,CAAAA,GAAW,SAAA,CACR9B,EAAK,SAAA,EAAa,OAAA,CACnB8B,CAAAA,GAAW,SAAA,CACR9B,CAAAA,CAAK,SAAA,EAAa,QACnB,OAAA,CAaZ,MAAA,CAAA8B,CAAAA,CACA,KAAA,CAAA3D,CAAAA,CAGA,GAAI0C,EAAe,CAAE,YAAA,CAAAA,CAAa,CAAA,CAAI,EAAC,CACvC,MAAA,CAhB2C,CAC3C,SAAA,CAAW,oCAAA,CACX,QAAA,CAAU,wBAAA,CACV,MAAA,CAAQ,sBAAA,CACR,QAAS,uCAAA,CACT,OAAA,CAAS,6BAAA,CACT,OAAA,CAAS,wBACX,CAAA,CASkBiB,CAAM,CACxB,CACF,CC/LO,SAASG,CAAAA,CAAiBC,CAAAA,CAAgD,CAC/E,IAAMC,CAAAA,CAAUD,CAAAA,CAAO,IAAA,EAAQ,0BAAA,CACzB1D,CAAAA,CAAAA,CAAQ,cAAA,CAAe,IAAA,CAAK2D,CAAO,CAAA,CAAIA,CAAAA,CAAU,CAAA,QAAA,EAAWA,CAAO,CAAA,CAAA,EAAI,QAAQ,KAAA,CAAO,EAAE,CAAA,CACxFC,CAAAA,CAAAA,CAAQF,CAAAA,CAAO,IAAA,EAAQ,YAAY,OAAA,CAAQ,SAAA,CAAW,GAAG,CAAA,CACzDG,CAAAA,CAAW,CAAA,EAAG7D,CAAI,CAAA,EAAG4D,CAAI,CAAA,CAAA,CACzBE,CAAAA,CAAYJ,CAAAA,CAAO,SAAA,EAAa,MAEtC,OAAO,CACL,MAAM,OAAA,CAAQlB,CAAAA,CAAoC,CAchD,IAAMlD,CAAAA,CAAKkD,CAAAA,CAAM,UAAA,CAAW,UAAA,CACtBT,CAAAA,CAAKS,CAAAA,CAAM,WAAW,SAAA,CACtBuB,CAAAA,CAAU,CACd,OAAA,CAASL,CAAAA,CAAO,MAAA,CAChB,MAAOlB,CAAAA,CAAM,KAAA,CACb,WAAA,CAAaA,CAAAA,CAAM,UAAA,CACnB,SAAA,CAAWA,CAAAA,CAAM,SAAA,CACjB,UAAA,CAAY,CACV,GAAGA,CAAAA,CAAM,UAAA,CACT,GAAI,OAAOlD,CAAAA,EAAO,QAAA,EAAYA,CAAAA,CAAK,CAAE,eAAA,CAAiBA,CAAG,EAAI,EAAC,CAC9D,GAAI,OAAOyC,CAAAA,EAAO,QAAA,EAAYA,EAAK,CAAE,GAAA,CAAKA,CAAG,CAAA,CAAI,EACnD,CACF,CAAA,CAIMiC,CAAAA,CAAM,MAAMF,CAAAA,CAAUD,CAAAA,CAAU,CACpC,MAAA,CAAQ,OACR,OAAA,CAAS,CAAE,cAAA,CAAgB,kBAAmB,CAAA,CAC9C,IAAA,CAAM,KAAK,SAAA,CAAUE,CAAO,CAAA,CAC5B,SAAA,CAAW,IAAA,CACX,MAAA,CAAQ,YAAY,OAAA,CAAQL,CAAAA,CAAO,SAAA,EAAa,GAAI,CACtD,CAAC,CAAA,CACD,GAAI,CAACM,CAAAA,CAAI,EAAA,CACP,MAAM,IAAInB,CAAAA,CACR,2BAA2BmB,CAAAA,CAAI,MAAM,CAAA,CAAA,EAAIA,CAAAA,CAAI,UAAU,CAAA,CAAA,CACvDA,EAAI,MAAA,CACJ,MAAMA,CAAAA,CAAI,IAAA,EAAK,CAAE,KAAA,CAAM,IAAG,CAAA,CAAY,CACxC,CAEJ,CACF,CACF,CClEO,SAASC,CAAAA,CAAiBP,CAAAA,CAAgD,CAC/E,IAAMI,CAAAA,CAAYJ,CAAAA,CAAO,SAAA,EAAa,MAChCQ,CAAAA,CAAYR,CAAAA,CAAO,SAAA,GAAeS,CAAAA,EAA6BA,CAAAA,CAAAA,CAErE,OAAO,CACL,MAAM,OAAA,CAAQ3B,CAAAA,CAAoC,CAChD,IAAMwB,CAAAA,CAAM,MAAMF,CAAAA,CAAUJ,CAAAA,CAAO,GAAA,CAAK,CACtC,MAAA,CAAQ,MAAA,CACR,OAAA,CAAS,CACP,cAAA,CAAgB,kBAAA,CAChB,GAAIA,CAAAA,CAAO,OAAA,EAAW,EACxB,CAAA,CACA,IAAA,CAAM,IAAA,CAAK,SAAA,CAAUQ,CAAAA,CAAU1B,CAAK,CAAC,CAAA,CACrC,SAAA,CAAW,IAAA,CACX,MAAA,CAAQ,WAAA,CAAY,OAAA,CAAQkB,EAAO,SAAA,EAAa,GAAI,CACtD,CAAC,CAAA,CACD,GAAI,CAACM,CAAAA,CAAI,EAAA,CACP,MAAM,IAAInB,CAAAA,CACR,CAAA,wBAAA,EAA2BmB,CAAAA,CAAI,MAAM,CAAA,CAAA,EAAIA,CAAAA,CAAI,UAAU,CAAA,CAAA,CACvDA,CAAAA,CAAI,MAAA,CACJ,MAAMA,CAAAA,CAAI,IAAA,EAAK,CAAE,KAAA,CAAM,IAAG,CAAA,CAAY,CACxC,CAEJ,CACF,CACF,CCtCO,SAASI,CAAAA,CACdC,CAAAA,CACkB,CAClB,OAAO,CAAE,OAAA,CAAAA,CAAQ,CACnB","file":"index.js","sourcesContent":["/**\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","/**\n * Keyed, non-reversible anonymous identifiers.\n *\n * The previous implementation was an unsalted 32-bit djb2 over `ip:userAgent`.\n * Because the user agent is emitted in plaintext on the same event, an attacker\n * held half the preimage and only had to search the IPv4 space — recovering a\n * residential IP took 75 seconds single-threaded. That is pseudonymisation, not\n * anonymisation, and it does not survive GDPR Recital 26.\n *\n * This uses HMAC-SHA-256 with a caller-supplied secret, truncated to 64 bits.\n * Web Crypto is available on Vercel Edge, Cloudflare Workers, Deno and Node 18+.\n */\n\n/** Thrown when a secret is missing or unusable. */\nexport class HashSecretError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'HashSecretError'\n }\n}\n\n/**\n * Web Crypto, or a clear error explaining why it is missing.\n *\n * `globalThis.crypto` is present by default from Node 19; on Node 18 it sat\n * behind `--experimental-global-webcrypto`. We require Node >= 20 rather than\n * shipping a `node:crypto` fallback, because a static import of a Node builtin\n * breaks bundling for the edge runtimes this library primarily targets — and\n * Node 18 reached end of life in April 2025.\n */\nfunction subtle(): SubtleCrypto {\n const c = globalThis.crypto\n if (!c?.subtle) {\n throw new HashSecretError(\n 'Web Crypto is unavailable. agent-analytics requires Node >= 20, or any ' +\n 'runtime exposing globalThis.crypto.subtle (Vercel Edge, Cloudflare ' +\n 'Workers, Deno, browsers).'\n )\n }\n return c.subtle\n}\n\n// Importing a CryptoKey costs more than the signature itself, so keep one per\n// secret. Bounded by however many secrets a process configures — realistically\n// one.\nconst KEYS = new Map<string, Promise<CryptoKey>>()\n\nfunction keyFor(secret: string): Promise<CryptoKey> {\n let k = KEYS.get(secret)\n if (!k) {\n k = subtle().importKey(\n 'raw',\n new TextEncoder().encode(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign']\n )\n KEYS.set(secret, k)\n }\n return k\n}\n\n/**\n * Hash `input` under `secret`, returning `anon_` followed by 16 hex characters\n * (64 bits — collision-free well past any realistic distinct-visitor count).\n *\n * The secret must be stable across instances for identifiers to be comparable\n * over time, and secret from anyone who can read your events: publishing it\n * makes the identifier exactly as reversible as the old implementation was.\n * Rotating it deliberately breaks continuity, which is correct behaviour for a\n * privacy-preserving id.\n */\nexport async function hashId(input: string, secret: string): Promise<string> {\n if (typeof secret !== 'string' || secret.length === 0) {\n throw new HashSecretError('hashId requires a non-empty secret')\n }\n const sig = await subtle().sign('HMAC', await keyFor(secret), new TextEncoder().encode(input))\n const bytes = new Uint8Array(sig, 0, 8)\n let out = ''\n for (const b of bytes) out += b.toString(16).padStart(2, '0')\n return 'anon_' + out\n}\n\n/**\n * Generate a random secret. Used as the default when none is configured, so the\n * privacy-preserving path is the one you get by doing nothing. Identifiers are\n * then only stable within a single instance's lifetime — set a real secret when\n * you need them comparable across instances and over time.\n */\nexport function randomSecret(): string {\n const b = new Uint8Array(32)\n subtle() // surface the same clear error if Web Crypto is missing\n globalThis.crypto.getRandomValues(b)\n let out = ''\n for (const x of b) out += x.toString(16).padStart(2, '0')\n return out\n}\n","import { classifyRequest, detectHeadless, isAiBot, isHttpClient } from './bots.js'\nimport { hashId, randomSecret } from './hash.js'\nimport type { TrackVisitOptions } from './types.js'\n\n/**\n * Fallback secret, generated once per instance. Keeps the default path\n * privacy-preserving rather than making callers opt in to safety, at the cost\n * of identifiers that only correlate within one instance's lifetime.\n */\nlet fallbackSecret: string | undefined\nlet warnedNoSecret = false\n\nfunction resolveSecret(explicit: string | undefined): string {\n if (explicit) return explicit\n const fromEnv =\n typeof process !== 'undefined' ? process.env?.AGENT_ANALYTICS_ID_SECRET : undefined\n if (fromEnv) return fromEnv\n if (!fallbackSecret) {\n fallbackSecret = randomSecret()\n if (!warnedNoSecret) {\n warnedNoSecret = true\n // Once per instance, not once per request.\n console.warn(\n '[agent-analytics] No idSecret or AGENT_ANALYTICS_ID_SECRET set. ' +\n 'Using a per-instance random secret: distinctIds will not correlate ' +\n 'across instances or deploys.'\n )\n }\n }\n return fallbackSecret\n}\n\n/**\n * Capture an event describing the incoming request. Fire-and-forget: awaits the\n * adapter but routes errors to {@link TrackVisitOptions.onError} rather than\n * letting them reach the response path. Callers typically don't await it.\n *\n * By default, captures every request so coding-agent traffic (axios, curl,\n * Electron, …) shows up alongside branded crawlers. Set `onlyBots: true` to\n * restrict capture to UAs matching {@link AI_BOT_PATTERN}.\n */\nexport async function trackVisit(req: Request, opts: TrackVisitOptions): Promise<void> {\n const userAgent = req.headers.get('user-agent') || ''\n\n const onlyBots = opts.onlyBots ?? false\n const skipBrowsers = opts.skipBrowsers ?? false\n if (onlyBots && !isAiBot(userAgent)) return\n if (skipBrowsers && !isAiBot(userAgent) && !isHttpClient(userAgent)) {\n // Not a declared bot or HTTP client — check headless heuristics.\n // Playwright-based agents (Aider, OpenCode) will pass if they're missing\n // standard browser headers. Real browsers get skipped.\n if (!detectHeadless(req).likely) return\n }\n\n try {\n let pathname = '/'\n let originFromUrl = ''\n try {\n const url = new URL(req.url)\n pathname = url.pathname\n originFromUrl = url.origin\n } catch {\n // Some runtimes hand us a relative URL; fall back to the raw string.\n pathname = req.url || '/'\n }\n const origin = opts.origin ?? originFromUrl\n\n const forwardedFor = req.headers.get('x-forwarded-for') || ''\n const ip = forwardedFor.split(',')[0]?.trim() ?? ''\n const referer = req.headers.get('referer')\n const country = opts.captureCountry\n ? req.headers.get('x-vercel-ip-country') ||\n req.headers.get('cf-ipcountry') ||\n req.headers.get('x-country-code') ||\n null\n : null\n const geo = opts.captureGeo ? extractGeo(req) : null\n const classification = classifyRequest(req)\n\n // Verification is injected rather than imported, so the published IP range\n // tables only reach bundles that actually use them. Import `verifyRequest`\n // from `@apideck/agent-analytics/verify` and pass it as `verify`.\n // May be async: Web Bot Auth fetches a signer's key directory on first\n // sight of that origin, then serves from cache.\n const verification = opts.verify ? await opts.verify(req) : null\n\n // Headless scoring only discriminates for browser-shaped UAs. On a declared\n // crawler or an HTTP client it fires on nearly everything — measured true on\n // 99% of captured events — so it reads as signal when it is noise. Omitted\n // rather than emitted as a near-constant.\n const headlessMeaningful =\n classification.kind === 'headless-likely' || classification.kind === 'browser'\n\n const distinctId = await hashId(`${ip}:${userAgent}`, resolveSecret(opts.idSecret))\n\n const event = {\n event: opts.eventName ?? 'agent_visit',\n distinctId,\n timestamp: new Date().toISOString(),\n properties: {\n // Caller properties are spread FIRST so library-computed fields always\n // win. Spreading them last let a colliding key silently overwrite\n // bot_name or is_ai_bot — corrupting the very classification they were\n // meant to annotate.\n ...opts.properties,\n $process_person_profile: false,\n $current_url: origin ? `${origin}${pathname}` : pathname,\n path: pathname,\n method: req.method,\n ...(opts.captureCountry ? { country_code: country } : {}),\n ...(geo ?? {}),\n ...(opts.captureIp ? { client_ip: ip || null } : {}),\n user_agent: userAgent,\n is_ai_bot: classification.isAiBot,\n bot_name: classification.label,\n ua_category: classification.kind,\n coding_agent_hint: classification.codingAgentHint,\n ...(headlessMeaningful\n ? {\n headless_score: classification.headless?.score ?? 0,\n headless_likely: classification.headless?.likely ?? false\n }\n : {}),\n ...(verification\n ? {\n bot_verified: verification.verified,\n bot_verification: verification.verdict,\n ...(verification.reason ? { bot_verification_reason: verification.reason } : {})\n }\n : {}),\n referer,\n source: opts.source ?? null\n }\n }\n\n await opts.analytics.capture(event)\n } catch (err) {\n // Analytics must never affect the response — but silence is how a wrong API\n // key goes unnoticed for a week, so surface it when the caller asks.\n opts.onError?.(err instanceof Error ? err : new Error(String(err)))\n }\n}\n\n// Vercel edge URL-encodes city/region (e.g. `San%20Francisco`); decode so\n// downstream consumers don't have to. Numeric fields (lat/lng) and timezone\n// pass through untouched. Headers without a value are dropped rather than\n// emitted as empty strings.\nfunction extractGeo(req: Request): Record<string, string> {\n const decode = (v: string | null) => {\n if (!v) return ''\n try {\n return decodeURIComponent(v)\n } catch {\n return v\n }\n }\n const fields: Array<[string, string]> = [\n ['region', decode(req.headers.get('x-vercel-ip-country-region'))],\n ['city', decode(req.headers.get('x-vercel-ip-city'))],\n ['latitude', req.headers.get('x-vercel-ip-latitude') ?? ''],\n ['longitude', req.headers.get('x-vercel-ip-longitude') ?? ''],\n ['timezone', req.headers.get('x-vercel-ip-timezone') ?? '']\n ]\n const out: Record<string, string> = {}\n for (const [k, v] of fields) if (v) out[k] = v\n return out\n}\n","/** Thrown when the analytics backend rejects, errors, or times out a capture. */\nexport class CaptureTransportError extends Error {\n readonly status: number | undefined\n readonly body: string | undefined\n constructor(message: string, status?: number, body?: string) {\n super(message)\n this.name = 'CaptureTransportError'\n this.status = status\n this.body = body\n }\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","import type { AnalyticsAdapter, CaptureEvent } from '../types.js'\nimport { CaptureTransportError } from '../errors.js'\n\n\nexport interface PostHogAdapterConfig {\n /** PostHog project API key (the public one used by the JS SDK). */\n apiKey: string\n /**\n * PostHog host, with or without scheme. Defaults to `https://us.i.posthog.com`.\n * Use `https://eu.i.posthog.com` for EU cloud, or your own reverse-proxy\n * domain (e.g. `https://svc.example.com`).\n */\n host?: string\n /**\n * Path on the host that accepts single-event captures. Defaults to\n * `/i/v0/e/` which is PostHog's current endpoint for this.\n */\n path?: string\n /**\n * Override the `fetch` implementation (useful for tests or custom runtimes\n * that need a pinned fetch).\n */\n fetchImpl?: typeof fetch\n /**\n * Abort the capture after this many milliseconds. Defaults to 3000. Without\n * a bound, a hung backend leaves a pending promise for the lifetime of an\n * edge invocation.\n */\n timeoutMs?: number\n}\n\n/**\n * Adapter that posts each event to the PostHog capture endpoint. Uses\n * `keepalive: true` so the request survives after a serverless response\n * returns — events aren't guaranteed (fire-and-forget), but that's the\n * trade we want to keep the hot path fast.\n */\nexport function posthogAnalytics(config: PostHogAdapterConfig): AnalyticsAdapter {\n const hostRaw = config.host ?? 'https://us.i.posthog.com'\n const base = (/^https?:\\/\\//.test(hostRaw) ? hostRaw : `https://${hostRaw}`).replace(/\\/$/, '')\n const path = (config.path ?? '/i/v0/e/').replace(/^(?!\\/)/, '/')\n const endpoint = `${base}${path}`\n const fetchImpl = config.fetchImpl ?? fetch\n\n return {\n async capture(event: CaptureEvent): Promise<void> {\n // PostHog runs its own user-agent and GeoIP enrichment, but only off its\n // canonical property names. We already carry both values under our own\n // keys, so mirroring them costs nothing and unlocks a free second opinion:\n // getTrafficCategory(), getBotName() and friends all read\n // `properties.$raw_user_agent`, and GeoIP reads `properties.$ip`.\n //\n // Without this every event arrives with traffic category `no_user_agent`\n // and PostHog's whole bot taxonomy sits dormant — which is exactly what\n // happened on ours until someone queried it.\n //\n // `$ip` is mirrored only when the caller already opted into `captureIp`.\n // Adding it otherwise would put a raw address on the event that the\n // caller deliberately kept off.\n const ua = event.properties.user_agent\n const ip = event.properties.client_ip\n const payload = {\n api_key: config.apiKey,\n event: event.event,\n distinct_id: event.distinctId,\n timestamp: event.timestamp,\n properties: {\n ...event.properties,\n ...(typeof ua === 'string' && ua ? { $raw_user_agent: ua } : {}),\n ...(typeof ip === 'string' && ip ? { $ip: ip } : {})\n }\n }\n // A 401 from a mistyped key used to look identical to success. Surface\n // it: `trackVisit` routes it to `onError` and still never throws into\n // the response path.\n const res = await fetchImpl(endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n keepalive: true,\n signal: AbortSignal.timeout(config.timeoutMs ?? 3000)\n })\n if (!res.ok) {\n throw new CaptureTransportError(\n `PostHog capture failed: ${res.status} ${res.statusText}`,\n res.status,\n await res.text().catch(() => undefined)\n )\n }\n }\n }\n}\n","import type { AnalyticsAdapter, CaptureEvent } from '../types.js'\nimport { CaptureTransportError } from '../errors.js'\n\nexport interface WebhookAdapterConfig {\n /** Destination URL that receives a POST for each event. */\n url: string\n /** Extra headers merged onto the POST (useful for shared-secret auth). */\n headers?: Record<string, string>\n /**\n * Transform the event into the exact JSON body the destination expects.\n * Defaults to sending the {@link CaptureEvent} as-is.\n */\n transform?: (event: CaptureEvent) => unknown\n /** Override the `fetch` implementation. */\n fetchImpl?: typeof fetch\n /** Abort the capture after this many milliseconds. Defaults to 3000. */\n timeoutMs?: number\n}\n\n/**\n * Adapter that POSTs each event to an arbitrary webhook URL. Keeps the\n * library analytics-backend-agnostic — use this when PostHog isn't your\n * analytics of record, or when you want to multiplex events through your\n * own ingestion layer.\n */\nexport function webhookAnalytics(config: WebhookAdapterConfig): AnalyticsAdapter {\n const fetchImpl = config.fetchImpl ?? fetch\n const transform = config.transform ?? ((e: CaptureEvent): unknown => e)\n\n return {\n async capture(event: CaptureEvent): Promise<void> {\n const res = await fetchImpl(config.url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(config.headers ?? {})\n },\n body: JSON.stringify(transform(event)),\n keepalive: true,\n signal: AbortSignal.timeout(config.timeoutMs ?? 3000)\n })\n if (!res.ok) {\n throw new CaptureTransportError(\n `Webhook capture failed: ${res.status} ${res.statusText}`,\n res.status,\n await res.text().catch(() => undefined)\n )\n }\n }\n }\n}\n","import type { AnalyticsAdapter, CaptureEvent } from '../types.js'\n\n/**\n * Escape hatch for wiring a callback directly as an analytics adapter.\n * Useful when you want to log events, pipe them through your own SDK, or\n * compose multiple adapters.\n *\n * @example\n * ```ts\n * const devAnalytics = customAnalytics((e) => console.log('[doc_view]', e))\n * ```\n */\nexport function customAnalytics(\n capture: (event: CaptureEvent) => Promise<void> | void\n): AnalyticsAdapter {\n return { capture }\n}\n"]}
package/dist/pay.cjs ADDED
@@ -0,0 +1,8 @@
1
+ 'use strict';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)}}exports.entitlementGateway=J;exports.hasPaymentPayload=L;exports.memoryEntitlementStore=Q;exports.mppxGateway=F;exports.paymentGate=Y;exports.paymentPayload=m;exports.paymentRequired=f;exports.respondToDecision=z;exports.withSettlement=N;exports.x402Gateway=_;//# sourceMappingURL=pay.cjs.map
8
+ //# sourceMappingURL=pay.cjs.map