@@ -4,18 +4,28 @@ import (
44 "fmt"
55 "os"
66 "regexp"
7+ "strings"
78)
89
910// AgentName is a validated agent identifier safe for use in HTTP headers.
1011type AgentName string
1112
1213const (
13- agentAmp AgentName = "amp"
14- agentClaudeCode AgentName = "claude-code"
15- agentCodex AgentName = "codex"
16- agentCopilotCLI AgentName = "copilot-cli"
17- agentGeminiCLI AgentName = "gemini-cli"
18- agentOpencode AgentName = "opencode"
14+ agentAmp AgentName = "amp"
15+ agentClaudeCode AgentName = "claude-code"
16+ agentCodex AgentName = "codex"
17+ agentCopilotCLI AgentName = "copilot-cli"
18+ agentGeminiCLI AgentName = "gemini-cli"
19+ agentOpencode AgentName = "opencode"
20+ agentAntigravity AgentName = "antigravity"
21+ agentAugmentCLI AgentName = "augment-cli"
22+ agentReplit AgentName = "replit"
23+ agentGoose AgentName = "goose"
24+ agentCowork AgentName = "cowork"
25+ agentCursor AgentName = "cursor"
26+ agentCursorCLI AgentName = "cursor-cli"
27+ agentKiro AgentName = "kiro"
28+ agentPi AgentName = "pi"
1929)
2030
2131var validAgentName = regexp .MustCompile (`^[a-zA-Z0-9_-]+$` )
@@ -46,7 +56,7 @@ func detectWith(lookup func(string) (string, bool)) AgentName {
4656 return v
4757 }
4858
49- // Generic agent identifiers — checked first because they are the most specific signal.
59+ // Generic agent identifiers - checked first because they are the most specific signal.
5060 if v , ok := lookup ("AI_AGENT" ); ok && v != "" {
5161 if name , err := parseAgentName (v ); err == nil {
5262 return name
@@ -60,15 +70,15 @@ func detectWith(lookup func(string) (string, bool)) AgentName {
6070 return agentAmp
6171 }
6272
63- // OpenAI Codex CLI — https://github.com/openai/codex
73+ // OpenAI Codex CLI - https://github.com/openai/codex
6474 // CODEX_SANDBOX: https://github.com/openai/codex/blob/95e1d5993985019ce0ce0d10689caf1375f95120/codex-rs/core/src/spawn.rs#L25
6575 // CODEX_THREAD_ID: https://github.com/openai/codex/blob/95e1d5993985019ce0ce0d10689caf1375f95120/codex-rs/core/src/exec_env.rs#L8
6676 // CODEX_CI: https://github.com/openai/codex/blob/95e1d5993985019ce0ce0d10689caf1375f95120/codex-rs/core/src/unified_exec/process_manager.rs#L64
6777 if isSet ("CODEX_SANDBOX" ) || isSet ("CODEX_CI" ) || isSet ("CODEX_THREAD_ID" ) {
6878 return agentCodex
6979 }
7080
71- // Google Gemini CLI — https://github.com/google-gemini/gemini-cli
81+ // Google Gemini CLI - https://github.com/google-gemini/gemini-cli
7282 // GEMINI_CLI: https://github.com/google-gemini/gemini-cli/blob/46fd7b4864111032a1c7dfa1821b2000fc7531da/docs/tools/shell.md#L96-L97
7383 if isSet ("GEMINI_CLI" ) {
7484 return agentGeminiCLI
@@ -80,20 +90,92 @@ func detectWith(lookup func(string) (string, bool)) AgentName {
8090 return agentCopilotCLI
8191 }
8292
83- // OpenCode — https://github.com/anomalyco/opencode
93+ // OpenCode - https://github.com/anomalyco/opencode
8494 // OPENCODE: https://github.com/anomalyco/opencode/blob/fde201c286a83ff32dda9b41d61d734a4449fe70/packages/opencode/src/index.ts#L78-L80
95+ // Not OPENCODE_CALLER or OPENCODE_CLIENT: they name the client that launched
96+ // opencode (e.g. the VS Code extension), not the running agent.
8597 if isSet ("OPENCODE" ) {
8698 return agentOpencode
8799 }
88100
89- // Anthropic Claude Code — https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/overview
101+ // Antigravity
102+ // No first-party docs
103+ if isSet ("ANTIGRAVITY_AGENT" ) {
104+ return agentAntigravity
105+ }
106+
107+ // Augment CLI
108+ // No first-party docs
109+ if isSet ("AUGMENT_AGENT" ) {
110+ return agentAugmentCLI
111+ }
112+
113+ // Replit
114+ // REPL_ID is present throughout any Replit environment, not only when a
115+ // Replit agent is driving the CLI, so it is a broad, low-confidence signal.
116+ // REPL_ID: https://github.com/replit/go-replidentity/blob/2966ea2d227d572f6054ee8f077ad16a1be02663/examples/extract.go#L25
117+ if isSet ("REPL_ID" ) {
118+ return agentReplit
119+ }
120+
121+ // Anthropic Claude Code - https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/overview
90122 // CLAUDECODE: https://code.claude.com/docs/en/env-vars (CLAUDECODE section)
91- // Checked last because other agents (e.g. Amp) set CLAUDECODE=1 alongside their own vars.
92- if isSet ("CLAUDECODE" ) {
123+ // CLAUDE_CODE, CLAUDE_CODE_IS_COWORK: no first-party docs
124+ //
125+ // Cowork is a Claude Code mode that also sets CLAUDECODE, so it is checked
126+ // first to win over the generic Claude Code signal below.
127+ if isSet ("CLAUDE_CODE_IS_COWORK" ) {
128+ return agentCowork
129+ }
130+
131+ // Claude Code is checked after Amp and Cowork, which also set CLAUDECODE, so
132+ // those more specific agents are detected first.
133+ if isSet ("CLAUDECODE" ) || isSet ("CLAUDE_CODE" ) {
93134 // There is a CLAUDE_CODE_ENTRYPOINT env var that is set to `cli` or `desktop` etc, but it's not documented
94135 // so we don't want to rely on it too heavily. We'll just return a generic claude-code agent name.
95136 return agentClaudeCode
96137 }
97138
139+ // Cursor
140+ // No first-party docs
141+ // CURSOR_TRACE_ID (IDE) takes precedence over the Cursor CLI signal below.
142+ if isSet ("CURSOR_TRACE_ID" ) {
143+ return agentCursor
144+ }
145+
146+ // Cursor CLI
147+ // No first-party docs
148+ if isSet ("CURSOR_AGENT" ) || valueOf ("CURSOR_EXTENSION_HOST_ROLE" ) == "agent-exec" {
149+ return agentCursorCLI
150+ }
151+
152+ // Single-source signals matched against one environment variable. These
153+ // carry lower corroboration than the presence-based agents above, so they
154+ // are checked after them.
155+
156+ // Kiro
157+ // No first-party docs
158+ if valueOf ("TERM_PROGRAM" ) == "kiro" {
159+ return agentKiro
160+ }
161+
162+ // Pi
163+ // No first-party docs
164+ // Anchored to a path separator so it only matches ".pi/agent" as a real
165+ // path segment, not an incidental substring. The Windows separator is
166+ // matched too, though confidence there is lower since it is unconfirmed
167+ // that pi uses this layout on Windows.
168+ if strings .Contains (valueOf ("PATH" ), "/.pi/agent" ) || strings .Contains (valueOf ("PATH" ), `\.pi\agent` ) {
169+ return agentPi
170+ }
171+
172+ // Goose is checked last because GOOSE_PROVIDER only indicates that Goose is
173+ // configured as a model provider, not that it is driving the CLI, so any
174+ // more specific signal above should win.
175+ // GOOSE_PROVIDER: https://github.com/aaif-goose/goose/blob/48a2a3d1804ae75eb7b208a5d0d73fd976511b80/crates/goose/src/config/providers.rs#L93
176+ if isSet ("GOOSE_PROVIDER" ) {
177+ return agentGoose
178+ }
179+
98180 return ""
99181}
0 commit comments