@@ -237,52 +237,15 @@ private static String cicdProvider() {
237237 return cicdProvider ;
238238 }
239239
240- // Matches an environment variable. If value is empty, matching is
241- // presence-only (any value, including empty string, counts as a match). If
242- // value is non-empty, the env var must be set to exactly that value.
243- private static class EnvMatcher {
244- private final String envVar ;
245- private final String value ;
246-
247- EnvMatcher (String envVar ) {
248- this (envVar , "" );
249- }
250-
251- EnvMatcher (String envVar , String value ) {
252- this .envVar = envVar ;
253- this .value = value ;
254- }
255-
256- boolean fires (Environment env ) {
257- String v = env .get (envVar );
258- if (v == null ) {
259- return false ;
260- }
261- if (value .isEmpty ()) {
262- return true ;
263- }
264- return v .equals (value );
265- }
266- }
267-
268- // Describes a single AI coding agent and the environment matchers that
269- // identify it. The agent is detected if ANY matcher in matchAny fires.
240+ // Describes a single AI coding agent: the env var that identifies it and the
241+ // product name reported in the user agent.
270242 private static class KnownAgent {
243+ private final String envVar ;
271244 private final String product ;
272- private final List <EnvMatcher > matchAny ;
273245
274- KnownAgent (String product , List <EnvMatcher > matchAny ) {
246+ KnownAgent (String envVar , String product ) {
247+ this .envVar = envVar ;
275248 this .product = product ;
276- this .matchAny = matchAny ;
277- }
278-
279- boolean fires (Environment env ) {
280- for (EnvMatcher m : matchAny ) {
281- if (m .fires (env )) {
282- return true ;
283- }
284- }
285- return false ;
286249 }
287250 }
288251
@@ -295,64 +258,26 @@ boolean fires(Environment env) {
295258 // Agents are listed alphabetically by product name.
296259 private static List <KnownAgent > listKnownAgents () {
297260 return Arrays .asList (
298- // Amp also sets AGENT=amp; handled by the central AGENT fallback.
299- new KnownAgent (
300- "amp" ,
301- Collections .singletonList (
302- new EnvMatcher ("AMP_CURRENT_THREAD_ID" ))), // https://ampcode.com/
303- new KnownAgent (
304- "antigravity" ,
305- Collections .singletonList (
306- new EnvMatcher ("ANTIGRAVITY_AGENT" ))), // Closed source (Google)
307261 new KnownAgent (
308- "augment" ,
309- Collections .singletonList (
310- new EnvMatcher ("AUGMENT_AGENT" ))), // https://www.augmentcode.com/
311- new KnownAgent (
312- "claude-code" ,
313- Collections .singletonList (
314- new EnvMatcher ("CLAUDECODE" ))), // https://github.com/anthropics/claude-code
315- new KnownAgent (
316- "cline" ,
317- Collections .singletonList (
318- new EnvMatcher ("CLINE_ACTIVE" ))), // https://github.com/cline/cline (v3.24.0+)
319- new KnownAgent (
320- "codex" ,
321- Collections .singletonList (
322- new EnvMatcher ("CODEX_CI" ))), // https://github.com/openai/codex
323- new KnownAgent (
324- "copilot-cli" ,
325- Collections .singletonList (
326- new EnvMatcher ("COPILOT_CLI" ))), // https://github.com/features/copilot
262+ "AMP_CURRENT_THREAD_ID" ,
263+ "amp" ), // https://ampcode.com/ (also sets AGENT=amp, handled centrally)
264+ new KnownAgent ("ANTIGRAVITY_AGENT" , "antigravity" ), // Closed source (Google)
265+ new KnownAgent ("AUGMENT_AGENT" , "augment" ), // https://www.augmentcode.com/
266+ new KnownAgent ("CLAUDECODE" , "claude-code" ), // https://github.com/anthropics/claude-code
267+ new KnownAgent ("CLINE_ACTIVE" , "cline" ), // https://github.com/cline/cline (v3.24.0+)
268+ new KnownAgent ("CODEX_CI" , "codex" ), // https://github.com/openai/codex
269+ new KnownAgent ("COPILOT_CLI" , "copilot-cli" ), // https://github.com/features/copilot
327270 // VS Code Copilot terminal; best-effort heuristic, not officially identified.
271+ new KnownAgent ("COPILOT_MODEL" , "copilot-vscode" ),
272+ new KnownAgent ("CURSOR_AGENT" , "cursor" ), // Closed source
273+ new KnownAgent ("GEMINI_CLI" , "gemini-cli" ), // https://google-gemini.github.io/gemini-cli
328274 new KnownAgent (
329- "copilot-vscode" , Collections .singletonList (new EnvMatcher ("COPILOT_MODEL" ))),
330- new KnownAgent (
331- "cursor" , Collections .singletonList (new EnvMatcher ("CURSOR_AGENT" ))), // Closed source
332- new KnownAgent (
333- "gemini-cli" ,
334- Collections .singletonList (
335- new EnvMatcher ("GEMINI_CLI" ))), // https://google-gemini.github.io/gemini-cli
336- // Goose also sets AGENT=goose; handled by the central AGENT fallback.
337- new KnownAgent (
338- "goose" ,
339- Collections .singletonList (
340- new EnvMatcher ("GOOSE_TERMINAL" ))), // https://block.github.io/goose/
341- new KnownAgent (
342- "kiro" ,
343- Collections .singletonList (new EnvMatcher ("KIRO" ))), // https://kiro.dev/ (Amazon)
344- new KnownAgent (
345- "openclaw" ,
346- Collections .singletonList (
347- new EnvMatcher ("OPENCLAW_SHELL" ))), // https://github.com/anthropics/openclaw
348- new KnownAgent (
349- "opencode" ,
350- Collections .singletonList (
351- new EnvMatcher ("OPENCODE" ))), // https://github.com/opencode-ai/opencode
352- new KnownAgent (
353- "windsurf" ,
354- Collections .singletonList (
355- new EnvMatcher ("WINDSURF_AGENT" )))); // https://codeium.com/windsurf (Codeium)
275+ "GOOSE_TERMINAL" ,
276+ "goose" ), // https://block.github.io/goose/ (also sets AGENT=goose, handled centrally)
277+ new KnownAgent ("KIRO" , "kiro" ), // https://kiro.dev/ (Amazon)
278+ new KnownAgent ("OPENCLAW_SHELL" , "openclaw" ), // https://github.com/anthropics/openclaw
279+ new KnownAgent ("OPENCODE" , "opencode" ), // https://github.com/opencode-ai/opencode
280+ new KnownAgent ("WINDSURF_AGENT" , "windsurf" )); // https://codeium.com/windsurf (Codeium)
356281 }
357282
358283 // Looks up the active agent provider based on environment variables.
@@ -376,33 +301,38 @@ private static List<KnownAgent> listKnownAgents() {
376301 // e.g. AGENT=cursor + CLAUDECODE=1 yields "claude-code", and
377302 // AGENT=goose + CLAUDECODE=1 also yields "claude-code".
378303 private static String lookupAgentProvider (Environment env ) {
379- List <KnownAgent > knownAgents = listKnownAgents ();
380- String detected = "" ;
381- int count = 0 ;
382- for (KnownAgent agent : knownAgents ) {
383- if (agent .fires (env )) {
384- detected = agent .product ;
385- count ++;
386- if (count > 1 ) {
387- break ;
388- }
304+ List <KnownAgent > agents = listKnownAgents ();
305+
306+ List <String > matches = new ArrayList <>();
307+ for (KnownAgent a : agents ) {
308+ if (env .get (a .envVar ) != null ) {
309+ matches .add (a .product );
389310 }
390311 }
391- if (count == 1 ) {
392- return detected ;
312+
313+ if (matches .size () == 1 ) {
314+ return matches .get (0 );
393315 }
394- if (count == 0 ) {
395- String agentValue = env .get (AGENT_ENV_VAR );
396- if (agentValue != null && !agentValue .isEmpty ()) {
397- for (KnownAgent agent : knownAgents ) {
398- if (agent .product .equals (agentValue )) {
399- return agentValue ;
400- }
401- }
402- return "unknown" ;
316+ if (matches .size () > 1 ) {
317+ return "" ; // ambiguity
318+ }
319+ return agentEnvFallback (env , agents );
320+ }
321+
322+ // agentEnvFallback honors the agents.md AGENT=<name> standard.
323+ // Returns the value if it matches a known product name, "unknown" if AGENT
324+ // is set to any other non-empty value, and "" if AGENT is unset or empty.
325+ private static String agentEnvFallback (Environment env , List <KnownAgent > agents ) {
326+ String v = env .get (AGENT_ENV_VAR );
327+ if (v == null || v .isEmpty ()) {
328+ return "" ;
329+ }
330+ for (KnownAgent a : agents ) {
331+ if (a .product .equals (v )) {
332+ return v ;
403333 }
404334 }
405- return "" ;
335+ return "unknown " ;
406336 }
407337
408338 // Thread-safe lazy initialization of agent provider detection
0 commit comments