@@ -18,19 +18,20 @@ import {
1818 ENABLED_KEY_NAME ,
1919 METADATA_KEY_NAME ,
2020 ETAG_KEY_NAME ,
21- FEATURE_FLAG_ID_KEY_NAME ,
2221 FEATURE_FLAG_REFERENCE_KEY_NAME ,
2322 ALLOCATION_KEY_NAME ,
2423 SEED_KEY_NAME ,
2524 VARIANTS_KEY_NAME ,
2625 CONDITIONS_KEY_NAME ,
2726 CLIENT_FILTERS_KEY_NAME
2827} from "./featureManagement/constants.js" ;
29- import { FM_PACKAGE_NAME } from "./requestTracing/constants.js" ;
28+ import { FM_PACKAGE_NAME , AI_MIME_PROFILE , AI_CHAT_COMPLETION_MIME_PROFILE } from "./requestTracing/constants.js" ;
29+ import { parseContentType , isJsonContentType , isFeatureFlagContentType , isSecretReferenceContentType } from "./common/contentType.js" ;
3030import { AzureKeyVaultKeyValueAdapter } from "./keyvault/AzureKeyVaultKeyValueAdapter.js" ;
3131import { RefreshTimer } from "./refresh/RefreshTimer.js" ;
3232import { RequestTracingOptions , getConfigurationSettingWithTrace , listConfigurationSettingsWithTrace , requestTracingEnabled } from "./requestTracing/utils.js" ;
3333import { FeatureFlagTracingOptions } from "./requestTracing/FeatureFlagTracingOptions.js" ;
34+ import { AIConfigurationTracingOptions } from "./requestTracing/AIConfigurationTracingOptions.js" ;
3435import { KeyFilter , LabelFilter , SettingSelector } from "./types.js" ;
3536import { ConfigurationClientManager } from "./ConfigurationClientManager.js" ;
3637import { getFixedBackoffDuration , calculateBackoffDuration } from "./failover.js" ;
@@ -64,6 +65,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
6465 #isFailoverRequest: boolean = false ;
6566 #featureFlagTracing: FeatureFlagTracingOptions | undefined ;
6667 #fmVersion: string | undefined ;
68+ #aiConfigurationTracing: AIConfigurationTracingOptions | undefined ;
6769
6870 // Refresh
6971 #refreshInProgress: boolean = false ;
@@ -103,6 +105,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
103105 // enable request tracing if not opt-out
104106 this . #requestTracingEnabled = requestTracingEnabled ( ) ;
105107 if ( this . #requestTracingEnabled) {
108+ this . #aiConfigurationTracing = new AIConfigurationTracingOptions ( ) ;
106109 this . #featureFlagTracing = new FeatureFlagTracingOptions ( ) ;
107110 }
108111
@@ -184,7 +187,8 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
184187 replicaCount : this . #clientManager. getReplicaCount ( ) ,
185188 isFailoverRequest : this . #isFailoverRequest,
186189 featureFlagTracing : this . #featureFlagTracing,
187- fmVersion : this . #fmVersion
190+ fmVersion : this . #fmVersion,
191+ aiConfigurationTracing : this . #aiConfigurationTracing
188192 } ;
189193 }
190194
@@ -485,9 +489,14 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
485489 await this . #updateWatchedKeyValuesEtag( loadedSettings ) ;
486490 }
487491
492+ if ( this . #requestTracingEnabled && this . #aiConfigurationTracing !== undefined ) {
493+ // Reset old AI configuration tracing in order to track the information present in the current response from server.
494+ this . #aiConfigurationTracing. reset ( ) ;
495+ }
496+
488497 // adapt configuration settings to key-values
489498 for ( const setting of loadedSettings ) {
490- const [ key , value ] = await this . #processKeyValues ( setting ) ;
499+ const [ key , value ] = await this . #processKeyValue ( setting ) ;
491500 keyValues . push ( [ key , value ] ) ;
492501 }
493502
@@ -536,6 +545,11 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
536545 const loadFeatureFlag = true ;
537546 const featureFlagSettings = await this . #loadConfigurationSettings( loadFeatureFlag ) ;
538547
548+ if ( this . #requestTracingEnabled && this . #featureFlagTracing !== undefined ) {
549+ // Reset old feature flag tracing in order to track the information present in the current response from server.
550+ this . #featureFlagTracing. reset ( ) ;
551+ }
552+
539553 // parse feature flags
540554 const featureFlags = await Promise . all (
541555 featureFlagSettings . map ( setting => this . #parseFeatureFlag( setting ) )
@@ -703,12 +717,35 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
703717 throw new Error ( "All fallback clients failed to get configuration settings." ) ;
704718 }
705719
706- async #processKeyValues( setting : ConfigurationSetting < string > ) : Promise < [ string , unknown ] > {
720+ async #processKeyValue( setting : ConfigurationSetting < string > ) : Promise < [ string , unknown ] > {
721+ this . #setAIConfigurationTracing( setting ) ;
722+
707723 const [ key , value ] = await this . #processAdapters( setting ) ;
708724 const trimmedKey = this . #keyWithPrefixesTrimmed( key ) ;
709725 return [ trimmedKey , value ] ;
710726 }
711727
728+ #setAIConfigurationTracing( setting : ConfigurationSetting < string > ) : void {
729+ if ( this . #requestTracingEnabled && this . #aiConfigurationTracing !== undefined ) {
730+ const contentType = parseContentType ( setting . contentType ) ;
731+ // content type: "application/json; profile=\"https://azconfig.io/mime-profiles/ai\"""
732+ if ( isJsonContentType ( contentType ) &&
733+ ! isFeatureFlagContentType ( contentType ) &&
734+ ! isSecretReferenceContentType ( contentType ) ) {
735+ const profile = contentType ?. parameters [ "profile" ] ;
736+ if ( profile === undefined ) {
737+ return ;
738+ }
739+ if ( profile . includes ( AI_MIME_PROFILE ) ) {
740+ this . #aiConfigurationTracing. usesAIConfiguration = true ;
741+ }
742+ if ( profile . includes ( AI_CHAT_COMPLETION_MIME_PROFILE ) ) {
743+ this . #aiConfigurationTracing. usesAIChatCompletionConfiguration = true ;
744+ }
745+ }
746+ }
747+ }
748+
712749 async #processAdapters( setting : ConfigurationSetting < string > ) : Promise < [ string , unknown ] > {
713750 for ( const adapter of this . #adapters) {
714751 if ( adapter . canProcess ( setting ) ) {
@@ -740,12 +777,25 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
740777 const metadata = featureFlag [ TELEMETRY_KEY_NAME ] [ METADATA_KEY_NAME ] ;
741778 featureFlag [ TELEMETRY_KEY_NAME ] [ METADATA_KEY_NAME ] = {
742779 [ ETAG_KEY_NAME ] : setting . etag ,
743- [ FEATURE_FLAG_ID_KEY_NAME ] : await this . #calculateFeatureFlagId( setting ) ,
744780 [ FEATURE_FLAG_REFERENCE_KEY_NAME ] : this . #createFeatureFlagReference( setting ) ,
745781 ...( metadata || { } )
746782 } ;
747783 }
748784
785+ this . #setFeatureFlagTracing( featureFlag ) ;
786+
787+ return featureFlag ;
788+ }
789+
790+ #createFeatureFlagReference( setting : ConfigurationSetting < string > ) : string {
791+ let featureFlagReference = `${ this . #clientManager. endpoint . origin } /kv/${ setting . key } ` ;
792+ if ( setting . label && setting . label . trim ( ) . length !== 0 ) {
793+ featureFlagReference += `?label=${ setting . label } ` ;
794+ }
795+ return featureFlagReference ;
796+ }
797+
798+ #setFeatureFlagTracing( featureFlag : any ) : void {
749799 if ( this . #requestTracingEnabled && this . #featureFlagTracing !== undefined ) {
750800 if ( featureFlag [ CONDITIONS_KEY_NAME ] &&
751801 featureFlag [ CONDITIONS_KEY_NAME ] [ CLIENT_FILTERS_KEY_NAME ] &&
@@ -764,66 +814,6 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
764814 this . #featureFlagTracing. usesSeed = true ;
765815 }
766816 }
767-
768- return featureFlag ;
769- }
770-
771- async #calculateFeatureFlagId( setting : ConfigurationSetting < string > ) : Promise < string > {
772- let crypto ;
773-
774- // Check for browser environment
775- if ( typeof window !== "undefined" && window . crypto && window . crypto . subtle ) {
776- crypto = window . crypto ;
777- }
778- // Check for Node.js environment
779- else if ( typeof global !== "undefined" && global . crypto ) {
780- crypto = global . crypto ;
781- }
782- // Fallback to native Node.js crypto module
783- else {
784- try {
785- if ( typeof module !== "undefined" && module . exports ) {
786- crypto = require ( "crypto" ) ;
787- }
788- else {
789- crypto = await import ( "crypto" ) ;
790- }
791- } catch ( error ) {
792- console . error ( "Failed to load the crypto module:" , error . message ) ;
793- throw error ;
794- }
795- }
796-
797- let baseString = `${ setting . key } \n` ;
798- if ( setting . label && setting . label . trim ( ) . length !== 0 ) {
799- baseString += `${ setting . label } ` ;
800- }
801-
802- // Convert to UTF-8 encoded bytes
803- const data = new TextEncoder ( ) . encode ( baseString ) ;
804-
805- // In the browser, use crypto.subtle.digest
806- if ( crypto . subtle ) {
807- const hashBuffer = await crypto . subtle . digest ( "SHA-256" , data ) ;
808- const hashArray = new Uint8Array ( hashBuffer ) ;
809- // btoa/atob is also available in Node.js 18+
810- const base64String = btoa ( String . fromCharCode ( ...hashArray ) ) ;
811- const base64urlString = base64String . replace ( / \+ / g, "-" ) . replace ( / \/ / g, "_" ) . replace ( / = + $ / , "" ) ;
812- return base64urlString ;
813- }
814- // In Node.js, use the crypto module's hash function
815- else {
816- const hash = crypto . createHash ( "sha256" ) . update ( data ) . digest ( ) ;
817- return hash . toString ( "base64url" ) ;
818- }
819- }
820-
821- #createFeatureFlagReference( setting : ConfigurationSetting < string > ) : string {
822- let featureFlagReference = `${ this . #clientManager. endpoint . origin } /kv/${ setting . key } ` ;
823- if ( setting . label && setting . label . trim ( ) . length !== 0 ) {
824- featureFlagReference += `?label=${ setting . label } ` ;
825- }
826- return featureFlagReference ;
827817 }
828818}
829819
0 commit comments