@@ -29,15 +29,15 @@ import {
2929 ENABLED_KEY_NAME ,
3030 METADATA_KEY_NAME ,
3131 ETAG_KEY_NAME ,
32- FEATURE_FLAG_ID_KEY_NAME ,
3332 FEATURE_FLAG_REFERENCE_KEY_NAME ,
3433 ALLOCATION_KEY_NAME ,
3534 SEED_KEY_NAME ,
3635 VARIANTS_KEY_NAME ,
3736 CONDITIONS_KEY_NAME ,
3837 CLIENT_FILTERS_KEY_NAME
3938} from "./featureManagement/constants.js" ;
40- import { FM_PACKAGE_NAME } from "./requestTracing/constants.js" ;
39+ import { FM_PACKAGE_NAME , AI_MIME_PROFILE , AI_CHAT_COMPLETION_MIME_PROFILE } from "./requestTracing/constants.js" ;
40+ import { parseContentType , isJsonContentType , isFeatureFlagContentType , isSecretReferenceContentType } from "./common/contentType.js" ;
4141import { AzureKeyVaultKeyValueAdapter } from "./keyvault/AzureKeyVaultKeyValueAdapter.js" ;
4242import { RefreshTimer } from "./refresh/RefreshTimer.js" ;
4343import {
@@ -49,6 +49,7 @@ import {
4949 requestTracingEnabled
5050} from "./requestTracing/utils.js" ;
5151import { FeatureFlagTracingOptions } from "./requestTracing/FeatureFlagTracingOptions.js" ;
52+ import { AIConfigurationTracingOptions } from "./requestTracing/AIConfigurationTracingOptions.js" ;
5253import { KeyFilter , LabelFilter , SettingSelector } from "./types.js" ;
5354import { ConfigurationClientManager } from "./ConfigurationClientManager.js" ;
5455
@@ -78,6 +79,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
7879 #isFailoverRequest: boolean = false ;
7980 #featureFlagTracing: FeatureFlagTracingOptions | undefined ;
8081 #fmVersion: string | undefined ;
82+ #aiConfigurationTracing: AIConfigurationTracingOptions | undefined ;
8183
8284 // Refresh
8385 #refreshInProgress: boolean = false ;
@@ -117,6 +119,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
117119 // enable request tracing if not opt-out
118120 this . #requestTracingEnabled = requestTracingEnabled ( ) ;
119121 if ( this . #requestTracingEnabled) {
122+ this . #aiConfigurationTracing = new AIConfigurationTracingOptions ( ) ;
120123 this . #featureFlagTracing = new FeatureFlagTracingOptions ( ) ;
121124 }
122125
@@ -198,7 +201,8 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
198201 replicaCount : this . #clientManager. getReplicaCount ( ) ,
199202 isFailoverRequest : this . #isFailoverRequest,
200203 featureFlagTracing : this . #featureFlagTracing,
201- fmVersion : this . #fmVersion
204+ fmVersion : this . #fmVersion,
205+ aiConfigurationTracing : this . #aiConfigurationTracing
202206 } ;
203207 }
204208
@@ -459,9 +463,14 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
459463 await this . #updateWatchedKeyValuesEtag( loadedSettings ) ;
460464 }
461465
466+ if ( this . #requestTracingEnabled && this . #aiConfigurationTracing !== undefined ) {
467+ // Reset old AI configuration tracing in order to track the information present in the current response from server.
468+ this . #aiConfigurationTracing. reset ( ) ;
469+ }
470+
462471 // process key-values, watched settings have higher priority
463472 for ( const setting of loadedSettings ) {
464- const [ key , value ] = await this . #processKeyValues ( setting ) ;
473+ const [ key , value ] = await this . #processKeyValue ( setting ) ;
465474 keyValues . push ( [ key , value ] ) ;
466475 }
467476
@@ -510,6 +519,11 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
510519 const loadFeatureFlag = true ;
511520 const featureFlagSettings = await this . #loadConfigurationSettings( loadFeatureFlag ) ;
512521
522+ if ( this . #requestTracingEnabled && this . #featureFlagTracing !== undefined ) {
523+ // Reset old feature flag tracing in order to track the information present in the current response from server.
524+ this . #featureFlagTracing. reset ( ) ;
525+ }
526+
513527 // parse feature flags
514528 const featureFlags = await Promise . all (
515529 featureFlagSettings . map ( setting => this . #parseFeatureFlag( setting ) )
@@ -702,12 +716,35 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
702716 throw new Error ( "All clients failed to get configuration settings." ) ;
703717 }
704718
705- async #processKeyValues( setting : ConfigurationSetting < string > ) : Promise < [ string , unknown ] > {
719+ async #processKeyValue( setting : ConfigurationSetting < string > ) : Promise < [ string , unknown ] > {
720+ this . #setAIConfigurationTracing( setting ) ;
721+
706722 const [ key , value ] = await this . #processAdapters( setting ) ;
707723 const trimmedKey = this . #keyWithPrefixesTrimmed( key ) ;
708724 return [ trimmedKey , value ] ;
709725 }
710726
727+ #setAIConfigurationTracing( setting : ConfigurationSetting < string > ) : void {
728+ if ( this . #requestTracingEnabled && this . #aiConfigurationTracing !== undefined ) {
729+ const contentType = parseContentType ( setting . contentType ) ;
730+ // content type: "application/json; profile=\"https://azconfig.io/mime-profiles/ai\"""
731+ if ( isJsonContentType ( contentType ) &&
732+ ! isFeatureFlagContentType ( contentType ) &&
733+ ! isSecretReferenceContentType ( contentType ) ) {
734+ const profile = contentType ?. parameters [ "profile" ] ;
735+ if ( profile === undefined ) {
736+ return ;
737+ }
738+ if ( profile . includes ( AI_MIME_PROFILE ) ) {
739+ this . #aiConfigurationTracing. usesAIConfiguration = true ;
740+ }
741+ if ( profile . includes ( AI_CHAT_COMPLETION_MIME_PROFILE ) ) {
742+ this . #aiConfigurationTracing. usesAIChatCompletionConfiguration = true ;
743+ }
744+ }
745+ }
746+ }
747+
711748 async #processAdapters( setting : ConfigurationSetting < string > ) : Promise < [ string , unknown ] > {
712749 for ( const adapter of this . #adapters) {
713750 if ( adapter . canProcess ( setting ) ) {
@@ -739,12 +776,25 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
739776 const metadata = featureFlag [ TELEMETRY_KEY_NAME ] [ METADATA_KEY_NAME ] ;
740777 featureFlag [ TELEMETRY_KEY_NAME ] [ METADATA_KEY_NAME ] = {
741778 [ ETAG_KEY_NAME ] : setting . etag ,
742- [ FEATURE_FLAG_ID_KEY_NAME ] : await this . #calculateFeatureFlagId( setting ) ,
743779 [ FEATURE_FLAG_REFERENCE_KEY_NAME ] : this . #createFeatureFlagReference( setting ) ,
744780 ...( metadata || { } )
745781 } ;
746782 }
747783
784+ this . #setFeatureFlagTracing( featureFlag ) ;
785+
786+ return featureFlag ;
787+ }
788+
789+ #createFeatureFlagReference( setting : ConfigurationSetting < string > ) : string {
790+ let featureFlagReference = `${ this . #clientManager. endpoint . origin } /kv/${ setting . key } ` ;
791+ if ( setting . label && setting . label . trim ( ) . length !== 0 ) {
792+ featureFlagReference += `?label=${ setting . label } ` ;
793+ }
794+ return featureFlagReference ;
795+ }
796+
797+ #setFeatureFlagTracing( featureFlag : any ) : void {
748798 if ( this . #requestTracingEnabled && this . #featureFlagTracing !== undefined ) {
749799 if ( featureFlag [ CONDITIONS_KEY_NAME ] &&
750800 featureFlag [ CONDITIONS_KEY_NAME ] [ CLIENT_FILTERS_KEY_NAME ] &&
@@ -763,66 +813,6 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
763813 this . #featureFlagTracing. usesSeed = true ;
764814 }
765815 }
766-
767- return featureFlag ;
768- }
769-
770- async #calculateFeatureFlagId( setting : ConfigurationSetting < string > ) : Promise < string > {
771- let crypto ;
772-
773- // Check for browser environment
774- if ( typeof window !== "undefined" && window . crypto && window . crypto . subtle ) {
775- crypto = window . crypto ;
776- }
777- // Check for Node.js environment
778- else if ( typeof global !== "undefined" && global . crypto ) {
779- crypto = global . crypto ;
780- }
781- // Fallback to native Node.js crypto module
782- else {
783- try {
784- if ( typeof module !== "undefined" && module . exports ) {
785- crypto = require ( "crypto" ) ;
786- }
787- else {
788- crypto = await import ( "crypto" ) ;
789- }
790- } catch ( error ) {
791- console . error ( "Failed to load the crypto module:" , error . message ) ;
792- throw error ;
793- }
794- }
795-
796- let baseString = `${ setting . key } \n` ;
797- if ( setting . label && setting . label . trim ( ) . length !== 0 ) {
798- baseString += `${ setting . label } ` ;
799- }
800-
801- // Convert to UTF-8 encoded bytes
802- const data = new TextEncoder ( ) . encode ( baseString ) ;
803-
804- // In the browser, use crypto.subtle.digest
805- if ( crypto . subtle ) {
806- const hashBuffer = await crypto . subtle . digest ( "SHA-256" , data ) ;
807- const hashArray = new Uint8Array ( hashBuffer ) ;
808- // btoa/atob is also available in Node.js 18+
809- const base64String = btoa ( String . fromCharCode ( ...hashArray ) ) ;
810- const base64urlString = base64String . replace ( / \+ / g, "-" ) . replace ( / \/ / g, "_" ) . replace ( / = + $ / , "" ) ;
811- return base64urlString ;
812- }
813- // In Node.js, use the crypto module's hash function
814- else {
815- const hash = crypto . createHash ( "sha256" ) . update ( data ) . digest ( ) ;
816- return hash . toString ( "base64url" ) ;
817- }
818- }
819-
820- #createFeatureFlagReference( setting : ConfigurationSetting < string > ) : string {
821- let featureFlagReference = `${ this . #clientManager. endpoint . origin } /kv/${ setting . key } ` ;
822- if ( setting . label && setting . label . trim ( ) . length !== 0 ) {
823- featureFlagReference += `?label=${ setting . label } ` ;
824- }
825- return featureFlagReference ;
826816 }
827817}
828818
0 commit comments