|
12 | 12 |
|
13 | 13 | from opentelemetry.trace import Span, Status, StatusCode |
14 | 14 |
|
| 15 | +from crewai.utilities.constants import CODING_AGENT_ENV_MARKERS |
| 16 | + |
15 | 17 |
|
16 | 18 | if TYPE_CHECKING: |
17 | 19 | from crewai.crew import Crew |
18 | 20 | from crewai.task import Task |
19 | 21 |
|
20 | 22 |
|
21 | | -# Environment variables set by AI coding assistants, checked in order. |
22 | | -# Only the assistant's name is ever recorded - never the variable's value. |
23 | | -_CODING_AGENT_ENV_MARKERS: Final[tuple[tuple[str, str], ...]] = ( |
24 | | - ("CLAUDECODE", "claude_code"), |
25 | | - ("CLAUDE_CODE_ENTRYPOINT", "claude_code"), |
26 | | - ("CURSOR_TRACE_ID", "cursor"), |
27 | | - ("CURSOR_AGENT", "cursor"), |
28 | | - ("CODEX_SANDBOX", "codex"), |
29 | | - ("CODEX_SANDBOX_NETWORK_DISABLED", "codex"), |
30 | | - ("GEMINI_CLI", "gemini_cli"), |
31 | | - ("AIDER_MODEL", "aider"), |
32 | | - ("WINDSURF_SESSION_ID", "windsurf"), |
33 | | - ("DEVIN_SESSION_ID", "devin"), |
34 | | - ("REPLIT_AGENT", "replit_agent"), |
35 | | - ("COPILOT_AGENT_ID", "copilot"), |
36 | | - ("GITHUB_COPILOT_CLI", "copilot"), |
37 | | - ("OPENHANDS_SESSION_ID", "openhands"), |
38 | | - ("CLINE_ACTIVE", "cline"), |
39 | | - ("AMP_AGENT", "amp_code"), |
40 | | -) |
41 | | - |
42 | 23 | # Editors whose integrated terminal implies a human is likely present. Used only |
43 | 24 | # as a weaker fallback when no explicit coding-agent marker is found. |
44 | 25 | _EDITOR_TERM_MARKERS: Final[tuple[tuple[str, str, str], ...]] = ( |
|
49 | 30 | _FALLBACK_AGENT_NAMES: Final[tuple[str, ...]] = ("non_interactive", "unknown") |
50 | 31 |
|
51 | 32 | # The complete set of values detect_coding_agent() can ever return. Every value |
52 | | -# is a literal defined in this module, which is what makes the function |
53 | | -# structurally incapable of emitting PII: no environment value, path, hostname, |
54 | | -# or user-supplied string can reach the return value. |
| 33 | +# is a literal from CODING_AGENT_ENV_MARKERS or this module, which is what makes |
| 34 | +# the function structurally incapable of emitting PII: no environment value, |
| 35 | +# path, hostname, or user-supplied string can reach the return value. |
55 | 36 | KNOWN_CODING_AGENTS: Final[frozenset[str]] = frozenset( |
56 | | - [name for _, name in _CODING_AGENT_ENV_MARKERS] |
| 37 | + [name for name, _ in CODING_AGENT_ENV_MARKERS] |
57 | 38 | + [name for _, _, name in _EDITOR_TERM_MARKERS] |
58 | 39 | + list(_FALLBACK_AGENT_NAMES) |
59 | 40 | ) |
|
62 | 43 | def detect_coding_agent() -> str: |
63 | 44 | """Best-effort detection of the AI coding assistant running this process. |
64 | 45 |
|
65 | | - Detection is based on environment variables that coding assistants set in |
66 | | - the shells they spawn. Only the assistant's normalized name is returned - |
67 | | - environment variable values are never read into the return value or |
68 | | - recorded anywhere. |
| 46 | + Uses the shared ``CODING_AGENT_ENV_MARKERS`` table, so this agrees with the |
| 47 | + env-context events emitted by ``get_env_context()`` rather than maintaining |
| 48 | + a second, narrower set of markers. Precedence follows that table: Claude |
| 49 | + Code, then Codex, then Cursor, then the remaining assistants. |
| 50 | +
|
| 51 | + Only the assistant's normalized name is returned - environment variable |
| 52 | + values are never read into the return value or recorded anywhere. |
69 | 53 |
|
70 | | - This is intentionally heuristic: markers change as tools evolve, so a |
71 | | - result of "unknown" means "no known marker present", not "no agent". |
| 54 | + Two limits worth knowing. This is heuristic: markers change as tools |
| 55 | + evolve, so "unknown" means "no known marker present", not "no agent". And |
| 56 | + some markers (the Cursor set in particular) are set by the editor for any |
| 57 | + integrated terminal, so a result names the environment the process is |
| 58 | + running *under*, not proof that an agent authored the code. |
72 | 59 |
|
73 | 60 | Returns: |
74 | 61 | A normalized assistant name (e.g. "claude_code", "cursor", "codex"), |
75 | 62 | an editor terminal hint (e.g. "vscode_terminal"), "non_interactive" |
76 | 63 | when no marker is found and there is no TTY, or "unknown" otherwise. |
77 | 64 | The result is always a member of KNOWN_CODING_AGENTS. |
78 | 65 | """ |
79 | | - for env_var, agent_name in _CODING_AGENT_ENV_MARKERS: |
80 | | - if os.environ.get(env_var): |
| 66 | + for agent_name, env_vars in CODING_AGENT_ENV_MARKERS: |
| 67 | + if any(os.environ.get(env_var) for env_var in env_vars): |
81 | 68 | return agent_name |
82 | 69 |
|
83 | 70 | for env_var, expected, agent_name in _EDITOR_TERM_MARKERS: |
|
0 commit comments