Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion documentation/project-docs/telemetry.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` or `cursor` |
| **LLM** | Detected LLM/assistant environment identifiers (comma-separated) | `claude`, `cursor`, `gemini`, `copilot`, `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 |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ public MSBuildForwardingAppWithoutLogging(MSBuildArgs msbuildArgs, string? msbui
msbuildArgs.OtherMSBuildArgs.Add("-nologo");
}
string? tlpDefault = TerminalLoggerDefault;
// new for .NET 9 - default TL to auto (aka enable in non-CI scenarios)
if (string.IsNullOrWhiteSpace(tlpDefault))
{
tlpDefault = "auto";
Expand Down
30 changes: 29 additions & 1 deletion src/Cli/dotnet/Commands/MSBuild/MSBuildForwardingApp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ public class MSBuildForwardingApp : CommandBase

private readonly MSBuildForwardingAppWithoutLogging _forwardingAppWithoutLogging;

/// <summary>
/// Adds the CLI's telemetry logger to the MSBuild arguments if telemetry is enabled.
/// </summary>
/// <param name="msbuildArgs"></param>
/// <returns></returns>
private static MSBuildArgs ConcatTelemetryLogger(MSBuildArgs msbuildArgs)
{
if (Telemetry.Telemetry.CurrentSessionId != null)
Expand All @@ -34,6 +39,28 @@ private static MSBuildArgs ConcatTelemetryLogger(MSBuildArgs msbuildArgs)
return msbuildArgs;
}

/// <summary>
/// Adjusts MSBuild loggers to be more suitable for LLM/agentic environments, if such an environment is detected.
/// </summary>
/// <param name="msbuildArgs"></param>
/// <returns></returns>
private static MSBuildArgs AdjustLoggerForLLMs(MSBuildArgs msbuildArgs)
{
if (new Telemetry.LLMEnvironmentDetectorForTelemetry().GetLLMEnvironment() is string _)
{
try
{
// introduced in https://github.com/dotnet/msbuild/pull/12581, disables live-updating node display, which wastes tokens
msbuildArgs.OtherMSBuildArgs.Add("-tlp:DISABLENODEDISPLAY");
return msbuildArgs;
}
catch (Exception)
{
}
}
return msbuildArgs;
}

/// <summary>
/// Mostly intended for quick/one-shot usage - most 'core' SDK commands should do more hands-on parsing.
/// </summary>
Expand All @@ -45,8 +72,9 @@ public MSBuildForwardingApp(IEnumerable<string> rawMSBuildArgs, string? msbuildP

public MSBuildForwardingApp(MSBuildArgs msBuildArgs, string? msbuildPath = null, bool includeLogo = false)
{
var modifiedMSBuildArgs = AdjustLoggerForLLMs(ConcatTelemetryLogger(msBuildArgs));
_forwardingAppWithoutLogging = new MSBuildForwardingAppWithoutLogging(
ConcatTelemetryLogger(msBuildArgs),
modifiedMSBuildArgs,
msbuildPath: msbuildPath,
includeLogo: includeLogo);

Expand Down
16 changes: 8 additions & 8 deletions src/Cli/dotnet/Telemetry/EnvironmentDetectionRule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.DotNet.Cli.Utils;

namespace Microsoft.DotNet.Cli.Telemetry;

Expand Down Expand Up @@ -33,8 +34,7 @@ public BooleanEnvironmentRule(params string[] variables)

public override bool IsMatch()
{
return _variables.Any(variable =>
bool.TryParse(Environment.GetEnvironmentVariable(variable), out bool value) && value);
return _variables.Any(variable => Env.GetEnvironmentVariableAsBool(variable));
}
}

Expand Down Expand Up @@ -81,12 +81,12 @@ public override bool IsMatch()
/// <typeparam name="T">The type of the result value.</typeparam>
internal class EnvironmentDetectionRuleWithResult<T> where T : class
{
private readonly string[] _variables;
private readonly EnvironmentDetectionRule _rule;
private readonly T _result;

public EnvironmentDetectionRuleWithResult(T result, params string[] variables)
public EnvironmentDetectionRuleWithResult(T result, EnvironmentDetectionRule rule)
{
_variables = variables ?? throw new ArgumentNullException(nameof(variables));
_rule = rule ?? throw new ArgumentNullException(nameof(rule));
_result = result ?? throw new ArgumentNullException(nameof(result));
}

Expand All @@ -96,8 +96,8 @@ public EnvironmentDetectionRuleWithResult(T result, params string[] variables)
/// <returns>The result value if the rule matches; otherwise, null.</returns>
public T? GetResult()
{
return _variables.Any(variable => !string.IsNullOrEmpty(Environment.GetEnvironmentVariable(variable)))
? _result
return _rule.IsMatch()
? _result
: null;
}
}
}
12 changes: 9 additions & 3 deletions src/Cli/dotnet/Telemetry/LLMEnvironmentDetectorForTelemetry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,20 @@ internal class LLMEnvironmentDetectorForTelemetry : ILLMEnvironmentDetector
{
private static readonly EnvironmentDetectionRuleWithResult<string>[] _detectionRules = [
// Claude Code
new EnvironmentDetectionRuleWithResult<string>("claude", "CLAUDECODE"),
new EnvironmentDetectionRuleWithResult<string>("claude", new AnyPresentEnvironmentRule("CLAUDECODE")),
// Cursor AI
new EnvironmentDetectionRuleWithResult<string>("cursor", "CURSOR_EDITOR")
new EnvironmentDetectionRuleWithResult<string>("cursor", new AnyPresentEnvironmentRule("CURSOR_EDITOR")),
// Gemini
new EnvironmentDetectionRuleWithResult<string>("gemini", new BooleanEnvironmentRule("GEMINI_CLI")),
// GitHub Copilot
new EnvironmentDetectionRuleWithResult<string>("copilot", new BooleanEnvironmentRule("GITHUB_COPILOT_CLI_MODE")),
// (proposed) generic flag for Agentic usage
new EnvironmentDetectionRuleWithResult<string>("generic_agent", new BooleanEnvironmentRule("AGENT_CLI")),
];

public string? GetLLMEnvironment()
{
var results = _detectionRules.Select(r => r.GetResult()).Where(r => r != null).ToArray();
return results.Length > 0 ? string.Join(", ", results) : null;
}
}
}
Loading