Skip to content

Commit 487f330

Browse files
committed
Continue work
1 parent abda8a2 commit 487f330

File tree

7 files changed

+100
-41
lines changed

7 files changed

+100
-41
lines changed

src/vs/workbench/api/browser/mainThreadChatOutputRenderer.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55

66
import { VSBuffer } from '../../../base/common/buffer.js';
77
import { Disposable, IDisposable } from '../../../base/common/lifecycle.js';
8-
import { IChatOutputItemRendererService } from '../../contrib/chat/browser/chatOutputItemRenderer.js';
8+
import { URI, UriComponents } from '../../../base/common/uri.js';
9+
import { ExtensionIdentifier } from '../../../platform/extensions/common/extensions.js';
10+
import { IChatOutputRendererService } from '../../contrib/chat/browser/chatOutputItemRenderer.js';
911
import { IExtHostContext } from '../../services/extensions/common/extHostCustomers.js';
1012
import { ExtHostChatOutputRendererShape, ExtHostContext, MainThreadChatOutputRendererShape } from '../common/extHost.protocol.js';
1113
import { MainThreadWebviews } from './mainThreadWebviews.js';
@@ -21,7 +23,7 @@ export class MainThreadChatOutputRenderer extends Disposable implements MainThre
2123
constructor(
2224
extHostContext: IExtHostContext,
2325
private readonly _mainThreadWebview: MainThreadWebviews,
24-
@IChatOutputItemRendererService private readonly _rendererService: IChatOutputItemRendererService,
26+
@IChatOutputRendererService private readonly _rendererService: IChatOutputRendererService,
2527
) {
2628
super();
2729
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostChatOutputRenderer);
@@ -34,7 +36,7 @@ export class MainThreadChatOutputRenderer extends Disposable implements MainThre
3436
this.registeredRenderers.clear();
3537
}
3638

37-
$registerChatOutputRenderer(mime: string): void {
39+
$registerChatOutputRenderer(mime: string, extensionId: ExtensionIdentifier, extensionLocation: UriComponents): void {
3840
this._rendererService.registerRenderer(mime, {
3941
renderOutputPart: async (mime, data, webview, token) => {
4042
const webviewHandle = `chat-output-${++this._webviewHandlePool}`;
@@ -43,8 +45,10 @@ export class MainThreadChatOutputRenderer extends Disposable implements MainThre
4345
serializeBuffersForPostMessage: true,
4446
});
4547

46-
this._proxy.$renderChatPart(mime, VSBuffer.wrap(data), webviewHandle, token);
48+
this._proxy.$renderChatOutput(mime, VSBuffer.wrap(data), webviewHandle, token);
4749
},
50+
}, {
51+
extension: { id: extensionId, location: URI.revive(extensionLocation) }
4852
});
4953
}
5054

src/vs/workbench/api/common/extHost.protocol.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1453,12 +1453,12 @@ export interface ExtHostUriOpenersShape {
14531453
}
14541454

14551455
export interface MainThreadChatOutputRendererShape extends IDisposable {
1456-
$registerChatOutputRenderer(mime: string): void;
1456+
$registerChatOutputRenderer(mime: string, extensionId: ExtensionIdentifier, extensionLocation: UriComponents): void;
14571457
$unregisterChatOutputRenderer(mime: string): void;
14581458
}
14591459

14601460
export interface ExtHostChatOutputRendererShape {
1461-
$renderChatPart(mime: string, valueData: VSBuffer, webviewHandle: string, token: CancellationToken): Promise<void>;
1461+
$renderChatOutput(mime: string, valueData: VSBuffer, webviewHandle: string, token: CancellationToken): Promise<void>;
14621462
}
14631463

