@@ -19,19 +19,20 @@ import {
1919 ENABLED_KEY_NAME ,
2020 METADATA_KEY_NAME ,
2121 ETAG_KEY_NAME ,
22- FEATURE_FLAG_ID_KEY_NAME ,
2322 FEATURE_FLAG_REFERENCE_KEY_NAME ,
2423 ALLOCATION_KEY_NAME ,
2524 SEED_KEY_NAME ,
2625 VARIANTS_KEY_NAME ,
2726 CONDITIONS_KEY_NAME ,
2827 CLIENT_FILTERS_KEY_NAME
2928} from "./featureManagement/constants.js" ;
30- import { FM_PACKAGE_NAME } from "./requestTracing/constants.js" ;
29+ import { FM_PACKAGE_NAME , AI_MIME_PROFILE , AI_CHAT_COMPLETION_MIME_PROFILE } from "./requestTracing/constants.js" ;
30+ import { parseContentType , isJsonContentType , isFeatureFlagContentType , isSecretReferenceContentType } from "./common/contentType.js" ;
3131import { AzureKeyVaultKeyValueAdapter } from "./keyvault/AzureKeyVaultKeyValueAdapter.js" ;
3232import { RefreshTimer } from "./refresh/RefreshTimer.js" ;
3333import { RequestTracingOptions , getConfigurationSettingWithTrace , listConfigurationSettingsWithTrace , requestTracingEnabled } from "./requestTracing/utils.js" ;
3434import { FeatureFlagTracingOptions } from "./requestTracing/FeatureFlagTracingOptions.js" ;
35+ import { AIConfigurationTracingOptions } from "./requestTracing/AIConfigurationTracingOptions.js" ;
3536import { KeyFilter , LabelFilter , SettingSelector } from "./types.js" ;
3637import { ConfigurationClientManager } from "./ConfigurationClientManager.js" ;
3738import { getFixedBackoffDuration , calculateBackoffDuration } from "./failover.js" ;
@@ -65,6 +66,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
6566 #isFailoverRequest: boolean = false ;
6667 #featureFlagTracing: FeatureFlagTracingOptions | undefined ;
6768 #fmVersion: string | undefined ;
69+ #aiConfigurationTracing: AIConfigurationTracingOptions | undefined ;
6870
6971 // Refresh
7072 #refreshInProgress: boolean = false ;
@@ -111,6 +113,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
111113 // enable request tracing if not opt-out
112114 this . #requestTracingEnabled = requestTracingEnabled ( ) ;
113115 if ( this . #requestTracingEnabled) {
116+ this . #aiConfigurationTracing = new AIConfigurationTracingOptions ( ) ;
114117 this . #featureFlagTracing = new FeatureFlagTracingOptions ( ) ;
115118 }
116119
@@ -191,7 +194,8 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
191194 replicaCount : this . #clientManager. getReplicaCount ( ) ,
192195 isFailoverRequest : this . #isFailoverRequest,
193196 featureFlagTracing : this . #featureFlagTracing,
194- fmVersion : this . #fmVersion
197+ fmVersion : this . #fmVersion,
198+ aiConfigurationTracing : this . #aiConfigurationTracing
195199 } ;
196200 }
197201
@@ -489,19 +493,24 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
489493 * Loads selected key-values and watched settings (sentinels) for refresh from App Configuration to the local configuration.
490494 */
491495 async #loadSelectedAndWatchedKeyValues( ) {
496+ this . #secretReferences = [ ] ; // clear all cached key vault reference configuration settings
492497 const keyValues : [ key : string , value : unknown ] [ ] = [ ] ;
493498 const loadedSettings = await this . #loadConfigurationSettings( ) ;
494499 if ( this . #refreshEnabled && ! this . #watchAll) {
495500 await this . #updateWatchedKeyValuesEtag( loadedSettings ) ;
496501 }
497502
498- // clear all cached key vault reference configuration settings
499- this . #secretReferences = [ ] ;
503+ if ( this . #requestTracingEnabled && this . #aiConfigurationTracing !== undefined ) {
504+ // reset old AI configuration tracing in order to track the information present in the current response from server
505+ this . #aiConfigurationTracing. reset ( ) ;
506+ }
507+
508+ // adapt configuration settings to key-values
500509 for ( const setting of loadedSettings ) {
501510 if ( this . #secretRefreshEnabled && isSecretReference ( setting ) ) {
502511 this . #secretReferences. push ( setting ) ;
503512 }
504- const [ key , value ] = await this . #processKeyValues ( setting ) ;
513+ const [ key , value ] = await this . #processKeyValue ( setting ) ;
505514 keyValues . push ( [ key , value ] ) ;
506515 }
507516
@@ -550,6 +559,11 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
550559 const loadFeatureFlag = true ;
551560 const featureFlagSettings = await this . #loadConfigurationSettings( loadFeatureFlag ) ;
552561
562+ if ( this . #requestTracingEnabled && this . #featureFlagTracing !== undefined ) {
563+ // Reset old feature flag tracing in order to track the information present in the current response from server.
564+ this . #featureFlagTracing. reset ( ) ;
565+ }
566+
553567 // parse feature flags
554568 const featureFlags = await Promise . all (
555569 featureFlagSettings . map ( setting => this . #parseFeatureFlag( setting ) )
@@ -625,7 +639,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
625639 }
626640
627641 for ( const setting of this . #secretReferences) {
628- const [ key , value ] = await this . #processKeyValues ( setting ) ;
642+ const [ key , value ] = await this . #processKeyValue ( setting ) ;
629643 this . #configMap. set ( key , value ) ;
630644 }
631645
@@ -735,12 +749,35 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
735749 throw new Error ( "All fallback clients failed to get configuration settings." ) ;
736750 }
737751
738- async #processKeyValues( setting : ConfigurationSetting < string > ) : Promise < [ string , unknown ] > {
752+ async #processKeyValue( setting : ConfigurationSetting < string > ) : Promise < [ string , unknown ] > {
753+ this . #setAIConfigurationTracing( setting ) ;
754+
739755 const [ key , value ] = await this . #processAdapters( setting ) ;
740756 const trimmedKey = this . #keyWithPrefixesTrimmed( key ) ;
741757 return [ trimmedKey , value ] ;
742758 }
743759
760+ #setAIConfigurationTracing( setting : ConfigurationSetting < string > ) : void {
761+ if ( this . #requestTracingEnabled && this . #aiConfigurationTracing !== undefined ) {
762+ const contentType = parseContentType ( setting . contentType ) ;
763+ // content type: "application/json; profile=\"https://azconfig.io/mime-profiles/ai\"""
764+ if ( isJsonContentType ( contentType ) &&
765+ ! isFeatureFlagContentType ( contentType ) &&
766+ ! isSecretReferenceContentType ( contentType ) ) {
767+ const profile = contentType ?. parameters [ "profile" ] ;
768+ if ( profile === undefined ) {
769+ return ;
770+ }
771+ if ( profile . includes ( AI_MIME_PROFILE ) ) {
772+ this . #aiConfigurationTracing. usesAIConfiguration = true ;
773+ }
774+ if ( profile . includes ( AI_CHAT_COMPLETION_MIME_PROFILE ) ) {
775+ this . #aiConfigurationTracing. usesAIChatCompletionConfiguration = true ;
776+ }
777+ }
778+ }
779+ }
780+
744781 async #processAdapters( setting : ConfigurationSetting < string > ) : Promise < [ string , unknown ] > {
745782 for ( const adapter of this . #adapters) {
746783 if ( adapter . canProcess ( setting ) ) {
@@ -772,12 +809,25 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
772809 const metadata = featureFlag [ TELEMETRY_KEY_NAME ] [ METADATA_KEY_NAME ] ;
773810 featureFlag [ TELEMETRY_KEY_NAME ] [ METADATA_KEY_NAME ] = {
774811 [ ETAG_KEY_NAME ] : setting . etag ,
775- [ FEATURE_FLAG_ID_KEY_NAME ] : await this . #calculateFeatureFlagId( setting ) ,
776812 [ FEATURE_FLAG_REFERENCE_KEY_NAME ] : this . #createFeatureFlagReference( setting ) ,
777813 ...( metadata || { } )
778814 } ;
779815 }
780816
817+ this . #setFeatureFlagTracing( featureFlag ) ;
818+
819+ return featureFlag ;
820+ }
821+
822+ #createFeatureFlagReference( setting : ConfigurationSetting < string > ) : string {
823+ let featureFlagReference = `${ this . #clientManager. endpoint . origin } /kv/${ setting . key } ` ;
824+ if ( setting . label && setting . label . trim ( ) . length !== 0 ) {
825+ featureFlagReference += `?label=${ setting . label } ` ;
826+ }
827+ return featureFlagReference ;
828+ }
829+
830+ #setFeatureFlagTracing( featureFlag : any ) : void {
781831 if ( this . #requestTracingEnabled && this . #featureFlagTracing !== undefined ) {
782832 if ( featureFlag [ CONDITIONS_KEY_NAME ] &&
783833 featureFlag [ CONDITIONS_KEY_NAME ] [ CLIENT_FILTERS_KEY_NAME ] &&
@@ -796,66 +846,6 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
796846 this . #featureFlagTracing. usesSeed = true ;
797847 }
798848 }
799-
800- return featureFlag ;
801- }
802-
803- async #calculateFeatureFlagId( setting : ConfigurationSetting < string > ) : Promise < string > {
804- let crypto ;
805-
806- // Check for browser environment
807- if ( typeof window !== "undefined" && window . crypto && window . crypto . subtle ) {
808- crypto = window . crypto ;
809- }
810- // Check for Node.js environment
811- else if ( typeof global !== "undefined" && global . crypto ) {
812- crypto = global . crypto ;
813- }
814- // Fallback to native Node.js crypto module
815- else {
816- try {
817- if ( typeof module !== "undefined" && module . exports ) {
818- crypto = require ( "crypto" ) ;
819- }
820- else {
821- crypto = await import ( "crypto" ) ;
822- }
823- } catch ( error ) {
824- console . error ( "Failed to load the crypto module:" , error . message ) ;
825- throw error ;
826- }
827- }
828-
829- let baseString = `${ setting . key } \n` ;
830- if ( setting . label && setting . label . trim ( ) . length !== 0 ) {
831- baseString += `${ setting . label } ` ;
832- }
833-
834- // Convert to UTF-8 encoded bytes
835- const data = new TextEncoder ( ) . encode ( baseString ) ;
836-
837- // In the browser, use crypto.subtle.digest
838- if ( crypto . subtle ) {
839- const hashBuffer = await crypto . subtle . digest ( "SHA-256" , data ) ;
840- const hashArray = new Uint8Array ( hashBuffer ) ;
841- // btoa/atob is also available in Node.js 18+
842- const base64String = btoa ( String . fromCharCode ( ...hashArray ) ) ;
843- const base64urlString = base64String . replace ( / \+ / g, "-" ) . replace ( / \/ / g, "_" ) . replace ( / = + $ / , "" ) ;
844- return base64urlString ;
845- }
846- // In Node.js, use the crypto module's hash function
847- else {
848- const hash = crypto . createHash ( "sha256" ) . update ( data ) . digest ( ) ;
849- return hash . toString ( "base64url" ) ;
850- }
851- }
852-
853- #createFeatureFlagReference( setting : ConfigurationSetting < string > ) : string {
854- let featureFlagReference = `${ this . #clientManager. endpoint . origin } /kv/${ setting . key } ` ;
855- if ( setting . label && setting . label . trim ( ) . length !== 0 ) {
856- featureFlagReference += `?label=${ setting . label } ` ;
857- }
858- return featureFlagReference ;
859849 }
860850}
861851
0 commit comments