diff --git a/documentation/project-docs/telemetry.md b/documentation/project-docs/telemetry.md
index e559f60bb25b..6db82f712ee1 100644
--- a/documentation/project-docs/telemetry.md
+++ b/documentation/project-docs/telemetry.md
@@ -61,7 +61,7 @@ Every telemetry event automatically includes these common properties:
| **Telemetry Profile** | Custom telemetry profile (if set via env var) | Custom value or null |
| **Docker Container** | Whether running in Docker container | `True` or `False` |
| **CI** | Whether running in CI environment | `True` or `False` |
-| **LLM** | Detected LLM/assistant environment identifiers (comma-separated) | `claude`, `cursor`, `gemini`, `copilot`, `generic_agent` |
+| **LLM** | Detected LLM/assistant environment identifiers (comma-separated) | `claude`, `cursor`, `gemini`, `copilot`, `codex`, `aider`, `amp`, `qwen`, `droid`, `opencode`, `zed`, `kimi`, `openhands`, `goose`, `cline`, `roo`, `windsurf`, `generic_agent` |
| **Current Path Hash** | SHA256 hash of current directory path | Hashed value |
| **Machine ID** | SHA256 hash of machine MAC address (or GUID if unavailable) | Hashed value |
| **Machine ID Old** | Legacy machine ID for compatibility | Hashed value |
diff --git a/src/Cli/Microsoft.DotNet.Cli.Definitions/Telemetry/EnvironmentDetectionRule.cs b/src/Cli/Microsoft.DotNet.Cli.Definitions/Telemetry/EnvironmentDetectionRule.cs
index 80d710d66cc0..ad9322a22633 100644
--- a/src/Cli/Microsoft.DotNet.Cli.Definitions/Telemetry/EnvironmentDetectionRule.cs
+++ b/src/Cli/Microsoft.DotNet.Cli.Definitions/Telemetry/EnvironmentDetectionRule.cs
@@ -69,6 +69,27 @@ public override bool IsMatch()
}
}
+///
+/// Rule that matches when an environment variable contains a specific value (case-insensitive).
+///
+internal class EnvironmentVariableValueRule : EnvironmentDetectionRule
+{
+ private readonly string _variable;
+ private readonly string _expectedValue;
+
+ public EnvironmentVariableValueRule(string variable, string expectedValue)
+ {
+ _variable = variable ?? throw new ArgumentNullException(nameof(variable));
+ _expectedValue = expectedValue ?? throw new ArgumentNullException(nameof(expectedValue));
+ }
+
+ public override bool IsMatch()
+ {
+ var value = Environment.GetEnvironmentVariable(_variable);
+ return !string.IsNullOrEmpty(value) && value.Equals(_expectedValue, StringComparison.OrdinalIgnoreCase);
+ }
+}
+
///
/// Rule that matches when any of the specified environment variables is present and not null/empty,
/// and returns the associated result value.
diff --git a/src/Cli/dotnet/Telemetry/LLMEnvironmentDetectorForTelemetry.cs b/src/Cli/dotnet/Telemetry/LLMEnvironmentDetectorForTelemetry.cs
index b37f9b5d0830..65368c4eef69 100644
--- a/src/Cli/dotnet/Telemetry/LLMEnvironmentDetectorForTelemetry.cs
+++ b/src/Cli/dotnet/Telemetry/LLMEnvironmentDetectorForTelemetry.cs
@@ -7,13 +7,39 @@ internal class LLMEnvironmentDetectorForTelemetry : ILLMEnvironmentDetector
{
private static readonly EnvironmentDetectionRuleWithResult[] _detectionRules = [
// Claude Code
- new EnvironmentDetectionRuleWithResult("claude", new AnyPresentEnvironmentRule("CLAUDECODE")),
+ new EnvironmentDetectionRuleWithResult("claude", new AnyPresentEnvironmentRule("CLAUDECODE", "CLAUDE_CODE_ENTRYPOINT")),
// Cursor AI
- new EnvironmentDetectionRuleWithResult("cursor", new AnyPresentEnvironmentRule("CURSOR_EDITOR")),
+ new EnvironmentDetectionRuleWithResult("cursor", new AnyPresentEnvironmentRule("CURSOR_EDITOR", "CURSOR_AI")),
// Gemini
new EnvironmentDetectionRuleWithResult("gemini", new BooleanEnvironmentRule("GEMINI_CLI")),
// GitHub Copilot
new EnvironmentDetectionRuleWithResult("copilot", new BooleanEnvironmentRule("GITHUB_COPILOT_CLI_MODE")),
+ // Codex CLI
+ new EnvironmentDetectionRuleWithResult("codex", new AnyPresentEnvironmentRule("CODEX_CLI", "CODEX_SANDBOX")),
+ // Aider
+ new EnvironmentDetectionRuleWithResult("aider", new EnvironmentVariableValueRule("OR_APP_NAME", "Aider")),
+ // Amp
+ new EnvironmentDetectionRuleWithResult("amp", new AnyPresentEnvironmentRule("AMP_HOME")),
+ // Qwen Code
+ new EnvironmentDetectionRuleWithResult("qwen", new AnyPresentEnvironmentRule("QWEN_CODE")),
+ // Droid
+ new EnvironmentDetectionRuleWithResult("droid", new BooleanEnvironmentRule("DROID_CLI")),
+ // OpenCode
+ new EnvironmentDetectionRuleWithResult("opencode", new AnyPresentEnvironmentRule("OPENCODE_AI")),
+ // Zed AI
+ new EnvironmentDetectionRuleWithResult("zed", new AnyPresentEnvironmentRule("ZED_ENVIRONMENT", "ZED_TERM")),
+ // Kimi CLI
+ new EnvironmentDetectionRuleWithResult("kimi", new BooleanEnvironmentRule("KIMI_CLI")),
+ // OpenHands
+ new EnvironmentDetectionRuleWithResult("openhands", new EnvironmentVariableValueRule("OR_APP_NAME", "OpenHands")),
+ // Goose
+ new EnvironmentDetectionRuleWithResult("goose", new AnyPresentEnvironmentRule("GOOSE_TERMINAL")),
+ // Cline
+ new EnvironmentDetectionRuleWithResult("cline", new AnyPresentEnvironmentRule("CLINE_TASK_ID")),
+ // Roo Code
+ new EnvironmentDetectionRuleWithResult("roo", new AnyPresentEnvironmentRule("ROO_CODE_TASK_ID")),
+ // Windsurf
+ new EnvironmentDetectionRuleWithResult("windsurf", new AnyPresentEnvironmentRule("WINDSURF_SESSION")),
// (proposed) generic flag for Agentic usage
new EnvironmentDetectionRuleWithResult("generic_agent", new BooleanEnvironmentRule("AGENT_CLI")),
];
diff --git a/test/dotnet.Tests/TelemetryCommonPropertiesTests.cs b/test/dotnet.Tests/TelemetryCommonPropertiesTests.cs
index 4e28b92479d7..188faf237128 100644
--- a/test/dotnet.Tests/TelemetryCommonPropertiesTests.cs
+++ b/test/dotnet.Tests/TelemetryCommonPropertiesTests.cs
@@ -231,17 +231,44 @@ public void TelemetryCommonPropertiesShouldContainSessionId(string? sessionId)
public static TheoryData?, string?> LLMTelemetryTestCases => new()
{
{ new Dictionary { {"CLAUDECODE", "1" } }, "claude" },
+ { new Dictionary { {"CLAUDE_CODE_ENTRYPOINT", "some_value" } }, "claude" },
{ new Dictionary { { "CURSOR_EDITOR", "1" } }, "cursor" },
+ { new Dictionary { { "CURSOR_AI", "1" } }, "cursor" },
{ new Dictionary { { "GEMINI_CLI", "true" } }, "gemini" },
{ new Dictionary { { "GITHUB_COPILOT_CLI_MODE", "true" } }, "copilot" },
+ { new Dictionary { { "CODEX_CLI", "1" } }, "codex" },
+ { new Dictionary { { "CODEX_SANDBOX", "1" } }, "codex" },
+ { new Dictionary { { "OR_APP_NAME", "Aider" } }, "aider" },
+ { new Dictionary { { "OR_APP_NAME", "aider" } }, "aider" },
+ { new Dictionary { { "AMP_HOME", "/path/to/amp" } }, "amp" },
+ { new Dictionary { { "QWEN_CODE", "1" } }, "qwen" },
+ { new Dictionary { { "DROID_CLI", "true" } }, "droid" },
+ { new Dictionary { { "OPENCODE_AI", "1" } }, "opencode" },
+ { new Dictionary { { "ZED_ENVIRONMENT", "1" } }, "zed" },
+ { new Dictionary { { "ZED_TERM", "1" } }, "zed" },
+ { new Dictionary { { "KIMI_CLI", "true" } }, "kimi" },
+ { new Dictionary { { "OR_APP_NAME", "OpenHands" } }, "openhands" },
+ { new Dictionary { { "OR_APP_NAME", "openhands" } }, "openhands" },
+ { new Dictionary { { "GOOSE_TERMINAL", "1" } }, "goose" },
+ { new Dictionary { { "CLINE_TASK_ID", "task123" } }, "cline" },
+ { new Dictionary { { "ROO_CODE_TASK_ID", "task456" } }, "roo" },
+ { new Dictionary { { "WINDSURF_SESSION", "session789" } }, "windsurf" },
{ new Dictionary { { "AGENT_CLI", "true" } }, "generic_agent" },
+ // Test combinations of older tools
{ new Dictionary { { "CLAUDECODE", "1" }, { "CURSOR_EDITOR", "1" } }, "claude, cursor" },
{ new Dictionary { { "GEMINI_CLI", "true" }, { "GITHUB_COPILOT_CLI_MODE", "true" } }, "gemini, copilot" },
{ new Dictionary { { "CLAUDECODE", "1" }, { "GEMINI_CLI", "true" }, { "AGENT_CLI", "true" } }, "claude, gemini, generic_agent" },
{ new Dictionary { { "CLAUDECODE", "1" }, { "CURSOR_EDITOR", "1" }, { "GEMINI_CLI", "true" }, { "GITHUB_COPILOT_CLI_MODE", "true" }, { "AGENT_CLI", "true" } }, "claude, cursor, gemini, copilot, generic_agent" },
+ // Test combinations of newer tools
+ { new Dictionary { { "OR_APP_NAME", "Aider" }, { "CLINE_TASK_ID", "task123" } }, "aider, cline" },
+ { new Dictionary { { "CODEX_CLI", "1" }, { "WINDSURF_SESSION", "session789" } }, "codex, windsurf" },
+ { new Dictionary { { "GOOSE_TERMINAL", "1" }, { "ROO_CODE_TASK_ID", "task456" } }, "goose, roo" },
{ new Dictionary { { "GEMINI_CLI", "false" } }, null },
{ new Dictionary { { "GITHUB_COPILOT_CLI_MODE", "false" } }, null },
{ new Dictionary { { "AGENT_CLI", "false" } }, null },
+ { new Dictionary { { "DROID_CLI", "false" } }, null },
+ { new Dictionary { { "KIMI_CLI", "false" } }, null },
+ { new Dictionary { { "OR_APP_NAME", "SomeOtherApp" } }, null },
{ new Dictionary(), null },
};