Avoid ANSI and progress output when running in LLM environment#7649
Avoid ANSI and progress output when running in LLM environment#7649Youssef1313 merged 2 commits intomainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR aims to reduce noisy/token-inefficient terminal output in agent/LLM-driven runs by automatically disabling ANSI output and progress rendering when an LLM environment is detected.
Changes:
- Added an
LLMEnvironmentDetectorhelper that infers “LLM environment” based on known environment variables from various agent tools. - Updated
TerminalOutputDeviceto forceAnsiMode.NoAnsiand disable progress output when an LLM environment is detected.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| src/Platform/Microsoft.Testing.Platform/OutputDevice/TerminalOutputDevice.cs | Forces no-ANSI and no-progress behavior when LLMEnvironmentDetector indicates an LLM environment. |
| src/Platform/Microsoft.Testing.Platform/Helpers/LLMEnvironmentDetector.cs | Introduces environment-variable-based detection of common LLM/agent CLI environments. |
Comments suppressed due to low confidence (1)
src/Platform/Microsoft.Testing.Platform/OutputDevice/TerminalOutputDevice.cs:134
- The comments inside this
ifblock are now misleading: the condition isnoAnsi || LLMEnvironmentDetector.IsLLMEnvironment(), but the block comment says the user explicitly specified--no-ansiand that we should respect it. Please update the comment to reflect that this path also applies to auto-detection of an LLM environment (and clarify the precedence between auto-detection and CLI options if relevant).
if (noAnsi || LLMEnvironmentDetector.IsLLMEnvironment())
{
// User explicitly specified --no-ansi.
// We should respect that.
ansiMode = AnsiMode.NoAnsi;
src/Platform/Microsoft.Testing.Platform/OutputDevice/TerminalOutputDevice.cs
Show resolved
Hide resolved
src/Platform/Microsoft.Testing.Platform/OutputDevice/TerminalOutputDevice.cs
Outdated
Show resolved
Hide resolved
src/Platform/Microsoft.Testing.Platform/OutputDevice/TerminalOutputDevice.cs
Outdated
Show resolved
Hide resolved
src/Platform/Microsoft.Testing.Platform/Helpers/LLMEnvironmentDetector.cs
Show resolved
Hide resolved
Evangelink
left a comment
There was a problem hiding this comment.
Thanks for the PR @Youssef1313 — the concept is solid and the detection rule catalog is comprehensive. A few concerns:
Architecture / Testability:
LLMEnvironmentDetectorusesSystem.Environmentdirectly, bypassing theIEnvironmentabstraction that the project enforces viaBannedSymbols.txt.TerminalOutputDeviceandCIEnvironmentDetectorboth follow the DI pattern — this class should too.- The static design prevents unit testing. Following the
CIEnvironmentDetectorpattern (non-static class with injectedIEnvironment) would enable proper test coverage.
Scope:
- The linked issue (#7647) requests explicit
TESTINGPLATFORM_NOPROGRESS/TESTINGPLATFORM_NOANSIenv vars. This PR takes the complementary auto-detection approach instead. Both are valuable — is the env var support planned as a follow-up?
Minor:
- Comment inside the
ifblock is now misleading after the condition change. ArgumentNullExceptionusage vs project'sArgumentGuardconvention.- The string composition in
GetLLMEnvironment()is unused — only the null check matters.
CI is green on completed checks. The integration point in TerminalOutputDevice is clean and minimal.
|
|
||
| // Copy from https://github.com/dotnet/sdk/tree/1e5d8e39d3026edb222cdf4f8d8240f1eb99f24b/src/Cli/Microsoft.DotNet.Cli.Definitions/Telemetry | ||
| internal static class LLMEnvironmentDetector | ||
| { |
There was a problem hiding this comment.
This class calls Environment.GetEnvironmentVariable() directly (suppressing RS0030 each time), but the project's BannedSymbols.txt bans the entire System.Environment type with the directive "Use 'IEnvironment' instead".
TerminalOutputDevice (the caller of this class) already properly uses the IEnvironment abstraction via constructor injection. CIEnvironmentDetector in TestFramework follows the same DI pattern.
Additionally, the static design (static class with a static auto-property initialized at class load) makes this completely untestable — you can't mock environment variables without actually setting them in the process, and the result is frozen for the process lifetime.
Suggestion: Follow the CIEnvironmentDetector pattern — make this a non-static class with constructor-injected IEnvironment. Provide a singleton default via a static Instance property if convenient static access is still desired. This aligns with the architectural conventions and enables unit testing.
|
|
||
| private static string? GetLLMEnvironment() | ||
| { | ||
| string?[] results = DetectionRules.Select(r => r.GetResult()).Where(r => r != null).ToArray(); |
There was a problem hiding this comment.
GetLLMEnvironment() builds a comma-separated string of all matched LLM environment names, but IsLLMEnvironment() only checks !RoslynString.IsNullOrEmpty(...) — the actual environment names are never exposed, logged, or returned to any caller. The generic EnvironmentDetectionRuleWithResult<T> adds complexity for a feature (returning which LLM was detected) that isn't currently used.
If the string is intended for future telemetry/logging, consider adding a comment documenting that intent. Otherwise, this could be simplified to a bool check that short-circuits on the first match (avoiding unnecessary allocations from Select + Where + ToArray + string.Join).
| { | ||
| private readonly string[] _variables; | ||
|
|
||
| public BooleanEnvironmentRule(params string[] variables) |
There was a problem hiding this comment.
The constructors use ?? throw new ArgumentNullException(nameof(...)) but the project's BannedSymbols.txt bans ArgumentNullException.#ctor in favor of ArgumentGuard.
Since this is a copy from dotnet/sdk, the deviation may be intentional to keep the code synchronized — but worth calling out for awareness. If strict alignment with project conventions is preferred, these should use ArgumentGuard.
| // In LLM environments, prefer simple text output so that LLM can parse it easily. | ||
| // Note that NoAnsi also implies no progress. | ||
| if (noAnsi || LLMEnvironmentDetector.IsLLMEnvironment()) | ||
| { |
There was a problem hiding this comment.
The comment inside this if block still reads "User explicitly specified --no-ansi. We should respect that." but the condition now also covers auto-detected LLM environments. Consider updating to reflect both paths, e.g.:
// User explicitly specified --no-ansi, or an LLM environment was auto-detected.
// Use plain text output for simpler parsing.(This was flagged as a suppressed low-confidence comment by the automated reviewer — I agree it should be addressed.)
There was a problem hiding this comment.
The linked issue (#7647) specifically requests adding TESTINGPLATFORM_NOPROGRESS and TESTINGPLATFORM_NOANSI environment variables as defaults (overridable by CLI args). This PR takes a different approach (auto-detection via heuristics). Both approaches are complementary — auto-detection is great for zero-config UX, but explicit env vars give users deterministic control regardless of whether their tool is in the detection list.
Consider whether the env var support from #7647 should also be included here, or if that's deferred to a follow-up. The naming convention would fit naturally alongside existing vars in EnvironmentVariableConstants.cs (TESTINGPLATFORM_NOBANNER, TESTINGPLATFORM_DIAGNOSTIC, etc.).
Fixes #7647