Module: EzLogsAgent::UserAgentDetector
- Defined in:
- lib/ez_logs_agent/user_agent_detector.rb
Overview
Detects whether an HTTP request originated from an AI agent client (Claude Desktop, ChatGPT, Cursor, Windsurf, …) by inspecting the User-Agent string.
Behavior is intentionally narrow: returns vendor metadata only when the UA starts with one of the known LLM-client prefixes. Anything else returns nil — we never infer agency from weak signals.
The Next.js agent ships an identical detector (‘src/user-agent-detector.ts`) so both wire-format emitters classify the same UA the same way.
Constant Summary collapse
- PATTERNS =
Patterns are matched case-insensitively against the start of the User-Agent string. New vendors must be added in BOTH agents.
[ { prefix: "claude-", vendor: "claude", label: "Claude" }, { prefix: "anthropic-", vendor: "claude", label: "Claude" }, { prefix: "gpt-", vendor: "openai", label: "ChatGPT" }, { prefix: "chatgpt-", vendor: "openai", label: "ChatGPT" }, { prefix: "openai-", vendor: "openai", label: "ChatGPT" }, { prefix: "cursor-", vendor: "cursor", label: "Cursor" }, { prefix: "windsurf-", vendor: "windsurf", label: "Windsurf" } ].freeze
Class Method Summary collapse
-
.classify(user_agent) ⇒ Hash?
Classify a User-Agent string.
Class Method Details
.classify(user_agent) ⇒ Hash?
Classify a User-Agent string.
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
# File 'lib/ez_logs_agent/user_agent_detector.rb', line 33 def self.classify(user_agent) return nil if user_agent.nil? || user_agent.empty? ua = user_agent.downcase match = PATTERNS.find { |p| ua.start_with?(p[:prefix]) } return nil unless match { kind: "agent", vendor: match[:vendor], label: match[:label], suggested_id: "agent:#{match[:vendor]}" } rescue # Defensive: classifier must never crash the middleware. nil end |