4
4
*--------------------------------------------------------------------------------------------*/
5
5
6
6
import { getWindow } from '../../../../base/browser/dom.js' ;
7
+ import { raceCancellationError } from '../../../../base/common/async.js' ;
7
8
import { CancellationToken } from '../../../../base/common/cancellation.js' ;
9
+ import { matchesMimeType } from '../../../../base/common/dataTransfer.js' ;
10
+ import { CancellationError } from '../../../../base/common/errors.js' ;
8
11
import { Emitter , Event } from '../../../../base/common/event.js' ;
9
12
import { Disposable , DisposableStore , IDisposable } from '../../../../base/common/lifecycle.js' ;
10
13
import { autorun } from '../../../../base/common/observable.js' ;
@@ -14,8 +17,8 @@ import * as nls from '../../../../nls.js';
14
17
import { ExtensionIdentifier } from '../../../../platform/extensions/common/extensions.js' ;
15
18
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js' ;
16
19
import { IWebview , IWebviewService , WebviewContentPurpose } from '../../../contrib/webview/browser/webview.js' ;
17
- import { IExtensionService } from '../../../services/extensions/common/extensions.js' ;
18
- import { ExtensionsRegistry } from '../../../services/extensions/common/extensionsRegistry.js' ;
20
+ import { IExtensionService , isProposedApiEnabled } from '../../../services/extensions/common/extensions.js' ;
21
+ import { ExtensionsRegistry , IExtensionPointUser } from '../../../services/extensions/common/extensionsRegistry.js' ;
19
22
20
23
export interface IChatOutputItemRenderer {
21
24
renderOutputPart ( mime : string , data : Uint8Array , webview : IWebview , token : CancellationToken ) : Promise < void > ;
@@ -50,37 +53,48 @@ interface RenderOutputPartWebviewOptions {
50
53
}
51
54
52
55
56
+ interface RendererEntry {
57
+ readonly renderer : IChatOutputItemRenderer ;
58
+ readonly options : RegisterOptions ;
59
+ }
60
+
53
61
export class ChatOutputRendererService extends Disposable implements IChatOutputRendererService {
54
62
_serviceBrand : undefined ;
55
63
56
- private readonly _renderers = new Map < string , {
57
- readonly renderer : IChatOutputItemRenderer ;
58
- readonly options : RegisterOptions ;
64
+ private readonly _contributions = new Map < /*viewType*/ string , {
65
+ readonly mimes : readonly string [ ] ;
59
66
} > ( ) ;
60
67
68
+ private readonly _renderers = new Map < /*viewType*/ string , RendererEntry > ( ) ;
69
+
61
70
constructor (
62
71
@IWebviewService private readonly _webviewService : IWebviewService ,
63
72
@IExtensionService private readonly _extensionService : IExtensionService ,
64
73
) {
65
74
super ( ) ;
75
+
76
+ this . _register ( chatOutputRenderContributionPoint . setHandler ( extensions => {
77
+ this . updateContributions ( extensions ) ;
78
+ } ) ) ;
66
79
}
67
80
68
- registerRenderer ( mime : string , renderer : IChatOutputItemRenderer , options : RegisterOptions ) : IDisposable {
69
- this . _renderers . set ( mime , { renderer, options } ) ;
81
+ registerRenderer ( viewType : string , renderer : IChatOutputItemRenderer , options : RegisterOptions ) : IDisposable {
82
+ this . _renderers . set ( viewType , { renderer, options } ) ;
70
83
return {
71
84
dispose : ( ) => {
72
- this . _renderers . delete ( mime ) ;
85
+ this . _renderers . delete ( viewType ) ;
73
86
}
74
87
} ;
75
88
}
76
89
77
90
async renderOutputPart ( mime : string , data : Uint8Array , parent : HTMLElement , webviewOptions : RenderOutputPartWebviewOptions , token : CancellationToken ) : Promise < RenderedOutputPart > {
78
- // Activate extensions that contribute to chatOutputRenderer for this mime type
79
- await this . _extensionService . activateByEvent ( `onChatOutputRenderer:${ mime } ` ) ;
91
+ const rendererData = await this . getRenderer ( mime , token ) ;
92
+ if ( token . isCancellationRequested ) {
93
+ throw new CancellationError ( ) ;
94
+ }
80
95
81
- const rendererData = this . _renderers . get ( mime ) ;
82
96
if ( ! rendererData ) {
83
- throw new Error ( `No renderer registered for mime type: ${ mime } ` ) ;
97
+ throw new Error ( `No renderer registered found for mime type: ${ mime } ` ) ;
84
98
}
85
99
86
100
const store = new DisposableStore ( ) ;
@@ -120,19 +134,53 @@ export class ChatOutputRendererService extends Disposable implements IChatOutput
120
134
} ,
121
135
} ;
122
136
}
137
+
138
+ private async getRenderer ( mime : string , token : CancellationToken ) : Promise < RendererEntry | undefined > {
139
+ await raceCancellationError ( this . _extensionService . whenInstalledExtensionsRegistered ( ) , token ) ;
140
+ for ( const [ id , value ] of this . _contributions ) {
141
+ if ( value . mimes . some ( m => matchesMimeType ( m , [ mime ] ) ) ) {
142
+ await raceCancellationError ( this . _extensionService . activateByEvent ( `onChatOutputRenderer:${ id } ` ) , token ) ;
143
+ const rendererData = this . _renderers . get ( id ) ;
144
+ if ( rendererData ) {
145
+ return rendererData ;
146
+ }
147
+ }
148
+ }
149
+
150
+ return undefined ;
151
+ }
152
+
153
+ private updateContributions ( extensions : readonly IExtensionPointUser < readonly IChatOutputRendererContribution [ ] > [ ] ) {
154
+ this . _contributions . clear ( ) ;
155
+ for ( const extension of extensions ) {
156
+ if ( ! isProposedApiEnabled ( extension . description , 'chatOutputRenderer' ) ) {
157
+ continue ;
158
+ }
159
+
160
+ for ( const contribution of extension . value ) {
161
+ if ( this . _contributions . has ( contribution . viewType ) ) {
162
+ extension . collector . error ( `Chat output renderer with view type '${ contribution . viewType } ' already registered` ) ;
163
+ continue ;
164
+ }
165
+
166
+ this . _contributions . set ( contribution . viewType , {
167
+ mimes : contribution . mimeTypes ,
168
+ } ) ;
169
+ }
170
+ }
171
+ }
123
172
}
124
173
125
174
interface IChatOutputRendererContribution {
175
+ readonly viewType : string ;
126
176
readonly mimeTypes : readonly string [ ] ;
127
177
}
128
178
129
- ExtensionsRegistry . registerExtensionPoint < IChatOutputRendererContribution [ ] > ( {
130
- extensionPoint : 'chatOutputRenderer ' ,
179
+ const chatOutputRenderContributionPoint = ExtensionsRegistry . registerExtensionPoint < IChatOutputRendererContribution [ ] > ( {
180
+ extensionPoint : 'chatOutputRenderers ' ,
131
181
activationEventsGenerator : ( contributions : IChatOutputRendererContribution [ ] , result ) => {
132
182
for ( const contrib of contributions ) {
133
- for ( const mime of contrib . mimeTypes ) {
134
- result . push ( `onChatOutputRenderer:${ mime } ` ) ;
135
- }
183
+ result . push ( `onChatOutputRenderer:${ contrib . viewType } ` ) ;
136
184
}
137
185
} ,
138
186
jsonSchema : {
@@ -141,11 +189,15 @@ ExtensionsRegistry.registerExtensionPoint<IChatOutputRendererContribution[]>({
141
189
items : {
142
190
type : 'object' ,
143
191
additionalProperties : false ,
144
- required : [ 'mimeTypes' ] ,
192
+ required : [ 'viewType' , ' mimeTypes'] ,
145
193
properties : {
194
+ viewType : {
195
+ type : 'string' ,
196
+ description : nls . localize ( 'chatOutputRenderer.viewType' , 'Unique identifier for the renderer.' ) ,
197
+ } ,
146
198
mimeTypes : {
147
- description : nls . localize ( 'chatOutputRenderer.mimeTypes' , 'MIME types that this renderer can handle' ) ,
148
199
type : 'array' ,
200
+ description : nls . localize ( 'chatOutputRenderer.mimeTypes' , 'MIME types that this renderer can handle' ) ,
149
201
items : {
150
202
type : 'string'
151
203
}
0 commit comments