14641464
export interface MainThreadProfileContentHandlersShape {

src/vs/workbench/api/common/extHostChatOutputRenderer.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ export class ExtHostChatOutputRenderer implements ExtHostChatOutputRendererShape
1515

1616
private readonly _proxy: MainThreadChatOutputRendererShape;
1717

18-
private readonly _renderers = new Map</*mime*/ string, { renderer: vscode.ChatOutputRenderer; extension: IExtensionDescription }>();
18+
private readonly _renderers = new Map</*mime*/ string, {
19+
readonly renderer: vscode.ChatOutputRenderer;
20+
readonly extension: IExtensionDescription;
21+
}>();
1922

2023
constructor(
2124
mainContext: IMainContext,
@@ -26,27 +29,25 @@ export class ExtHostChatOutputRenderer implements ExtHostChatOutputRendererShape
2629

2730
registerChatOutputRenderer(extension: IExtensionDescription, mime: string, renderer: vscode.ChatOutputRenderer): vscode.Disposable {
2831
if (this._renderers.has(mime)) {
29-
throw new Error(`Chat response output renderer already registered for mime type: ${mime}`);
32+
throw new Error(`Chat output renderer already registered for mime type: ${mime}`);
3033
}
3134

3235
this._renderers.set(mime, { extension, renderer });
33-
this._proxy.$registerChatOutputRenderer(mime);
36+
this._proxy.$registerChatOutputRenderer(mime, extension.identifier, extension.extensionLocation);
3437

3538
return new Disposable(() => {
3639
this._renderers.delete(mime);
3740
this._proxy.$unregisterChatOutputRenderer(mime);
3841
});
3942
}
4043

41-
async $renderChatPart(mime: string, valueData: VSBuffer, webviewHandle: string, token: CancellationToken): Promise<void> {
44+
async $renderChatOutput(mime: string, valueData: VSBuffer, webviewHandle: string, token: CancellationToken): Promise<void> {
4245
const entry = this._renderers.get(mime);
4346
if (!entry) {
44-
throw new Error(`No chat response output renderer registered for mime type: ${mime}`);
47+
throw new Error(`No chat output renderer registered for mime type: ${mime}`);
4548
}
4649

4750
const webview = this.webviews.createNewWebview(webviewHandle, {}, entry.extension);
48-
49-
const part = Object.freeze<vscode.ToolResultDataOutput>({ mime, value: valueData.buffer });
50-
return entry.renderer.renderChatOutput(part, webview, token);
51+
return entry.renderer.renderChatOutput(valueData.buffer, webview, token);
5152
}
5253
}

src/vs/workbench/contrib/chat/browser/chat.contribution.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ import { ChatDynamicVariableModel } from './contrib/chatDynamicVariables.js';
114114
import { ChatAttachmentResolveService, IChatAttachmentResolveService } from './chatAttachmentResolveService.js';
115115
import { registerLanguageModelActions } from './actions/chatLanguageModelActions.js';
116116
import { PromptUrlHandler } from './promptSyntax/promptUrlHandler.js';
117+
import { ChatOutputRendererService, IChatOutputRendererService } from './chatOutputItemRenderer.js';
117118

118119
// Register configuration
119120
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
@@ -782,6 +783,7 @@ registerSingleton(IPromptsService, PromptsService, InstantiationType.Delayed);
782783
registerSingleton(IChatContextPickService, ChatContextPickService, InstantiationType.Delayed);
783784
registerSingleton(IChatModeService, ChatModeService, InstantiationType.Delayed);
784785
registerSingleton(IChatAttachmentResolveService, ChatAttachmentResolveService, InstantiationType.Delayed);
786+
registerSingleton(IChatOutputRendererService, ChatOutputRendererService, InstantiationType.Delayed);
785787

786788
registerWorkbenchContribution2(ChatEditingNotebookFileSystemProviderContrib.ID, ChatEditingNotebookFileSystemProviderContrib, WorkbenchPhase.BlockStartup);
787789

src/vs/workbench/contrib/chat/browser/chatOutputItemRenderer.ts

Lines changed: 64 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,34 @@ import { CancellationToken } from '../../../../base/common/cancellation.js';
88
import { Disposable, IDisposable } from '../../../../base/common/lifecycle.js';
99
import { URI } from '../../../../base/common/uri.js';
1010
import { generateUuid } from '../../../../base/common/uuid.js';
11+
import * as nls from '../../../../nls.js';
1112
import { ExtensionIdentifier } from '../../../../platform/extensions/common/extensions.js';
12-
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
1313
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
1414
import { ILogService } from '../../../../platform/log/common/log.js';
15-
import { IWebview, IWebviewService } from '../../../contrib/webview/browser/webview.js';
15+
import { IWebview, IWebviewService, WebviewContentPurpose } from '../../../contrib/webview/browser/webview.js';
16+
import { IExtensionService } from '../../../services/extensions/common/extensions.js';
17+
import { ExtensionsRegistry } from '../../../services/extensions/common/extensionsRegistry.js';
1618

1719
export interface IChatOutputItemRenderer {
18-
renderOutputPart(mime: string, data: Uint8Array, webivew: IWebview, token: CancellationToken): Promise<void>;
20+
renderOutputPart(mime: string, data: Uint8Array, webview: IWebview, token: CancellationToken): Promise<void>;
1921
}
2022

21-
export const IChatOutputItemRendererService = createDecorator<IChatOutputItemRendererService>('chatOutputItemRendererService');
23+
export const IChatOutputRendererService = createDecorator<IChatOutputRendererService>('chatOutputRendererService');
24+
25+
interface RegisterOptions {
26+
readonly extension?: {
27+
readonly id: ExtensionIdentifier;
28+
readonly location: URI;
29+
};
30+
}
2231

2332
/**
2433
* Service for rendering chat output items with special MIME types using registered renderers from extensions.
2534
*/
26-
export interface IChatOutputItemRendererService {
35+
export interface IChatOutputRendererService {
2736
readonly _serviceBrand: undefined;
2837

29-
registerRenderer(mime: string, renderer: IChatOutputItemRenderer): IDisposable;
38+
registerRenderer(mime: string, renderer: IChatOutputItemRenderer, options: RegisterOptions): IDisposable;
3039

3140
renderOutputPart(mime: string, data: Uint8Array, parent: HTMLElement, token: CancellationToken): Promise<IDisposable>;
3241
}
@@ -36,21 +45,25 @@ export interface IChatOutputItemRendererService {
3645
* This service connects with the MainThreadChatResponseOutputRenderer to render output parts
3746
* in chat responses using extension-provided renderers.
3847
*/
39-
export class ChatOutputItemRendererService extends Disposable implements IChatOutputItemRendererService {
48+
export class ChatOutputRendererService extends Disposable implements IChatOutputRendererService {
4049
_serviceBrand: undefined;
4150

42-
private readonly _renderers = new Map<string, IChatOutputItemRenderer>();
51+
private readonly _renderers = new Map<string, {
52+
readonly renderer: IChatOutputItemRenderer;
53+
readonly options: RegisterOptions;
54+
}>();
4355

4456
constructor(
4557
@ILogService private readonly _logService: ILogService,
4658
@IWebviewService private readonly _webviewService: IWebviewService,
59+
@IExtensionService private readonly _extensionService: IExtensionService,
4760
) {
4861
super();
4962
this._logService.debug('ChatOutputItemRendererService: Created');
5063
}
5164

52-
registerRenderer(mime: string, renderer: IChatOutputItemRenderer): IDisposable {
53-
this._renderers.set(mime, renderer);
65+
registerRenderer(mime: string, renderer: IChatOutputItemRenderer, options: RegisterOptions): IDisposable {
66+
this._renderers.set(mime, { renderer, options });
5467
this._logService.debug(`ChatOutputItemRendererService: Registered renderer for MIME type ${mime}`);
5568
return {
5669
dispose: () => {
@@ -60,35 +73,62 @@ export class ChatOutputItemRendererService extends Disposable implements IChatOu
6073
}
6174

6275
async renderOutputPart(mime: string, data: Uint8Array, parent: HTMLElement, token: CancellationToken): Promise<IDisposable> {
63-
const renderer = this._renderers.get(mime);
64-
if (!renderer) {
76+
// Activate extensions that contribute to chatOutputRenderer for this mime type
77+
await this._extensionService.activateByEvent(`onChatOutputRenderer:${mime}`);
78+
79+
const rendererData = this._renderers.get(mime);
80+
if (!rendererData) {
6581
throw new Error(`No renderer registered for mime type: ${mime}`);
6682
}
6783

6884
const webview = this._webviewService.createWebviewElement({
69-
title: 'My fancy chat renderer',
85+
title: '',
7086
origin: generateUuid(),
7187
options: {
72-
88+
enableFindWidget: false,
89+
purpose: WebviewContentPurpose.ChatOutputItem,
90+
tryRestoreScrollPosition: false,
7391
},
74-
contentOptions: {
75-
localResourceRoots: [],
76-
allowScripts: true,
77-
},
78-
extension: { id: new ExtensionIdentifier('xxx.yyy'), location: URI.file('/') }
92+
contentOptions: {},
93+
extension: rendererData.options.extension ? rendererData.options.extension : undefined,
7994
});
8095

81-
parent.style = 'max-height: 80vh; width: 100%;';
8296
webview.mountTo(parent, getWindow(parent));
8397

84-
await renderer.renderOutputPart(mime, data, webview, token);
98+
await rendererData.renderer.renderOutputPart(mime, data, webview, token);
8599

86100
return {
87-
dispose: () => { }
101+
dispose: () => {
102+
webview.dispose();
103+
}
88104
};
89105
}
90106
}
91107

92-
// Register the service
93-
registerSingleton(IChatOutputItemRendererService, ChatOutputItemRendererService, InstantiationType.Delayed);
108+
interface IChatOutputRendererContribution {
109+
readonly mimeTypes: readonly string[];
110+
}
111+
112+
ExtensionsRegistry.registerExtensionPoint<IChatOutputRendererContribution[]>({
113+
extensionPoint: 'chatOutputRenderer',
114+
jsonSchema: {
115+
description: nls.localize('vscode.extension.contributes.chatOutputRenderer', 'Contributes a renderer for specific MIME types in chat outputs'),
116+
type: 'array',
117+
items: {
118+
type: 'object',
119+
additionalProperties: false,
120+
required: ['mimeTypes'],
121+
properties: {
122+
mimeTypes: {
123+
description: nls.localize('chatOutputRenderer.mimeTypes', 'MIME types that this renderer can handle'),
124+
type: 'array',
125+
items: {
126+
type: 'string'
127+
}
128+
}
129+
}
130+
}
131+
}
132+
});
133+
94134

src/vs/workbench/contrib/webview/browser/webview.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ export const enum WebviewContentPurpose {
8484
NotebookRenderer = 'notebookRenderer',
8585
CustomEditor = 'customEditor',
8686
WebviewView = 'webviewView',
87+
ChatOutputItem = 'chatOutputItem',
8788
}
8889

8990
export type WebviewStyles = { readonly [key: string]: string | number };

src/vscode-dts/vscode.proposed.chatOutputRenderer.d.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,27 @@ declare module 'vscode' {
4040
* @param token A cancellation token that is cancelled if we no longer care about the rendering before this
4141
* call completes.
4242
*
43-
* @returns A promise that resolves when the rendering is complete.
43+
* @returns A promise that resolves when the webview has been initialized and is ready to be presented to the user.
4444
*/
45-
renderChatOutput(data: ToolResultDataOutput, webview: Webview, token: CancellationToken): Thenable<void>;
45+
renderChatOutput(data: Uint8Array, webview: Webview, token: CancellationToken): Thenable<void>;
4646
}
4747

4848
export namespace chat {
4949
/**
5050
* Registers a new renderer for a given mime type.
5151
*
52-
* TODO: needs contribution point so we know which mimes are available.
52+
* Note: To use this API, you should also add a contribution point in your extension's
53+
* package.json:
54+
*
55+
* ```json
56+
* "contributes": {
57+
* "chatOutputRenderer": [
58+
* {
59+
* "mimeTypes": ["application/your-mime-type"]
60+
* }
61+
* ]
62+
* }
63+
* ```
5364
*
5465
* @param mime The MIME type of the output that this renderer can handle.
5566
* @param renderer The renderer to register.

0 commit comments

Comments
 (0)