88#endif
99using System . Globalization ;
1010using System . Runtime . InteropServices ;
11+ using System . Text ;
12+ using NuGet . Common ;
1113using NuGet . Packaging ;
1214
1315namespace NuGet . Protocol . Core . Types
@@ -16,27 +18,38 @@ public class UserAgentStringBuilder
1618 {
1719 public static readonly string DefaultNuGetClientName = "NuGet Client V3" ;
1820
19- private const string UserAgentWithOSDescriptionAndVisualStudioSKUTemplate = "{0}/{1} ({2}, {3})" ;
20- private const string UserAgentWithOSDescriptionTemplate = "{0}/{1} ({2})" ;
21+ private const string UserAgentWithMetadataTemplate = "{0}/{1} ({2})" ;
2122 private const string UserAgentTemplate = "{0}/{1}" ;
2223
2324 private readonly string _clientName ;
2425 private string _vsInfo ;
2526 private string _osInfo ;
27+ private string _ciInfo ;
2628
2729 public UserAgentStringBuilder ( )
2830 : this ( DefaultNuGetClientName )
2931 {
3032 }
3133
3234 public UserAgentStringBuilder ( string clientName )
35+ : this ( clientName , EnvironmentVariableWrapper . Instance )
36+ {
37+ }
38+
39+ /// <summary>
40+ /// Internal constructor for testing purposes that allows injecting an environment variable reader.
41+ /// </summary>
42+ /// <param name="clientName">The client name to use in the user agent string.</param>
43+ /// <param name="environmentVariableReader">The environment variable reader for CI detection.</param>
44+ internal UserAgentStringBuilder ( string clientName , IEnvironmentVariableReader environmentVariableReader )
3345 {
3446 _clientName = clientName ;
3547
3648 // Read the client version from the assembly metadata and normalize it.
3749 NuGetClientVersion = MinClientVersionUtility . GetNuGetClientVersion ( ) . ToNormalizedString ( ) ;
3850
3951 _osInfo = GetOS ( ) ;
52+ _ciInfo = CIEnvironmentDetector . Detect ( environmentVariableReader ) ;
4053 }
4154
4255 public string NuGetClientVersion { get ; }
@@ -59,7 +72,9 @@ public string Build()
5972 clientInfo = DefaultNuGetClientName ;
6073 }
6174
62- if ( string . IsNullOrEmpty ( _osInfo ) )
75+ string metadataString = BuildMetadataString ( ) ;
76+
77+ if ( string . IsNullOrEmpty ( metadataString ) )
6378 {
6479 return string . Format (
6580 CultureInfo . InvariantCulture ,
@@ -68,25 +83,43 @@ public string Build()
6883 NuGetClientVersion ) ;
6984 }
7085
71- if ( string . IsNullOrEmpty ( _vsInfo ) )
86+ return string . Format (
87+ CultureInfo . InvariantCulture ,
88+ UserAgentWithMetadataTemplate ,
89+ clientInfo ,
90+ NuGetClientVersion ,
91+ metadataString ) ;
92+ }
93+
94+ /// <summary>
95+ /// Builds the metadata string for the parentheses section.
96+ /// Items are collected in order (OS, CI, VS) and joined with ", ".
97+ /// </summary>
98+ internal string BuildMetadataString ( )
99+ {
100+ var sb = new StringBuilder ( ) ;
101+
102+ // OS info
103+ if ( ! string . IsNullOrEmpty ( _osInfo ) )
72104 {
73- return string . Format (
74- CultureInfo . InvariantCulture ,
75- UserAgentWithOSDescriptionTemplate ,
76- clientInfo ,
77- NuGetClientVersion ,
78- _osInfo ) ;
105+ sb . Append ( _osInfo ) ;
79106 }
80- else
107+
108+ // CI info (formatted as "CI: {provider}")
109+ if ( ! string . IsNullOrEmpty ( _ciInfo ) )
81110 {
82- return string . Format (
83- CultureInfo . InvariantCulture ,
84- UserAgentWithOSDescriptionAndVisualStudioSKUTemplate ,
85- _clientName ,
86- NuGetClientVersion , /* NuGet version */
87- _osInfo , /* OS version */
88- _vsInfo ) ; /* VS SKU + version */
111+ if ( sb . Length > 0 ) sb . Append ( ", " ) ;
112+ sb . Append ( "CI: " ) . Append ( _ciInfo ) ;
89113 }
114+
115+ // VS info
116+ if ( ! string . IsNullOrEmpty ( _vsInfo ) )
117+ {
118+ if ( sb . Length > 0 ) sb . Append ( ", " ) ;
119+ sb . Append ( _vsInfo ) ;
120+ }
121+
122+ return sb . ToString ( ) ;
90123 }
91124
92125 internal static string GetOS ( )
0 commit comments