66import { DeferredPromise } from '../../../base/common/async.js' ;
77import { CancellationToken } from '../../../base/common/cancellation.js' ;
88import { Emitter , Event } from '../../../base/common/event.js' ;
9+ import { generateUuid } from '../../../base/common/uuid.js' ;
910import { IMarkdownString } from '../../../base/common/htmlContent.js' ;
1011import { Disposable , DisposableMap , IDisposable } from '../../../base/common/lifecycle.js' ;
1112import { revive } from '../../../base/common/marshalling.js' ;
@@ -41,12 +42,13 @@ import { ILanguageModelToolsService } from '../../contrib/chat/common/tools/lang
4142import { IExtHostContext , extHostNamedCustomer } from '../../services/extensions/common/extHostCustomers.js' ;
4243import { IExtensionService } from '../../services/extensions/common/extensions.js' ;
4344import { Dto } from '../../services/extensions/common/proxyIdentifier.js' ;
44- import { ExtHostChatAgentsShape2 , ExtHostContext , IChatSessionCustomizationItemDto , IChatSessionCustomizationProviderMetadataDto , IChatNotebookEditDto , IChatParticipantMetadata , IChatProgressDto , IChatSessionContextDto , ICustomAgentDto , IDynamicChatAgentProps , IExtensionChatAgentMetadata , IInstructionDto , ISkillDto , MainContext , MainThreadChatAgentsShape2 } from '../common/extHost.protocol.js' ;
45+ import { ExtHostChatAgentsShape2 , ExtHostContext , IChatSessionCustomizationChangeEventDto , IChatSessionCustomizationProviderMetadataDto , IChatNotebookEditDto , IChatParticipantMetadata , IChatProgressDto , IChatSessionContextDto , ICustomAgentDto , IDynamicChatAgentProps , IExtensionChatAgentMetadata , IInstructionDto , ISkillDto , MainContext , MainThreadChatAgentsShape2 } from '../common/extHost.protocol.js' ;
4546import { NotebookDto } from './mainThreadNotebookDto.js' ;
4647import { isUntitledChatSession } from '../../contrib/chat/common/model/chatUri.js' ;
47- import { ICustomizationHarnessService , IExternalCustomizationItem , IExternalCustomizationItemProvider , IHarnessDescriptor } from '../../contrib/chat/common/customizationHarnessService.js' ;
48+ import { ICustomizationHarnessService , IExternalCustomizationChangeEvent , IExternalCustomizationItem , IExternalCustomizationItemProvider , IExternalCustomizationResult , IHarnessDescriptor } from '../../contrib/chat/common/customizationHarnessService.js' ;
4849import { AICustomizationManagementSection } from '../../contrib/chat/common/aiCustomizationWorkspaceService.js' ;
4950import { IConfigurationService } from '../../../platform/configuration/common/configuration.js' ;
51+ import { ChatDebugLogLevel , IChatDebugResolvedEventContent , IChatDebugService } from '../../contrib/chat/common/chatDebugService.js' ;
5052
5153interface AgentData {
5254 dispose : ( ) => void ;
@@ -107,7 +109,9 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA
107109 private readonly _promptFileContentRegistrations = this . _register ( new DisposableMap < number , DisposableMap < string , IDisposable > > ( ) ) ;
108110
109111 private readonly _customizationProviders = this . _register ( new DisposableMap < number , IDisposable > ( ) ) ;
110- private readonly _customizationProviderEmitters = this . _register ( new DisposableMap < number , Emitter < void > > ( ) ) ;
112+ private readonly _customizationProviderEmitters = this . _register ( new DisposableMap < number , Emitter < IExternalCustomizationChangeEvent > > ( ) ) ;
113+ private readonly _customizationDebugDetails = new Map < string , IExternalCustomizationResult > ( ) ;
114+ private readonly _lastChangedTypes = new Map < number , readonly string [ ] | undefined > ( ) ;
111115
112116 private readonly _pendingProgress = new Map < string , { progress : ( parts : IChatProgress [ ] ) => void ; chatSession : IChatModel | undefined } > ( ) ;
113117 private readonly _proxy : ExtHostChatAgentsShape2 ;
@@ -131,10 +135,19 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA
131135 @ILanguageModelToolsService private readonly _languageModelToolsService : ILanguageModelToolsService ,
132136 @ICustomizationHarnessService private readonly _customizationHarnessService : ICustomizationHarnessService ,
133137 @IConfigurationService private readonly _configurationService : IConfigurationService ,
138+ @IChatDebugService private readonly _chatDebugService : IChatDebugService ,
134139 ) {
135140 super ( ) ;
136141 this . _proxy = extHostContext . getProxy ( ExtHostContext . ExtHostChatAgents2 ) ;
137142
143+ // Register a resolve provider for customization discovery debug events.
144+ this . _register ( this . _chatDebugService . registerProvider ( {
145+ provideChatDebugLog : async ( ) => undefined ,
146+ resolveChatDebugLogEvent : async ( eventId ) => {
147+ return this . _resolveCustomizationEvent ( eventId ) ;
148+ }
149+ } ) ) ;
150+
138151 // When the provider API kill-switch is toggled off, dispose all registered providers
139152 this . _register ( this . _configurationService . onDidChangeConfiguration ( e => {
140153 if ( e . affectsConfiguration ( 'chat.customizations.providerApi.enabled' ) ) {
@@ -611,18 +624,18 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA
611624 return ;
612625 }
613626
614- const emitter = new Emitter < void > ( ) ;
627+ const emitter = new Emitter < IExternalCustomizationChangeEvent > ( ) ;
615628 this . _customizationProviderEmitters . set ( handle , emitter ) ;
616629
617630 // Build the item provider that calls back to the ExtHost
618631 const itemProvider : IExternalCustomizationItemProvider = {
619632 onDidChange : emitter . event ,
620633 provideChatSessionCustomizations : async ( token ) => {
621- const items = await this . _proxy . $provideChatSessionCustomizations ( handle , token ) ;
622- if ( ! items ) {
634+ const result = await this . _proxy . $provideChatSessionCustomizations ( handle , token ) ;
635+ if ( ! result ) {
623636 return undefined ;
624637 }
625- return items . map ( ( item : IChatSessionCustomizationItemDto ) : IExternalCustomizationItem => ( {
638+ const items = result . items . map ( ( item ) : IExternalCustomizationItem => ( {
626639 uri : URI . revive ( item . uri ) ,
627640 type : item . type ,
628641 name : item . name ,
@@ -631,6 +644,18 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA
631644 badge : item . badge ,
632645 badgeTooltip : item . badgeTooltip ,
633646 } ) ) ;
647+ const mapped : IExternalCustomizationResult = {
648+ items,
649+ diagnostics : result . diagnostics ? {
650+ scannedPaths : result . diagnostics . scannedPaths ?. map ( p => URI . revive ( p ) ) ,
651+ skippedFiles : result . diagnostics . skippedFiles ?. map ( f => ( {
652+ uri : URI . revive ( f . uri ) ,
653+ reason : f . reason ,
654+ } ) ) ,
655+ } : undefined ,
656+ } ;
657+ this . _logCustomizationResult ( handle , metadata . label , mapped ) ;
658+ return mapped ;
634659 } ,
635660 } ;
636661
@@ -642,6 +667,7 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA
642667 case 'instructions' : return AICustomizationManagementSection . Instructions ;
643668 case 'prompt' : return AICustomizationManagementSection . Prompts ;
644669 case 'hook' : return AICustomizationManagementSection . Hooks ;
670+ case 'plugins' : return AICustomizationManagementSection . Plugins ;
645671 default : return type ;
646672 }
647673 } ) ;
@@ -668,11 +694,83 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA
668694 this . _customizationProviderEmitters . deleteAndDispose ( handle ) ;
669695 }
670696
671- $onDidChangeCustomizations ( handle : number ) : void {
697+ $onDidChangeCustomizations ( handle : number , event : IChatSessionCustomizationChangeEventDto ) : void {
698+ this . _lastChangedTypes . set ( handle , event . changedTypes ) ;
672699 const emitter = this . _customizationProviderEmitters . get ( handle ) ;
673700 if ( emitter ) {
674- emitter . fire ( ) ;
701+ emitter . fire ( { changedTypes : event . changedTypes } ) ;
702+ }
703+ }
704+
705+ private _logCustomizationResult ( handle : number , label : string , result : IExternalCustomizationResult ) : void {
706+ const sessionResource = this . _chatDebugService . activeSessionResource ;
707+ if ( ! sessionResource ) {
708+ return ;
709+ }
710+
711+ const changedTypes = this . _lastChangedTypes . get ( handle ) ;
712+ this . _lastChangedTypes . delete ( handle ) ;
713+
714+ const eventId = generateUuid ( ) ;
715+ this . _customizationDebugDetails . set ( eventId , result ) ;
716+
717+ // Evict oldest entries when the map exceeds the cap.
718+ if ( this . _customizationDebugDetails . size > 10_000 ) {
719+ const first = this . _customizationDebugDetails . keys ( ) . next ( ) . value ;
720+ if ( first !== undefined ) {
721+ this . _customizationDebugDetails . delete ( first ) ;
722+ }
723+ }
724+
725+ const parts : string [ ] = [ ] ;
726+ parts . push ( `${ result . items . length } loaded` ) ;
727+ if ( result . diagnostics ?. skippedFiles ?. length ) {
728+ parts . push ( `${ result . diagnostics . skippedFiles . length } skipped` ) ;
729+ }
730+ if ( result . diagnostics ?. scannedPaths ?. length ) {
731+ parts . push ( `${ result . diagnostics . scannedPaths . length } paths scanned` ) ;
675732 }
733+ if ( changedTypes ?. length ) {
734+ parts . push ( `changed: [${ changedTypes . join ( ', ' ) } ]` ) ;
735+ }
736+
737+ this . _chatDebugService . log (
738+ sessionResource ,
739+ `Customization discovery (${ label } )` ,
740+ parts . join ( ' | ' ) ,
741+ ChatDebugLogLevel . Info ,
742+ { id : eventId , category : 'customization-provider' } ,
743+ ) ;
744+ }
745+
746+ private _resolveCustomizationEvent ( eventId : string ) : IChatDebugResolvedEventContent | undefined {
747+ const result = this . _customizationDebugDetails . get ( eventId ) ;
748+ if ( ! result ) {
749+ return undefined ;
750+ }
751+
752+ return {
753+ kind : 'fileList' ,
754+ discoveryType : 'customization-provider' ,
755+ files : [
756+ ...result . items . map ( item => ( {
757+ uri : item . uri ,
758+ name : item . name ,
759+ status : 'loaded' as const ,
760+ storage : item . groupKey ,
761+ } ) ) ,
762+ ...( result . diagnostics ?. skippedFiles ?? [ ] ) . map ( f => ( {
763+ uri : f . uri ,
764+ name : f . uri . path . split ( '/' ) . pop ( ) ,
765+ status : 'skipped' as const ,
766+ skipReason : f . reason ,
767+ } ) ) ,
768+ ] ,
769+ sourceFolders : result . diagnostics ?. scannedPaths ?. map ( uri => ( {
770+ uri,
771+ storage : 'external' ,
772+ } ) ) ,
773+ } ;
676774 }
677775}
678776
0 commit comments