@walkeros/server-transformer-bot 4.1.0 → 4.1.1

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.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/transformer.ts","../src/detect/ua.ts","../src/data/agents.ts","../src/detect/score.ts"],"sourcesContent":["export { transformerBot } from './transformer';\nexport type { BotSettings, BotInput, BotOutput } from './types';\nexport { transformerBot as default } from './transformer';\n","import type { Transformer } from '@walkeros/core';\nimport { getMappingValue, setByPath } from '@walkeros/core';\nimport { computeScore } from './detect/score';\nimport type { BotInput, BotOutput, BotSettings } from './types';\n\nconst DEFAULT_INPUT: Required<BotInput> = {\n userAgent: 'ingest.userAgent',\n ip: 'ingest.ip',\n acceptLanguage: 'ingest.acceptLanguage',\n acceptEncoding: 'ingest.acceptEncoding',\n secFetchSite: 'ingest.secFetchSite',\n secFetchMode: 'ingest.secFetchMode',\n secFetchDest: 'ingest.secFetchDest',\n secFetchUser: 'ingest.secFetchUser',\n secChUa: 'ingest.secChUa',\n secChUaMobile: 'ingest.secChUaMobile',\n secChUaPlatform: 'ingest.secChUaPlatform',\n};\n\nconst DEFAULT_OUTPUT: Required<BotOutput> = {\n botScore: 'user.botScore',\n agentScore: 'user.agentScore',\n agentProduct: '', // off by default\n};\n\n/**\n * Mutating dot-path setter for ingest writes.\n *\n * We can't use @walkeros/core setByPath here: it clones-and-returns (immutable),\n * but ingest is the pipeline's mutable scratch context. We need in-place writes\n * so subsequent transformers in the chain see the values.\n */\nfunction setNestedPath(\n obj: Record<string, unknown>,\n path: string,\n value: unknown,\n): void {\n const keys = path.split('.');\n let cur: Record<string, unknown> = obj;\n for (let i = 0; i < keys.length - 1; i++) {\n const k = keys[i];\n const next = cur[k];\n if (typeof next !== 'object' || next === null) cur[k] = {};\n cur = cur[k] as Record<string, unknown>;\n }\n cur[keys[keys.length - 1]] = value;\n}\n\nexport const transformerBot: Transformer.Init<\n Transformer.Types<BotSettings>\n> = (context) => {\n const { config } = context;\n const settings: BotSettings = config.settings ?? {};\n const input: Required<BotInput> = {\n ...DEFAULT_INPUT,\n ...(settings.input ?? {}),\n };\n const output: BotOutput = {\n ...DEFAULT_OUTPUT,\n ...(settings.output ?? {}),\n };\n\n return {\n // Init's input config type is Partial<Settings>; the instance config type\n // is Settings. Same cast pattern the fingerprint transformer uses.\n type: 'bot',\n config: config as Transformer.Config<Transformer.Types<BotSettings>>,\n\n async push(event, ctx) {\n const { ingest, collector } = ctx;\n const source = { event, ingest };\n\n // v1 only reads userAgent. Other input fields are reserved for v1.1\n // (header heuristics); resolved-but-unused here would be wasteful, so\n // they are intentionally not read yet.\n const uaValue = await getMappingValue(source, input.userAgent, {\n collector,\n });\n const ua = typeof uaValue === 'string' ? uaValue : '';\n const score = computeScore(ua);\n\n let nextEvent = event;\n\n const writeOutput = (path: string, value: unknown) => {\n if (!path || value === undefined) return;\n if (path.startsWith('ingest.')) {\n setNestedPath(ingest, path.slice('ingest.'.length), value);\n } else {\n nextEvent = setByPath(nextEvent, path, value);\n }\n };\n\n writeOutput(output.botScore ?? '', score.botScore);\n writeOutput(output.agentScore ?? '', score.agentScore);\n writeOutput(output.agentProduct ?? '', score.agentProduct);\n\n return { event: nextEvent };\n },\n };\n};\n","import { isbot } from 'isbot';\nimport { agents, type AgentEntry } from '../data/agents';\n\nexport interface UAResult {\n isBot: boolean;\n agent?: { product: string; purpose: AgentEntry['purpose'] };\n}\n\nexport function detectUA(ua: string): UAResult {\n const lower = ua.toLowerCase();\n const matched = agents.find((a) => lower.includes(a.match.toLowerCase()));\n return {\n isBot: !ua || isbot(ua) || matched !== undefined,\n agent: matched\n ? { product: matched.product, purpose: matched.purpose }\n : undefined,\n };\n}\n","/**\n * Curated AI agent UA-substring map (2026-Q2).\n *\n * Each entry: substring matched case-insensitively against the User-Agent,\n * a product label written to event.user.agentProduct, and the purpose category.\n *\n * Purpose semantics:\n * - 'training' — crawls for model training; usually filter from analytics\n * - 'search-index' — crawls to power AI search answers; AEO-relevant\n * - 'user-action' — fetch initiated by a human via an AI tool; often kept as traffic\n *\n * Order matters: first-hit wins. More-specific entries must precede broader ones.\n *\n * Vendor docs of record (verified 2026-05):\n * OpenAI: https://platform.openai.com/docs/bots\n * Anthropic: https://support.claude.com/en/articles/8896518\n * Perplexity: https://docs.perplexity.ai/guides/bots\n * Meta: https://developers.facebook.com/docs/sharing/webmasters/web-crawlers\n * Google: https://developers.google.com/search/docs/crawling-indexing/google-common-crawlers\n * Apple: https://support.apple.com/en-us/119829\n * DuckDuckGo: https://duckduckgo.com/duckduckbot\n * Common Crawl: https://commoncrawl.org/faq\n * Amazon: https://developer.amazon.com/amazonbot\n *\n * Community cross-reference: https://github.com/ai-robots-txt/ai.robots.txt\n *\n * Reviewed quarterly — see /workspaces/developer/docs/research/2026-05-13-bot-detection.md.\n */\nexport interface AgentEntry {\n match: string;\n product: string;\n purpose: 'training' | 'search-index' | 'user-action';\n}\n\nexport const agents: AgentEntry[] = [\n // --- OpenAI ---\n { match: 'ChatGPT-User', product: 'ChatGPT-User', purpose: 'user-action' },\n { match: 'ChatGPT-Agent', product: 'ChatGPT-Agent', purpose: 'user-action' },\n { match: 'OAI-SearchBot', product: 'OAI-SearchBot', purpose: 'search-index' },\n { match: 'GPTBot', product: 'GPTBot', purpose: 'training' },\n\n // --- Anthropic ---\n // Claude-SearchBot must precede Claude-User (defensive specificity for composite UAs)\n {\n match: 'Claude-SearchBot',\n product: 'Claude-SearchBot',\n purpose: 'search-index',\n },\n { match: 'Claude-User', product: 'Claude-User', purpose: 'user-action' },\n { match: 'Claude-Code', product: 'Claude-Code', purpose: 'user-action' },\n { match: 'ClaudeBot', product: 'ClaudeBot', purpose: 'training' },\n // Legacy: only used by older Anthropic crawlers; kept for back-compat with old logs.\n { match: 'anthropic-ai', product: 'anthropic-ai', purpose: 'training' },\n\n // --- Perplexity ---\n {\n match: 'Perplexity-User',\n product: 'Perplexity-User',\n purpose: 'user-action',\n },\n {\n match: 'PerplexityBot',\n product: 'PerplexityBot',\n purpose: 'search-index',\n },\n\n // --- Mistral ---\n {\n match: 'MistralAI-User',\n product: 'MistralAI-User',\n purpose: 'user-action',\n },\n\n // --- Meta ---\n {\n match: 'Meta-ExternalFetcher',\n product: 'Meta-ExternalFetcher',\n purpose: 'user-action',\n },\n {\n match: 'Meta-ExternalAgent',\n product: 'Meta-ExternalAgent',\n purpose: 'training',\n },\n\n // --- Google ---\n {\n match: 'Google-CloudVertexBot',\n product: 'Google-CloudVertexBot',\n purpose: 'training',\n },\n {\n match: 'Google-Extended',\n product: 'Google-Extended',\n purpose: 'training',\n },\n\n // --- Apple ---\n {\n match: 'Applebot-Extended',\n product: 'Applebot-Extended',\n purpose: 'training',\n },\n\n // --- Amazon ---\n { match: 'Amazonbot', product: 'Amazonbot', purpose: 'training' },\n\n // --- DuckDuckGo ---\n {\n match: 'DuckAssistBot',\n product: 'DuckAssistBot',\n purpose: 'user-action',\n },\n\n // --- ByteDance ---\n { match: 'Bytespider', product: 'Bytespider', purpose: 'training' },\n\n // --- Common Crawl ---\n { match: 'CCBot', product: 'CCBot', purpose: 'training' },\n];\n","import { detectUA } from './ua';\n\nexport interface ScoreResult {\n /** 0-99, higher = more bot. v1 emits discrete values: 0, 70, 80, 90, 95. */\n botScore: number;\n /**\n * 0-99, higher = more likely an AI agent. v1 emits only 0 or 95\n * (binary UA-map match). Graduated values (e.g. 70 for unverified UA\n * claim, 99 for IP-reverse-DNS verified) are planned for v1.1.\n */\n agentScore: number;\n /** Matched AI agent UA substring, when one was found. */\n agentProduct?: string;\n}\n\n/**\n * v1: UA-only.\n *\n * botScore baseline:\n * - Empty UA → 70 (real browsers rarely strip UA)\n * - AI training crawler → 95\n * - AI user-action → 90\n * - isbot true → 80\n * - Otherwise → 0\n *\n * Header heuristics (Sec-Fetch missing, Sec-CH-UA major mismatch,\n * Accept-Language stripping) are intentionally deferred to v1.1 —\n * see the README \"Not in v1\" section and the research file.\n */\nexport function computeScore(ua: string): ScoreResult {\n if (!ua) {\n return { botScore: 70, agentScore: 0, agentProduct: undefined };\n }\n\n const uaResult = detectUA(ua);\n\n let botScore = 0;\n if (uaResult.agent) {\n botScore = uaResult.agent.purpose === 'user-action' ? 90 : 95;\n } else if (uaResult.isBot) {\n botScore = 80;\n }\n\n return {\n botScore,\n agentScore: uaResult.agent ? 95 : 0,\n agentProduct: uaResult.agent?.product,\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/transformer.ts","../src/detect/ua.ts","../src/data/agents.ts","../src/detect/score.ts"],"sourcesContent":["export { transformerBot } from './transformer';\nexport type { BotSettings, BotInput, BotOutput } from './types';\nexport { transformerBot as default } from './transformer';\n","import type { Transformer } from '@walkeros/core';\nimport { getMappingValue, setByPath } from '@walkeros/core';\nimport { computeScore } from './detect/score';\nimport type { BotInput, BotOutput, BotSettings } from './types';\n\nconst DEFAULT_INPUT: Required<BotInput> = {\n userAgent: 'ingest.userAgent',\n ip: 'ingest.ip',\n acceptLanguage: 'ingest.acceptLanguage',\n acceptEncoding: 'ingest.acceptEncoding',\n secFetchSite: 'ingest.secFetchSite',\n secFetchMode: 'ingest.secFetchMode',\n secFetchDest: 'ingest.secFetchDest',\n secFetchUser: 'ingest.secFetchUser',\n secChUa: 'ingest.secChUa',\n secChUaMobile: 'ingest.secChUaMobile',\n secChUaPlatform: 'ingest.secChUaPlatform',\n};\n\nconst DEFAULT_OUTPUT: Required<BotOutput> = {\n botScore: 'user.botScore',\n agentScore: 'user.agentScore',\n agentProduct: '', // off by default\n};\n\n/**\n * Mutating dot-path setter for ingest writes.\n *\n * We can't use @walkeros/core setByPath here: it clones-and-returns (immutable),\n * but ingest is the pipeline's mutable scratch context. We need in-place writes\n * so subsequent transformers in the chain see the values.\n */\nfunction setNestedPath(\n obj: Record<string, unknown>,\n path: string,\n value: unknown,\n): void {\n const keys = path.split('.');\n let cur: Record<string, unknown> = obj;\n for (let i = 0; i < keys.length - 1; i++) {\n const k = keys[i];\n const next = cur[k];\n if (typeof next !== 'object' || next === null) cur[k] = {};\n cur = cur[k] as Record<string, unknown>;\n }\n cur[keys[keys.length - 1]] = value;\n}\n\nexport const transformerBot: Transformer.Init<\n Transformer.Types<BotSettings>\n> = (context) => {\n const { config } = context;\n const settings: BotSettings = config.settings ?? {};\n const input: Required<BotInput> = {\n ...DEFAULT_INPUT,\n ...(settings.input ?? {}),\n };\n const output: BotOutput = {\n ...DEFAULT_OUTPUT,\n ...(settings.output ?? {}),\n };\n\n return {\n // Init's input config type is Partial<Settings>; the instance config type\n // is Settings. Same cast pattern the fingerprint transformer uses.\n type: 'bot',\n config: config as Transformer.Config<Transformer.Types<BotSettings>>,\n\n async push(event, ctx) {\n const { ingest, collector } = ctx;\n const source = { event, ingest };\n\n // v1 only reads userAgent. Other input fields are reserved for v1.1\n // (header heuristics); resolved-but-unused here would be wasteful, so\n // they are intentionally not read yet.\n const uaValue = await getMappingValue(source, input.userAgent, {\n collector,\n });\n const ua = typeof uaValue === 'string' ? uaValue : '';\n const score = computeScore(ua);\n\n let nextEvent = event;\n\n const writeOutput = (path: string, value: unknown) => {\n if (!path || value === undefined) return;\n if (path.startsWith('ingest.')) {\n const subPath = path.slice('ingest.'.length);\n if (!subPath) return;\n setNestedPath(ingest, subPath, value);\n } else {\n nextEvent = setByPath(nextEvent, path, value);\n }\n };\n\n writeOutput(output.botScore ?? '', score.botScore);\n writeOutput(output.agentScore ?? '', score.agentScore);\n writeOutput(output.agentProduct ?? '', score.agentProduct);\n\n return { event: nextEvent };\n },\n };\n};\n","import { isbot } from 'isbot';\nimport { agents, type AgentEntry } from '../data/agents';\n\nexport interface UAResult {\n isBot: boolean;\n agent?: { product: string; purpose: AgentEntry['purpose'] };\n}\n\nexport function detectUA(ua: string): UAResult {\n const lower = ua.toLowerCase();\n const matched = agents.find((a) => lower.includes(a.match.toLowerCase()));\n return {\n isBot: !ua || isbot(ua) || matched !== undefined,\n agent: matched\n ? { product: matched.product, purpose: matched.purpose }\n : undefined,\n };\n}\n","/**\n * Curated AI agent UA-substring map (2026-Q2).\n *\n * Each entry: substring matched case-insensitively against the User-Agent,\n * a product label written to event.user.agentProduct, and the purpose category.\n *\n * Purpose semantics:\n * - 'training' — crawls for model training; usually filter from analytics\n * - 'search-index' — crawls to power AI search answers; AEO-relevant\n * - 'user-action' — fetch initiated by a human via an AI tool; often kept as traffic\n *\n * Order matters: first-hit wins. More-specific entries must precede broader ones.\n *\n * Vendor docs of record (verified 2026-05):\n * OpenAI: https://platform.openai.com/docs/bots\n * Anthropic: https://support.claude.com/en/articles/8896518\n * Perplexity: https://docs.perplexity.ai/guides/bots\n * Meta: https://developers.facebook.com/docs/sharing/webmasters/web-crawlers\n * Google: https://developers.google.com/search/docs/crawling-indexing/google-common-crawlers\n * Apple: https://support.apple.com/en-us/119829\n * DuckDuckGo: https://duckduckgo.com/duckduckbot\n * Common Crawl: https://commoncrawl.org/faq\n * Amazon: https://developer.amazon.com/amazonbot\n *\n * Community cross-reference: https://github.com/ai-robots-txt/ai.robots.txt\n *\n * Reviewed quarterly. Source-of-truth references are listed above.\n */\nexport interface AgentEntry {\n match: string;\n product: string;\n purpose: 'training' | 'search-index' | 'user-action';\n}\n\nexport const agents: AgentEntry[] = [\n // --- OpenAI ---\n { match: 'ChatGPT-User', product: 'ChatGPT-User', purpose: 'user-action' },\n { match: 'ChatGPT-Agent', product: 'ChatGPT-Agent', purpose: 'user-action' },\n { match: 'OAI-SearchBot', product: 'OAI-SearchBot', purpose: 'search-index' },\n { match: 'GPTBot', product: 'GPTBot', purpose: 'training' },\n\n // --- Anthropic ---\n // Claude-SearchBot must precede Claude-User (defensive specificity for composite UAs)\n {\n match: 'Claude-SearchBot',\n product: 'Claude-SearchBot',\n purpose: 'search-index',\n },\n { match: 'Claude-User', product: 'Claude-User', purpose: 'user-action' },\n { match: 'Claude-Code', product: 'Claude-Code', purpose: 'user-action' },\n { match: 'ClaudeBot', product: 'ClaudeBot', purpose: 'training' },\n // Legacy: only used by older Anthropic crawlers; kept for back-compat with old logs.\n { match: 'anthropic-ai', product: 'anthropic-ai', purpose: 'training' },\n\n // --- Perplexity ---\n {\n match: 'Perplexity-User',\n product: 'Perplexity-User',\n purpose: 'user-action',\n },\n {\n match: 'PerplexityBot',\n product: 'PerplexityBot',\n purpose: 'search-index',\n },\n\n // --- Mistral ---\n {\n match: 'MistralAI-User',\n product: 'MistralAI-User',\n purpose: 'user-action',\n },\n\n // --- Meta ---\n {\n match: 'Meta-ExternalFetcher',\n product: 'Meta-ExternalFetcher',\n purpose: 'user-action',\n },\n {\n match: 'Meta-ExternalAgent',\n product: 'Meta-ExternalAgent',\n purpose: 'training',\n },\n\n // --- Google ---\n {\n match: 'Google-CloudVertexBot',\n product: 'Google-CloudVertexBot',\n purpose: 'training',\n },\n {\n match: 'Google-Extended',\n product: 'Google-Extended',\n purpose: 'training',\n },\n\n // --- Apple ---\n {\n match: 'Applebot-Extended',\n product: 'Applebot-Extended',\n purpose: 'training',\n },\n\n // --- Amazon ---\n { match: 'Amazonbot', product: 'Amazonbot', purpose: 'training' },\n\n // --- DuckDuckGo ---\n {\n match: 'DuckAssistBot',\n product: 'DuckAssistBot',\n purpose: 'user-action',\n },\n\n // --- ByteDance ---\n { match: 'Bytespider', product: 'Bytespider', purpose: 'training' },\n\n // --- Common Crawl ---\n { match: 'CCBot', product: 'CCBot', purpose: 'training' },\n];\n","import { detectUA } from './ua';\n\nexport interface ScoreResult {\n /** 0-99, higher = more bot. v1 emits discrete values: 0, 70, 80, 90, 95. */\n botScore: number;\n /**\n * 0-99, higher = more likely an AI agent. v1 emits only 0 or 95\n * (binary UA-map match). Graduated values (e.g. 70 for unverified UA\n * claim, 99 for IP-reverse-DNS verified) are planned for v1.1.\n */\n agentScore: number;\n /** Matched AI agent UA substring, when one was found. */\n agentProduct?: string;\n}\n\n/**\n * v1: UA-only.\n *\n * botScore baseline:\n * - Empty UA → 70 (real browsers rarely strip UA)\n * - AI training crawler → 95\n * - AI user-action → 90\n * - isbot true → 80\n * - Otherwise → 0\n *\n * Header heuristics (Sec-Fetch missing, Sec-CH-UA major mismatch,\n * Accept-Language stripping) are intentionally deferred to v1.1 —\n * see the README \"Not in v1\" section and the research file.\n */\nexport function computeScore(ua: string): ScoreResult {\n if (!ua) {\n return { botScore: 70, agentScore: 0, agentProduct: undefined };\n }\n\n const uaResult = detectUA(ua);\n\n let botScore = 0;\n if (uaResult.agent) {\n botScore = uaResult.agent.purpose === 'user-action' ? 90 : 95;\n } else if (uaResult.isBot) {\n botScore = 80;\n }\n\n return {\n botScore,\n agentScore: uaResult.agent ? 95 : 0,\n agentProduct: uaResult.agent?.product,\n };\n}\n"],"mappings":"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","names":[]}
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{getMappingValue as t,setByPath as e}from"@walkeros/core";import{isbot as o}from"isbot";var r=[{match:"ChatGPT-User",product:"ChatGPT-User",purpose:"user-action"},{match:"ChatGPT-Agent",product:"ChatGPT-Agent",purpose:"user-action"},{match:"OAI-SearchBot",product:"OAI-SearchBot",purpose:"search-index"},{match:"GPTBot",product:"GPTBot",purpose:"training"},{match:"Claude-SearchBot",product:"Claude-SearchBot",purpose:"search-index"},{match:"Claude-User",product:"Claude-User",purpose:"user-action"},{match:"Claude-Code",product:"Claude-Code",purpose:"user-action"},{match:"ClaudeBot",product:"ClaudeBot",purpose:"training"},{match:"anthropic-ai",product:"anthropic-ai",purpose:"training"},{match:"Perplexity-User",product:"Perplexity-User",purpose:"user-action"},{match:"PerplexityBot",product:"PerplexityBot",purpose:"search-index"},{match:"MistralAI-User",product:"MistralAI-User",purpose:"user-action"},{match:"Meta-ExternalFetcher",product:"Meta-ExternalFetcher",purpose:"user-action"},{match:"Meta-ExternalAgent",product:"Meta-ExternalAgent",purpose:"training"},{match:"Google-CloudVertexBot",product:"Google-CloudVertexBot",purpose:"training"},{match:"Google-Extended",product:"Google-Extended",purpose:"training"},{match:"Applebot-Extended",product:"Applebot-Extended",purpose:"training"},{match:"Amazonbot",product:"Amazonbot",purpose:"training"},{match:"DuckAssistBot",product:"DuckAssistBot",purpose:"user-action"},{match:"Bytespider",product:"Bytespider",purpose:"training"},{match:"CCBot",product:"CCBot",purpose:"training"}];function c(t){if(!t)return{botScore:70,agentScore:0,agentProduct:void 0};const e=function(t){const e=t.toLowerCase(),c=r.find(t=>e.includes(t.match.toLowerCase()));return{isBot:!t||o(t)||void 0!==c,agent:c?{product:c.product,purpose:c.purpose}:void 0}}(t);let c=0;return e.agent?c="user-action"===e.agent.purpose?90:95:e.isBot&&(c=80),{botScore:c,agentScore:e.agent?95:0,agentProduct:e.agent?.product}}var a={userAgent:"ingest.userAgent",ip:"ingest.ip",acceptLanguage:"ingest.acceptLanguage",acceptEncoding:"ingest.acceptEncoding",secFetchSite:"ingest.secFetchSite",secFetchMode:"ingest.secFetchMode",secFetchDest:"ingest.secFetchDest",secFetchUser:"ingest.secFetchUser",secChUa:"ingest.secChUa",secChUaMobile:"ingest.secChUaMobile",secChUaPlatform:"ingest.secChUaPlatform"},n={botScore:"user.botScore",agentScore:"user.agentScore",agentProduct:""};var s=o=>{const{config:r}=o,s=r.settings??{},p={...a,...s.input??{}},u={...n,...s.output??{}};return{type:"bot",config:r,async push(o,r){const{ingest:a,collector:n}=r,s={event:o,ingest:a},i=await t(s,p.userAgent,{collector:n}),d=c("string"==typeof i?i:"");let g=o;const h=(t,o)=>{t&&void 0!==o&&(t.startsWith("ingest.")?function(t,e,o){const r=e.split(".");let c=t;for(let t=0;t<r.length-1;t++){const e=r[t],o=c[e];"object"==typeof o&&null!==o||(c[e]={}),c=c[e]}c[r[r.length-1]]=o}(a,t.slice(7),o):g=e(g,t,o))};return h(u.botScore??"",d.botScore),h(u.agentScore??"",d.agentScore),h(u.agentProduct??"",d.agentProduct),{event:g}}}};export{s as default,s as transformerBot};//# sourceMappingURL=index.mjs.map
1
+ import{getMappingValue as t,setByPath as e}from"@walkeros/core";import{isbot as o}from"isbot";var r=[{match:"ChatGPT-User",product:"ChatGPT-User",purpose:"user-action"},{match:"ChatGPT-Agent",product:"ChatGPT-Agent",purpose:"user-action"},{match:"OAI-SearchBot",product:"OAI-SearchBot",purpose:"search-index"},{match:"GPTBot",product:"GPTBot",purpose:"training"},{match:"Claude-SearchBot",product:"Claude-SearchBot",purpose:"search-index"},{match:"Claude-User",product:"Claude-User",purpose:"user-action"},{match:"Claude-Code",product:"Claude-Code",purpose:"user-action"},{match:"ClaudeBot",product:"ClaudeBot",purpose:"training"},{match:"anthropic-ai",product:"anthropic-ai",purpose:"training"},{match:"Perplexity-User",product:"Perplexity-User",purpose:"user-action"},{match:"PerplexityBot",product:"PerplexityBot",purpose:"search-index"},{match:"MistralAI-User",product:"MistralAI-User",purpose:"user-action"},{match:"Meta-ExternalFetcher",product:"Meta-ExternalFetcher",purpose:"user-action"},{match:"Meta-ExternalAgent",product:"Meta-ExternalAgent",purpose:"training"},{match:"Google-CloudVertexBot",product:"Google-CloudVertexBot",purpose:"training"},{match:"Google-Extended",product:"Google-Extended",purpose:"training"},{match:"Applebot-Extended",product:"Applebot-Extended",purpose:"training"},{match:"Amazonbot",product:"Amazonbot",purpose:"training"},{match:"DuckAssistBot",product:"DuckAssistBot",purpose:"user-action"},{match:"Bytespider",product:"Bytespider",purpose:"training"},{match:"CCBot",product:"CCBot",purpose:"training"}];function c(t){if(!t)return{botScore:70,agentScore:0,agentProduct:void 0};const e=function(t){const e=t.toLowerCase(),c=r.find(t=>e.includes(t.match.toLowerCase()));return{isBot:!t||o(t)||void 0!==c,agent:c?{product:c.product,purpose:c.purpose}:void 0}}(t);let c=0;return e.agent?c="user-action"===e.agent.purpose?90:95:e.isBot&&(c=80),{botScore:c,agentScore:e.agent?95:0,agentProduct:e.agent?.product}}var a={userAgent:"ingest.userAgent",ip:"ingest.ip",acceptLanguage:"ingest.acceptLanguage",acceptEncoding:"ingest.acceptEncoding",secFetchSite:"ingest.secFetchSite",secFetchMode:"ingest.secFetchMode",secFetchDest:"ingest.secFetchDest",secFetchUser:"ingest.secFetchUser",secChUa:"ingest.secChUa",secChUaMobile:"ingest.secChUaMobile",secChUaPlatform:"ingest.secChUaPlatform"},n={botScore:"user.botScore",agentScore:"user.agentScore",agentProduct:""};var s=o=>{const{config:r}=o,s=r.settings??{},p={...a,...s.input??{}},u={...n,...s.output??{}};return{type:"bot",config:r,async push(o,r){const{ingest:a,collector:n}=r,s={event:o,ingest:a},i=await t(s,p.userAgent,{collector:n}),d=c("string"==typeof i?i:"");let g=o;const h=(t,o)=>{if(t&&void 0!==o)if(t.startsWith("ingest.")){const e=t.slice(7);if(!e)return;!function(t,e,o){const r=e.split(".");let c=t;for(let t=0;t<r.length-1;t++){const e=r[t],o=c[e];"object"==typeof o&&null!==o||(c[e]={}),c=c[e]}c[r[r.length-1]]=o}(a,e,o)}else g=e(g,t,o)};return h(u.botScore??"",d.botScore),h(u.agentScore??"",d.agentScore),h(u.agentProduct??"",d.agentProduct),{event:g}}}};export{s as default,s as transformerBot};//# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/transformer.ts","../src/detect/ua.ts","../src/data/agents.ts","../src/detect/score.ts"],"sourcesContent":["import type { Transformer } from '@walkeros/core';\nimport { getMappingValue, setByPath } from '@walkeros/core';\nimport { computeScore } from './detect/score';\nimport type { BotInput, BotOutput, BotSettings } from './types';\n\nconst DEFAULT_INPUT: Required<BotInput> = {\n userAgent: 'ingest.userAgent',\n ip: 'ingest.ip',\n acceptLanguage: 'ingest.acceptLanguage',\n acceptEncoding: 'ingest.acceptEncoding',\n secFetchSite: 'ingest.secFetchSite',\n secFetchMode: 'ingest.secFetchMode',\n secFetchDest: 'ingest.secFetchDest',\n secFetchUser: 'ingest.secFetchUser',\n secChUa: 'ingest.secChUa',\n secChUaMobile: 'ingest.secChUaMobile',\n secChUaPlatform: 'ingest.secChUaPlatform',\n};\n\nconst DEFAULT_OUTPUT: Required<BotOutput> = {\n botScore: 'user.botScore',\n agentScore: 'user.agentScore',\n agentProduct: '', // off by default\n};\n\n/**\n * Mutating dot-path setter for ingest writes.\n *\n * We can't use @walkeros/core setByPath here: it clones-and-returns (immutable),\n * but ingest is the pipeline's mutable scratch context. We need in-place writes\n * so subsequent transformers in the chain see the values.\n */\nfunction setNestedPath(\n obj: Record<string, unknown>,\n path: string,\n value: unknown,\n): void {\n const keys = path.split('.');\n let cur: Record<string, unknown> = obj;\n for (let i = 0; i < keys.length - 1; i++) {\n const k = keys[i];\n const next = cur[k];\n if (typeof next !== 'object' || next === null) cur[k] = {};\n cur = cur[k] as Record<string, unknown>;\n }\n cur[keys[keys.length - 1]] = value;\n}\n\nexport const transformerBot: Transformer.Init<\n Transformer.Types<BotSettings>\n> = (context) => {\n const { config } = context;\n const settings: BotSettings = config.settings ?? {};\n const input: Required<BotInput> = {\n ...DEFAULT_INPUT,\n ...(settings.input ?? {}),\n };\n const output: BotOutput = {\n ...DEFAULT_OUTPUT,\n ...(settings.output ?? {}),\n };\n\n return {\n // Init's input config type is Partial<Settings>; the instance config type\n // is Settings. Same cast pattern the fingerprint transformer uses.\n type: 'bot',\n config: config as Transformer.Config<Transformer.Types<BotSettings>>,\n\n async push(event, ctx) {\n const { ingest, collector } = ctx;\n const source = { event, ingest };\n\n // v1 only reads userAgent. Other input fields are reserved for v1.1\n // (header heuristics); resolved-but-unused here would be wasteful, so\n // they are intentionally not read yet.\n const uaValue = await getMappingValue(source, input.userAgent, {\n collector,\n });\n const ua = typeof uaValue === 'string' ? uaValue : '';\n const score = computeScore(ua);\n\n let nextEvent = event;\n\n const writeOutput = (path: string, value: unknown) => {\n if (!path || value === undefined) return;\n if (path.startsWith('ingest.')) {\n setNestedPath(ingest, path.slice('ingest.'.length), value);\n } else {\n nextEvent = setByPath(nextEvent, path, value);\n }\n };\n\n writeOutput(output.botScore ?? '', score.botScore);\n writeOutput(output.agentScore ?? '', score.agentScore);\n writeOutput(output.agentProduct ?? '', score.agentProduct);\n\n return { event: nextEvent };\n },\n };\n};\n","import { isbot } from 'isbot';\nimport { agents, type AgentEntry } from '../data/agents';\n\nexport interface UAResult {\n isBot: boolean;\n agent?: { product: string; purpose: AgentEntry['purpose'] };\n}\n\nexport function detectUA(ua: string): UAResult {\n const lower = ua.toLowerCase();\n const matched = agents.find((a) => lower.includes(a.match.toLowerCase()));\n return {\n isBot: !ua || isbot(ua) || matched !== undefined,\n agent: matched\n ? { product: matched.product, purpose: matched.purpose }\n : undefined,\n };\n}\n","/**\n * Curated AI agent UA-substring map (2026-Q2).\n *\n * Each entry: substring matched case-insensitively against the User-Agent,\n * a product label written to event.user.agentProduct, and the purpose category.\n *\n * Purpose semantics:\n * - 'training' — crawls for model training; usually filter from analytics\n * - 'search-index' — crawls to power AI search answers; AEO-relevant\n * - 'user-action' — fetch initiated by a human via an AI tool; often kept as traffic\n *\n * Order matters: first-hit wins. More-specific entries must precede broader ones.\n *\n * Vendor docs of record (verified 2026-05):\n * OpenAI: https://platform.openai.com/docs/bots\n * Anthropic: https://support.claude.com/en/articles/8896518\n * Perplexity: https://docs.perplexity.ai/guides/bots\n * Meta: https://developers.facebook.com/docs/sharing/webmasters/web-crawlers\n * Google: https://developers.google.com/search/docs/crawling-indexing/google-common-crawlers\n * Apple: https://support.apple.com/en-us/119829\n * DuckDuckGo: https://duckduckgo.com/duckduckbot\n * Common Crawl: https://commoncrawl.org/faq\n * Amazon: https://developer.amazon.com/amazonbot\n *\n * Community cross-reference: https://github.com/ai-robots-txt/ai.robots.txt\n *\n * Reviewed quarterly — see /workspaces/developer/docs/research/2026-05-13-bot-detection.md.\n */\nexport interface AgentEntry {\n match: string;\n product: string;\n purpose: 'training' | 'search-index' | 'user-action';\n}\n\nexport const agents: AgentEntry[] = [\n // --- OpenAI ---\n { match: 'ChatGPT-User', product: 'ChatGPT-User', purpose: 'user-action' },\n { match: 'ChatGPT-Agent', product: 'ChatGPT-Agent', purpose: 'user-action' },\n { match: 'OAI-SearchBot', product: 'OAI-SearchBot', purpose: 'search-index' },\n { match: 'GPTBot', product: 'GPTBot', purpose: 'training' },\n\n // --- Anthropic ---\n // Claude-SearchBot must precede Claude-User (defensive specificity for composite UAs)\n {\n match: 'Claude-SearchBot',\n product: 'Claude-SearchBot',\n purpose: 'search-index',\n },\n { match: 'Claude-User', product: 'Claude-User', purpose: 'user-action' },\n { match: 'Claude-Code', product: 'Claude-Code', purpose: 'user-action' },\n { match: 'ClaudeBot', product: 'ClaudeBot', purpose: 'training' },\n // Legacy: only used by older Anthropic crawlers; kept for back-compat with old logs.\n { match: 'anthropic-ai', product: 'anthropic-ai', purpose: 'training' },\n\n // --- Perplexity ---\n {\n match: 'Perplexity-User',\n product: 'Perplexity-User',\n purpose: 'user-action',\n },\n {\n match: 'PerplexityBot',\n product: 'PerplexityBot',\n purpose: 'search-index',\n },\n\n // --- Mistral ---\n {\n match: 'MistralAI-User',\n product: 'MistralAI-User',\n purpose: 'user-action',\n },\n\n // --- Meta ---\n {\n match: 'Meta-ExternalFetcher',\n product: 'Meta-ExternalFetcher',\n purpose: 'user-action',\n },\n {\n match: 'Meta-ExternalAgent',\n product: 'Meta-ExternalAgent',\n purpose: 'training',\n },\n\n // --- Google ---\n {\n match: 'Google-CloudVertexBot',\n product: 'Google-CloudVertexBot',\n purpose: 'training',\n },\n {\n match: 'Google-Extended',\n product: 'Google-Extended',\n purpose: 'training',\n },\n\n // --- Apple ---\n {\n match: 'Applebot-Extended',\n product: 'Applebot-Extended',\n purpose: 'training',\n },\n\n // --- Amazon ---\n { match: 'Amazonbot', product: 'Amazonbot', purpose: 'training' },\n\n // --- DuckDuckGo ---\n {\n match: 'DuckAssistBot',\n product: 'DuckAssistBot',\n purpose: 'user-action',\n },\n\n // --- ByteDance ---\n { match: 'Bytespider', product: 'Bytespider', purpose: 'training' },\n\n // --- Common Crawl ---\n { match: 'CCBot', product: 'CCBot', purpose: 'training' },\n];\n","import { detectUA } from './ua';\n\nexport interface ScoreResult {\n /** 0-99, higher = more bot. v1 emits discrete values: 0, 70, 80, 90, 95. */\n botScore: number;\n /**\n * 0-99, higher = more likely an AI agent. v1 emits only 0 or 95\n * (binary UA-map match). Graduated values (e.g. 70 for unverified UA\n * claim, 99 for IP-reverse-DNS verified) are planned for v1.1.\n */\n agentScore: number;\n /** Matched AI agent UA substring, when one was found. */\n agentProduct?: string;\n}\n\n/**\n * v1: UA-only.\n *\n * botScore baseline:\n * - Empty UA → 70 (real browsers rarely strip UA)\n * - AI training crawler → 95\n * - AI user-action → 90\n * - isbot true → 80\n * - Otherwise → 0\n *\n * Header heuristics (Sec-Fetch missing, Sec-CH-UA major mismatch,\n * Accept-Language stripping) are intentionally deferred to v1.1 —\n * see the README \"Not in v1\" section and the research file.\n */\nexport function computeScore(ua: string): ScoreResult {\n if (!ua) {\n return { botScore: 70, agentScore: 0, agentProduct: undefined };\n }\n\n const uaResult = detectUA(ua);\n\n let botScore = 0;\n if (uaResult.agent) {\n botScore = uaResult.agent.purpose === 'user-action' ? 90 : 95;\n } else if (uaResult.isBot) {\n botScore = 80;\n }\n\n return {\n botScore,\n agentScore: uaResult.agent ? 95 : 0,\n agentProduct: uaResult.agent?.product,\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/transformer.ts","../src/detect/ua.ts","../src/data/agents.ts","../src/detect/score.ts"],"sourcesContent":["import type { Transformer } from '@walkeros/core';\nimport { getMappingValue, setByPath } from '@walkeros/core';\nimport { computeScore } from './detect/score';\nimport type { BotInput, BotOutput, BotSettings } from './types';\n\nconst DEFAULT_INPUT: Required<BotInput> = {\n userAgent: 'ingest.userAgent',\n ip: 'ingest.ip',\n acceptLanguage: 'ingest.acceptLanguage',\n acceptEncoding: 'ingest.acceptEncoding',\n secFetchSite: 'ingest.secFetchSite',\n secFetchMode: 'ingest.secFetchMode',\n secFetchDest: 'ingest.secFetchDest',\n secFetchUser: 'ingest.secFetchUser',\n secChUa: 'ingest.secChUa',\n secChUaMobile: 'ingest.secChUaMobile',\n secChUaPlatform: 'ingest.secChUaPlatform',\n};\n\nconst DEFAULT_OUTPUT: Required<BotOutput> = {\n botScore: 'user.botScore',\n agentScore: 'user.agentScore',\n agentProduct: '', // off by default\n};\n\n/**\n * Mutating dot-path setter for ingest writes.\n *\n * We can't use @walkeros/core setByPath here: it clones-and-returns (immutable),\n * but ingest is the pipeline's mutable scratch context. We need in-place writes\n * so subsequent transformers in the chain see the values.\n */\nfunction setNestedPath(\n obj: Record<string, unknown>,\n path: string,\n value: unknown,\n): void {\n const keys = path.split('.');\n let cur: Record<string, unknown> = obj;\n for (let i = 0; i < keys.length - 1; i++) {\n const k = keys[i];\n const next = cur[k];\n if (typeof next !== 'object' || next === null) cur[k] = {};\n cur = cur[k] as Record<string, unknown>;\n }\n cur[keys[keys.length - 1]] = value;\n}\n\nexport const transformerBot: Transformer.Init<\n Transformer.Types<BotSettings>\n> = (context) => {\n const { config } = context;\n const settings: BotSettings = config.settings ?? {};\n const input: Required<BotInput> = {\n ...DEFAULT_INPUT,\n ...(settings.input ?? {}),\n };\n const output: BotOutput = {\n ...DEFAULT_OUTPUT,\n ...(settings.output ?? {}),\n };\n\n return {\n // Init's input config type is Partial<Settings>; the instance config type\n // is Settings. Same cast pattern the fingerprint transformer uses.\n type: 'bot',\n config: config as Transformer.Config<Transformer.Types<BotSettings>>,\n\n async push(event, ctx) {\n const { ingest, collector } = ctx;\n const source = { event, ingest };\n\n // v1 only reads userAgent. Other input fields are reserved for v1.1\n // (header heuristics); resolved-but-unused here would be wasteful, so\n // they are intentionally not read yet.\n const uaValue = await getMappingValue(source, input.userAgent, {\n collector,\n });\n const ua = typeof uaValue === 'string' ? uaValue : '';\n const score = computeScore(ua);\n\n let nextEvent = event;\n\n const writeOutput = (path: string, value: unknown) => {\n if (!path || value === undefined) return;\n if (path.startsWith('ingest.')) {\n const subPath = path.slice('ingest.'.length);\n if (!subPath) return;\n setNestedPath(ingest, subPath, value);\n } else {\n nextEvent = setByPath(nextEvent, path, value);\n }\n };\n\n writeOutput(output.botScore ?? '', score.botScore);\n writeOutput(output.agentScore ?? '', score.agentScore);\n writeOutput(output.agentProduct ?? '', score.agentProduct);\n\n return { event: nextEvent };\n },\n };\n};\n","import { isbot } from 'isbot';\nimport { agents, type AgentEntry } from '../data/agents';\n\nexport interface UAResult {\n isBot: boolean;\n agent?: { product: string; purpose: AgentEntry['purpose'] };\n}\n\nexport function detectUA(ua: string): UAResult {\n const lower = ua.toLowerCase();\n const matched = agents.find((a) => lower.includes(a.match.toLowerCase()));\n return {\n isBot: !ua || isbot(ua) || matched !== undefined,\n agent: matched\n ? { product: matched.product, purpose: matched.purpose }\n : undefined,\n };\n}\n","/**\n * Curated AI agent UA-substring map (2026-Q2).\n *\n * Each entry: substring matched case-insensitively against the User-Agent,\n * a product label written to event.user.agentProduct, and the purpose category.\n *\n * Purpose semantics:\n * - 'training' — crawls for model training; usually filter from analytics\n * - 'search-index' — crawls to power AI search answers; AEO-relevant\n * - 'user-action' — fetch initiated by a human via an AI tool; often kept as traffic\n *\n * Order matters: first-hit wins. More-specific entries must precede broader ones.\n *\n * Vendor docs of record (verified 2026-05):\n * OpenAI: https://platform.openai.com/docs/bots\n * Anthropic: https://support.claude.com/en/articles/8896518\n * Perplexity: https://docs.perplexity.ai/guides/bots\n * Meta: https://developers.facebook.com/docs/sharing/webmasters/web-crawlers\n * Google: https://developers.google.com/search/docs/crawling-indexing/google-common-crawlers\n * Apple: https://support.apple.com/en-us/119829\n * DuckDuckGo: https://duckduckgo.com/duckduckbot\n * Common Crawl: https://commoncrawl.org/faq\n * Amazon: https://developer.amazon.com/amazonbot\n *\n * Community cross-reference: https://github.com/ai-robots-txt/ai.robots.txt\n *\n * Reviewed quarterly. Source-of-truth references are listed above.\n */\nexport interface AgentEntry {\n match: string;\n product: string;\n purpose: 'training' | 'search-index' | 'user-action';\n}\n\nexport const agents: AgentEntry[] = [\n // --- OpenAI ---\n { match: 'ChatGPT-User', product: 'ChatGPT-User', purpose: 'user-action' },\n { match: 'ChatGPT-Agent', product: 'ChatGPT-Agent', purpose: 'user-action' },\n { match: 'OAI-SearchBot', product: 'OAI-SearchBot', purpose: 'search-index' },\n { match: 'GPTBot', product: 'GPTBot', purpose: 'training' },\n\n // --- Anthropic ---\n // Claude-SearchBot must precede Claude-User (defensive specificity for composite UAs)\n {\n match: 'Claude-SearchBot',\n product: 'Claude-SearchBot',\n purpose: 'search-index',\n },\n { match: 'Claude-User', product: 'Claude-User', purpose: 'user-action' },\n { match: 'Claude-Code', product: 'Claude-Code', purpose: 'user-action' },\n { match: 'ClaudeBot', product: 'ClaudeBot', purpose: 'training' },\n // Legacy: only used by older Anthropic crawlers; kept for back-compat with old logs.\n { match: 'anthropic-ai', product: 'anthropic-ai', purpose: 'training' },\n\n // --- Perplexity ---\n {\n match: 'Perplexity-User',\n product: 'Perplexity-User',\n purpose: 'user-action',\n },\n {\n match: 'PerplexityBot',\n product: 'PerplexityBot',\n purpose: 'search-index',\n },\n\n // --- Mistral ---\n {\n match: 'MistralAI-User',\n product: 'MistralAI-User',\n purpose: 'user-action',\n },\n\n // --- Meta ---\n {\n match: 'Meta-ExternalFetcher',\n product: 'Meta-ExternalFetcher',\n purpose: 'user-action',\n },\n {\n match: 'Meta-ExternalAgent',\n product: 'Meta-ExternalAgent',\n purpose: 'training',\n },\n\n // --- Google ---\n {\n match: 'Google-CloudVertexBot',\n product: 'Google-CloudVertexBot',\n purpose: 'training',\n },\n {\n match: 'Google-Extended',\n product: 'Google-Extended',\n purpose: 'training',\n },\n\n // --- Apple ---\n {\n match: 'Applebot-Extended',\n product: 'Applebot-Extended',\n purpose: 'training',\n },\n\n // --- Amazon ---\n { match: 'Amazonbot', product: 'Amazonbot', purpose: 'training' },\n\n // --- DuckDuckGo ---\n {\n match: 'DuckAssistBot',\n product: 'DuckAssistBot',\n purpose: 'user-action',\n },\n\n // --- ByteDance ---\n { match: 'Bytespider', product: 'Bytespider', purpose: 'training' },\n\n // --- Common Crawl ---\n { match: 'CCBot', product: 'CCBot', purpose: 'training' },\n];\n","import { detectUA } from './ua';\n\nexport interface ScoreResult {\n /** 0-99, higher = more bot. v1 emits discrete values: 0, 70, 80, 90, 95. */\n botScore: number;\n /**\n * 0-99, higher = more likely an AI agent. v1 emits only 0 or 95\n * (binary UA-map match). Graduated values (e.g. 70 for unverified UA\n * claim, 99 for IP-reverse-DNS verified) are planned for v1.1.\n */\n agentScore: number;\n /** Matched AI agent UA substring, when one was found. */\n agentProduct?: string;\n}\n\n/**\n * v1: UA-only.\n *\n * botScore baseline:\n * - Empty UA → 70 (real browsers rarely strip UA)\n * - AI training crawler → 95\n * - AI user-action → 90\n * - isbot true → 80\n * - Otherwise → 0\n *\n * Header heuristics (Sec-Fetch missing, Sec-CH-UA major mismatch,\n * Accept-Language stripping) are intentionally deferred to v1.1 —\n * see the README \"Not in v1\" section and the research file.\n */\nexport function computeScore(ua: string): ScoreResult {\n if (!ua) {\n return { botScore: 70, agentScore: 0, agentProduct: undefined };\n }\n\n const uaResult = detectUA(ua);\n\n let botScore = 0;\n if (uaResult.agent) {\n botScore = uaResult.agent.purpose === 'user-action' ? 90 : 95;\n } else if (uaResult.isBot) {\n botScore = 80;\n }\n\n return {\n botScore,\n agentScore: uaResult.agent ? 95 : 0,\n agentProduct: uaResult.agent?.product,\n };\n}\n"],"mappings":"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","names":[]}