1010 * @module
1111 */
1212
13+ import { sanitize } from './clientinfo' ;
14+
1315interface KnownAgent {
1416 readonly envVar : string ;
1517 readonly product : string ;
1618}
1719
18- // Name of the agents.md standard env var. When set to a value that no
19- // known agent recognizes, detection falls back to "unknown".
20+ // Name of the agents.md standard env var.
2021const AGENT_ENV_VAR = 'AGENT' ;
2122
22- // Canonical list of AI coding agents. Keep this list in sync with the
23- // Go, Java, and Python SDKs. Agents are listed alphabetically by product
24- // name.
23+ // Name of the Vercel @vercel /detect-agent convention env var. It serves
24+ // the same purpose as AGENT_ENV_VAR; agentEnvFallback consults it only when
25+ // AGENT_ENV_VAR is unset or empty.
26+ const AI_AGENT_ENV_VAR = 'AI_AGENT' ;
27+
28+ // Caps fallback values to keep the user-agent bounded. Explicit-matcher
29+ // products are short by construction; only the fallback path can carry
30+ // arbitrary lengths.
31+ const MAX_AGENT_FALLBACK_LEN = 64 ;
32+
33+ // Canonical list of AI coding agents. Keep this list, and the AGENT /
34+ // AI_AGENT fallback handling in agentEnvFallback, in sync with the Go,
35+ // Java, and Python SDKs. Agents are listed alphabetically by product name.
2536const KNOWN_AGENTS : readonly KnownAgent [ ] = [
2637 // The amp agent also sets AGENT=amp, handled by the central fallback.
2738 { envVar : 'AMP_CURRENT_THREAD_ID' , product : 'amp' } ,
@@ -45,24 +56,33 @@ const KNOWN_AGENTS: readonly KnownAgent[] = [
4556 { envVar : 'WINDSURF_AGENT' , product : 'windsurf' } ,
4657] ;
4758
59+ /**
60+ * Returns a sanitized, length-capped name from `AGENT` or `AI_AGENT`,
61+ * preferring `AGENT` when both are non-empty. Empty is treated as unset for
62+ * both. The value is passed through rather than categorized so that new
63+ * names are propagated without the need to update the list of known agents.
64+ */
4865function agentEnvFallback ( ) : string {
49- const v = process . env [ AGENT_ENV_VAR ] ;
66+ let v = process . env [ AGENT_ENV_VAR ] ;
67+ if ( v === undefined || v === '' ) {
68+ v = process . env [ AI_AGENT_ENV_VAR ] ;
69+ }
5070 if ( v === undefined || v === '' ) {
5171 return '' ;
5272 }
53- if ( KNOWN_AGENTS . some ( a => a . product === v ) ) {
54- return v ;
73+ v = sanitize ( v ) ;
74+ if ( v . length > MAX_AGENT_FALLBACK_LEN ) {
75+ v = v . slice ( 0 , MAX_AGENT_FALLBACK_LEN ) ;
5576 }
56- return 'unknown' ;
77+ return v ;
5778}
5879
5980/**
6081 * Checks environment variables for known AI agents and returns the
6182 * detected product name.
6283 *
6384 * Explicit product-specific env vars always take precedence over the
64- * generic agents.md `AGENT` env var. `AGENT` is consulted only as a
65- * fallback when no explicit matcher fires, so that an explicit signal
85+ * generic `AGENT` and `AI_AGENT` env vars, so that an explicit signal
6686 * (e.g. `CLAUDECODE=1`) always wins over a conflicting `AGENT=<name>`
6787 * value.
6888 *
@@ -73,8 +93,8 @@ function agentEnvFallback(): string {
7393 * can be stacked when one agent invokes another as a subagent (e.g.
7494 * Claude Code spawning a Cursor CLI subprocess), so the child process
7595 * inherits env vars from multiple layers.
76- * - When no known env var is set and `AGENT` is a non-empty value: the
77- * value itself if it names a known product, otherwise `"unknown"` .
96+ * - A sanitized, length-capped value from `AGENT` or `AI_AGENT` when no
97+ * known env var is set (see { @link agentEnvFallback}) .
7898 * - `""` when nothing is set.
7999 */
80100export function lookupAgentProvider ( ) : string {
@@ -101,13 +121,12 @@ let cached: string | undefined;
101121 * Returns one of:
102122 *
103123 * - The known product name when exactly one agent is detected via
104- * explicit env matchers, or when `AGENT` is set to a known product
105- * name and no explicit matcher fired.
124+ * explicit env matchers.
106125 * - `"multiple"` when multiple explicit matchers fire for different
107126 * agents (typically nested agents, e.g. Cursor CLI running as a
108127 * Claude Code subagent).
109- * - `"unknown"` when no explicit matcher fired and `AGENT` is set to a
110- * value that is not a known product name .
128+ * - A sanitized, length-capped value from `AGENT` or `AI_AGENT` when no
129+ * explicit matcher fired (see { @link agentEnvFallback}) .
111130 * - `""` when no agent is detected.
112131 */
113132export function agentProvider ( ) : string {
0 commit comments